mirror of https://github.com/pixelfed/pixelfed
More dusting (cleanup + lint)
parent
d02868ba9a
commit
5d00f2e69b
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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…
Reference in New Issue