More dusting (cleanup + lint)

pull/7387/head
Daniel Supernault 3 days ago
parent d02868ba9a
commit 5d00f2e69b
No known key found for this signature in database
GPG Key ID: 23740873EE6F76A1

@ -1,249 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Models\AccountLog;
use App\Models\Follower;
use App\Models\Like;
use App\Models\Status;
use App\Models\StatusHashtag;
use App\Models\User;
use Illuminate\Contracts\View\View;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
class SeasonalController extends Controller
{
public function __construct()
{
$this->middleware('auth');
}
public function yearInReview(): View
{
abort_if(now()->gt('2021-03-01 00:00:00'), 404);
abort_if(! db_is_mysql_maria(), 404);
$profile = Auth::user()->profile;
return view('account.yir', ['profile' => $profile]);
}
public function getData(Request $request): JsonResponse
{
abort_if(now()->gt('2021-03-01 00:00:00'), 404);
abort_if(! db_is_mysql_maria(), 404);
$uid = $request->user()->id;
$pid = $request->user()->profile_id;
$epoch = '2020-01-01 00:00:00';
$epochStart = '2020-01-01 00:00:00';
$epochEnd = '2020-12-31 23:59:59';
$siteKey = 'seasonal:my2020:shared';
$siteTtl = now()->addMonths(3);
$userKey = 'seasonal:my2020:user:'.$uid;
$userTtl = now()->addMonths(3);
$shared = Cache::remember($siteKey, $siteTtl, function () use ($epochStart, $epochEnd) {
return [
'average' => [
'posts' => round((float) DB::query()->fromSub(
Status::query()
->whereNull('uri')
->whereIn('type', ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
->where('created_at', '>', $epochStart)
->where('created_at', '<', $epochEnd)
->groupBy('profile_id')
->selectRaw('count(*) as count'),
'per_profile'
)->avg('count')),
'likes' => round((float) DB::query()->fromSub(
Like::query()
->where('created_at', '>', $epochStart)
->where('created_at', '<', $epochEnd)
->groupBy('profile_id')
->selectRaw('count(*) as count'),
'per_profile'
)->avg('count')),
],
'popular' => [
'hashtag' => StatusHashtag::selectRaw('*,count(hashtag_id) as count')
->where('created_at', '>', $epochStart)
->where('created_at', '<', $epochEnd)
->groupBy('hashtag_id')
->orderByDesc('count')
->take(1)
->get()
->map(function ($sh) {
return [
'name' => $sh->hashtag->name,
'count' => $sh->count,
];
})
->first(),
'post' => Status::whereScope('public')
->where('likes_count', '>', 1)
->whereIsNsfw(false)
->where('created_at', '>', $epochStart)
->where('created_at', '<', $epochEnd)
->orderByDesc('likes_count')
->take(1)
->get()
->map(function ($status) {
return [
'id' => (string) $status->id,
'username' => (string) $status->profile->username,
'created_at' => $status->created_at->format('M d, Y'),
'type' => $status->type,
'url' => $status->url(),
'thumb' => $status->thumb(),
'likes_count' => $status->likes_count,
'reblogs_count' => $status->reblogs_count,
'reply_count' => $status->reply_count ?? 0,
];
})
->first(),
'places' => Status::selectRaw('*, count(place_id) as count')
->whereNotNull('place_id')
->having('count', '>', 1)
->where('created_at', '>', $epochStart)
->where('created_at', '<', $epochEnd)
->groupBy('place_id')
->orderByDesc('count')
->take(1)
->get()
->map(function ($sh) {
return [
'name' => $sh->place->getName(),
'url' => $sh->place->url(),
'count' => $sh->count,
];
})
->first(),
],
];
});
$res = Cache::remember($userKey, $userTtl, function () use ($pid, $epochStart, $epochEnd, $request) {
return [
'account' => [
'user_id' => $request->user()->id,
'created_at' => $request->user()->created_at->format('M d, Y'),
'created_this_year' => $request->user()->created_at->gt('2020-01-01 00:00:00'),
'created_months_ago' => $request->user()->created_at->diffInMonths(now()),
'followers_this_year' => Follower::whereFollowingId($pid)
->where('created_at', '>', $epochStart)
->where('created_at', '<', $epochEnd)
->count(),
'followed_this_year' => Follower::whereProfileId($pid)
->where('created_at', '>', $epochStart)
->where('created_at', '<', $epochEnd)
->count(),
'most_popular' => Status::whereProfileId($pid)
->where('likes_count', '>', 1)
->where('created_at', '>', $epochStart)
->where('created_at', '<', $epochEnd)
->orderByDesc('likes_count')
->take(1)
->get()
->map(function ($status) {
return [
'id' => (string) $status->id,
'username' => (string) $status->profile->username,
'created_at' => $status->created_at->format('M d, Y'),
'type' => $status->type,
'url' => $status->url(),
'thumb' => $status->thumb(),
'likes_count' => $status->likes_count,
'reblogs_count' => $status->reblogs_count,
'reply_count' => $status->reply_count ?? 0,
];
})
->first(),
'posts_count' => Status::whereProfileId($pid)
->whereIn('type', ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
->where('created_at', '>', $epochStart)
->where('created_at', '<', $epochEnd)
->count(),
'likes_count' => Like::whereProfileId($pid)
->where('created_at', '>', $epochStart)
->where('created_at', '<', $epochEnd)
->count(),
'hashtag' => StatusHashtag::selectRaw('*, count(hashtag_id) as count')
->whereProfileId($pid)
->where('created_at', '>', $epochStart)
->where('created_at', '<', $epochEnd)
->groupBy('profile_id')
->orderByDesc('count')
->take(1)
->get()
->map(function ($sh) {
return [
'name' => $sh->hashtag->name,
'count' => $sh->count,
];
})
->first(),
'places' => Status::selectRaw('*, count(place_id) as count')
->whereNotNull('place_id')
->having('count', '>', 1)
->whereProfileId($pid)
->where('created_at', '>', $epochStart)
->where('created_at', '<', $epochEnd)
->groupBy('place_id')
->orderByDesc('count')
->take(1)
->get()
->map(function ($sh) {
return [
'name' => $sh->place->getName(),
'url' => $sh->place->url(),
'count' => $sh->count,
];
})
->first(),
'places_total' => Status::whereProfileId($pid)
->where('created_at', '>', $epochStart)
->where('created_at', '<', $epochEnd)
->whereNotNull('place_id')
->count(),
],
];
});
return response()->json(array_merge($res, $shared));
}
public function store(Request $request): JsonResponse
{
abort_if(now()->gt('2021-03-01 00:00:00'), 404);
abort_if(! db_is_mysql_maria(), 404);
$user = $request->user();
$log = AccountLog::firstOrCreate([
[
'item_type' => User::class,
'item_id' => $user->id,
'user_id' => $user->id,
'action' => 'seasonal.my2020.view',
],
[
'ip_address' => $request->ip(),
'user_agent' => $request->userAgent(),
],
]);
return response()->json(200);
}
}

@ -126,7 +126,7 @@ class Group extends Model
public function getMembershipType(): string
{
return $this->is_private ? 'private' : ($this->is_local ? 'local' : 'all');
return $this->is_private ? 'private' : ($this->local ? 'local' : 'all');
}
public function selfRole($id = false)

@ -18,7 +18,6 @@ use App\Http\Controllers\PortfolioController;
use App\Http\Controllers\ProfileSponsorController;
use App\Http\Controllers\PublicApiController;
use App\Http\Controllers\SearchController;
use App\Http\Controllers\SeasonalController;
use App\Http\Controllers\SoftwareUpdateController;
use App\Http\Controllers\SpaController;
use App\Http\Controllers\StatusController;
@ -119,8 +118,6 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['localization'])->grou
Route::get('discover/posts/trending', [DiscoverController::class, 'trendingApi']);
Route::get('discover/posts/hashtags', [DiscoverController::class, 'trendingHashtags']);
Route::get('discover/posts/places', [DiscoverController::class, 'trendingPlaces']);
Route::get('seasonal/yir', [SeasonalController::class, 'getData']);
Route::post('seasonal/yir', [SeasonalController::class, 'store']);
Route::get('mutes', [AccountController::class, 'accountMutesV2']);
Route::get('blocks', [AccountController::class, 'accountBlocksV2']);
Route::get('filters', [AccountController::class, 'accountFiltersV2']);

@ -1,81 +0,0 @@
<?php
use App\Models\Status;
use Illuminate\Foundation\Testing\LazilyRefreshDatabase;
use Illuminate\Support\Facades\DB;
uses(LazilyRefreshDatabase::class);
/*
|--------------------------------------------------------------------------
| Seasonal "Year in Review" aggregation
|--------------------------------------------------------------------------
|
| SeasonalController::getData previously computed the average number of
| posts per profile by loading every grouped row into PHP and calling
| ->pluck('count')->avg(). This verifies the SQL-side replacement:
| AVG over a subquery of per-profile counts returns the same value
| without materialising every group in memory.
|
*/
/**
* Mirrors the aggregation used in SeasonalController::getData for
* average posts per profile.
*/
function averagePostsPerProfile(string $epochStart, string $epochEnd): float
{
return (float) DB::query()->fromSub(
Status::query()
->whereNull('uri')
->whereIn('type', ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
->where('created_at', '>', $epochStart)
->where('created_at', '<', $epochEnd)
->groupBy('profile_id')
->selectRaw('count(*) as count'),
'per_profile'
)->avg('count');
}
it('averages matching posts per profile in sql', function () {
$epochStart = '2020-01-01 00:00:00';
$epochEnd = '2020-12-31 23:59:59';
$inRange = '2020-06-01 12:00:00';
// Profile 1: 2 matching photos. Profile 2: 4 matching photos.
// Average per profile = 3.
Status::factory()->count(2)->photo()->create([
'profile_id' => 1001,
'created_at' => $inRange,
]);
Status::factory()->count(4)->photo()->create([
'profile_id' => 1002,
'created_at' => $inRange,
]);
// Noise that must be excluded from the average:
// remote (uri set), wrong type, and out-of-range date.
Status::factory()->photo()->create([
'profile_id' => 1003,
'uri' => 'https://remote.example/statuses/1',
'created_at' => $inRange,
]);
Status::factory()->create([
'profile_id' => 1003,
'type' => 'text',
'created_at' => $inRange,
]);
Status::factory()->photo()->create([
'profile_id' => 1004,
'created_at' => '2019-01-01 00:00:00',
]);
expect(averagePostsPerProfile($epochStart, $epochEnd))->toBe(3.0);
});
it('returns zero when no posts match', function () {
$average = averagePostsPerProfile('2020-01-01 00:00:00', '2020-12-31 23:59:59');
// No rows -> AVG returns null -> cast to 0.0
expect($average)->toBe(0.0);
});
Loading…
Cancel
Save