feat: add community activity feed and mentions
This commit is contained in:
45
app/Http/Controllers/Api/CommunityActivityController.php
Normal file
45
app/Http/Controllers/Api/CommunityActivityController.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\CommunityActivityService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
final class CommunityActivityController extends Controller
|
||||
{
|
||||
public function __construct(private readonly CommunityActivityService $activityService)
|
||||
{
|
||||
}
|
||||
|
||||
public function index(Request $request): JsonResponse
|
||||
{
|
||||
$filter = $this->resolveFilter($request);
|
||||
|
||||
if ($this->activityService->requiresAuthentication($filter) && ! $request->user()) {
|
||||
return response()->json(['error' => 'Unauthenticated'], 401);
|
||||
}
|
||||
|
||||
$feed = $this->activityService->getFeed(
|
||||
viewer: $request->user(),
|
||||
filter: $filter,
|
||||
page: (int) $request->query('page', 1),
|
||||
perPage: (int) $request->query('per_page', CommunityActivityService::DEFAULT_PER_PAGE),
|
||||
actorUserId: $request->filled('user_id') ? (int) $request->query('user_id') : null,
|
||||
);
|
||||
|
||||
return response()->json($feed);
|
||||
}
|
||||
|
||||
private function resolveFilter(Request $request): string
|
||||
{
|
||||
if ($request->boolean('following') && ! $request->filled('filter')) {
|
||||
return 'following';
|
||||
}
|
||||
|
||||
return (string) $request->query('filter', 'all');
|
||||
}
|
||||
}
|
||||
@@ -5,120 +5,50 @@ declare(strict_types=1);
|
||||
namespace App\Http\Controllers\Web;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\ActivityEvent;
|
||||
use App\Models\Artwork;
|
||||
use App\Models\User;
|
||||
use App\Services\CommunityActivityService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* Community activity feed.
|
||||
*
|
||||
* GET /community/activity?type=global|following
|
||||
*/
|
||||
final class CommunityActivityController extends Controller
|
||||
{
|
||||
private const PER_PAGE = 30;
|
||||
public function __construct(private readonly CommunityActivityService $activityService)
|
||||
{
|
||||
}
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
$user = $request->user();
|
||||
$type = $request->query('type', 'global'); // global | following
|
||||
$perPage = self::PER_PAGE;
|
||||
|
||||
$query = ActivityEvent::query()
|
||||
->orderByDesc('created_at')
|
||||
->with(['actor:id,name,username']);
|
||||
|
||||
if ($type === 'following' && $user) {
|
||||
// Show only events from followed users
|
||||
$followingIds = DB::table('user_followers')
|
||||
->where('follower_id', $user->id)
|
||||
->pluck('user_id')
|
||||
->all();
|
||||
|
||||
if (empty($followingIds)) {
|
||||
$query->whereRaw('0 = 1'); // empty result set
|
||||
} else {
|
||||
$query->whereIn('actor_id', $followingIds);
|
||||
}
|
||||
$filter = $this->resolveFilter($request);
|
||||
if ($this->activityService->requiresAuthentication($filter) && ! $request->user()) {
|
||||
$filter = 'all';
|
||||
}
|
||||
|
||||
$events = $query->paginate($perPage)->withQueryString();
|
||||
$enriched = $this->enrich($events->getCollection());
|
||||
$feed = $this->activityService->getFeed(
|
||||
viewer: $request->user(),
|
||||
filter: $filter,
|
||||
page: 1,
|
||||
perPage: CommunityActivityService::DEFAULT_PER_PAGE,
|
||||
actorUserId: $request->filled('user_id') ? (int) $request->query('user_id') : null,
|
||||
);
|
||||
|
||||
return view('web.community.activity', [
|
||||
'events' => $events,
|
||||
'enriched' => $enriched,
|
||||
'active_tab' => $type,
|
||||
return view('web.comments.latest', [
|
||||
'page_title' => 'Community Activity',
|
||||
'props' => [
|
||||
'initialActivities' => $feed['data'],
|
||||
'initialMeta' => $feed['meta'],
|
||||
'initialFilter' => $feed['filter'],
|
||||
'initialUserId' => $request->filled('user_id') ? (int) $request->query('user_id') : null,
|
||||
'isAuthenticated' => (bool) $request->user(),
|
||||
],
|
||||
'initialFilter' => $feed['filter'],
|
||||
'initialUserId' => $request->filled('user_id') ? (int) $request->query('user_id') : null,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach target object data to each event for display.
|
||||
*/
|
||||
private function enrich(\Illuminate\Support\Collection $events): \Illuminate\Support\Collection
|
||||
private function resolveFilter(Request $request): string
|
||||
{
|
||||
// Collect artwork IDs and user IDs to eager-load
|
||||
$artworkIds = $events
|
||||
->where('target_type', ActivityEvent::TARGET_ARTWORK)
|
||||
->pluck('target_id')
|
||||
->unique()
|
||||
->values()
|
||||
->all();
|
||||
if ($request->boolean('following') && ! $request->filled('filter')) {
|
||||
return 'following';
|
||||
}
|
||||
|
||||
$userIds = $events
|
||||
->where('target_type', ActivityEvent::TARGET_USER)
|
||||
->pluck('target_id')
|
||||
->unique()
|
||||
->values()
|
||||
->all();
|
||||
|
||||
$artworks = Artwork::whereIn('id', $artworkIds)
|
||||
->with('user:id,name,username')
|
||||
->get(['id', 'title', 'slug', 'user_id', 'hash', 'thumb_ext'])
|
||||
->keyBy('id');
|
||||
|
||||
$users = User::whereIn('id', $userIds)
|
||||
->with('profile:user_id,avatar_hash')
|
||||
->get(['id', 'name', 'username'])
|
||||
->keyBy('id');
|
||||
|
||||
return $events->map(function (ActivityEvent $event) use ($artworks, $users): array {
|
||||
$target = null;
|
||||
|
||||
if ($event->target_type === ActivityEvent::TARGET_ARTWORK) {
|
||||
$artwork = $artworks->get($event->target_id);
|
||||
$target = $artwork ? [
|
||||
'id' => $artwork->id,
|
||||
'title' => $artwork->title,
|
||||
'url' => '/art/' . $artwork->id . '/' . $artwork->slug,
|
||||
'thumb' => $artwork->thumbUrl('sm'),
|
||||
] : null;
|
||||
} elseif ($event->target_type === ActivityEvent::TARGET_USER) {
|
||||
$u = $users->get($event->target_id);
|
||||
$target = $u ? [
|
||||
'id' => $u->id,
|
||||
'name' => $u->name,
|
||||
'username' => $u->username,
|
||||
'url' => '/@' . $u->username,
|
||||
] : null;
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $event->id,
|
||||
'type' => $event->type,
|
||||
'target_type' => $event->target_type,
|
||||
'actor' => [
|
||||
'id' => $event->actor?->id,
|
||||
'name' => $event->actor?->name,
|
||||
'username' => $event->actor?->username,
|
||||
'url' => '/@' . $event->actor?->username,
|
||||
],
|
||||
'target' => $target,
|
||||
'created_at' => $event->created_at?->toIso8601String(),
|
||||
];
|
||||
});
|
||||
return (string) $request->query('filter', 'all');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user