Add sports-match type, device tracking, profile visits, and share refactor
- New SportsMatch model/controller and sports UI components/modal - Move share-modal to a reusable x-share-modal/x-share-button component - Add VideoSharedWithUser notification and share-to-members flow - Device/user-agent tracking on views, downloads, share accesses - ProfileVisit model + migration; subscription source tracking - Email thumbnail support; remove stale TODO files
This commit is contained in:
1 parent
6aae6f86b6
commit
73527f3781
61 files changed
+4014
-636
No files matched your search
@@ -21,6 +21,33 @@ class UserController extends Controller
|
||||
$this->middleware('auth')->except(['channel']);
|
||||
}
|
||||
|
||||
// Typeahead search for members (used by the "Share to member" picker)
|
||||
public function searchUsers(Request $request)
|
||||
{
|
||||
$q = trim((string) $request->query('q', ''));
|
||||
if (mb_strlen($q) < 1) {
|
||||
return response()->json(['users' => []]);
|
||||
}
|
||||
|
||||
$users = User::where('id', '!=', Auth::id())
|
||||
->where(function ($w) use ($q) {
|
||||
$w->where('name', 'like', "%{$q}%")
|
||||
->orWhere('username', 'like', "%{$q}%");
|
||||
})
|
||||
->orderBy('name')
|
||||
->limit(8)
|
||||
->get(['id', 'name', 'username', 'avatar']);
|
||||
|
||||
return response()->json([
|
||||
'users' => $users->map(fn ($u) => [
|
||||
'id' => $u->id,
|
||||
'name' => $u->name,
|
||||
'channel' => $u->channel,
|
||||
'avatar' => $u->avatar_url,
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
// Profile page - personal overview for the authenticated user
|
||||
public function profile()
|
||||
{
|
||||
@@ -378,7 +405,49 @@ class UserController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
public function toggleSubscribe(User $user)
|
||||
public function recordProfileVisit(Request $request, User $user)
|
||||
{
|
||||
// Don't record self-visits or repeated visits from the same person in the last 30 minutes
|
||||
$visitorId = Auth::id();
|
||||
$deviceId = $request->cookie('_did');
|
||||
|
||||
if ($visitorId && $visitorId === $user->id) {
|
||||
return response()->json(['ok' => true, 'skipped' => 'self']);
|
||||
}
|
||||
|
||||
$sourceVideoId = $request->integer('source_video_id') ?: null;
|
||||
if ($sourceVideoId && ! Video::whereKey($sourceVideoId)->exists()) {
|
||||
$sourceVideoId = null;
|
||||
}
|
||||
|
||||
$dedup = \App\Models\ProfileVisit::where('profile_user_id', $user->id)
|
||||
->where('created_at', '>=', now()->subMinutes(30))
|
||||
->when($visitorId, fn ($q) => $q->where('visitor_user_id', $visitorId))
|
||||
->when(! $visitorId && $deviceId, fn ($q) => $q->whereNull('visitor_user_id')->where('device_id', $deviceId))
|
||||
->when($sourceVideoId, fn ($q) => $q->where('source_video_id', $sourceVideoId))
|
||||
->exists();
|
||||
|
||||
if ($dedup) {
|
||||
return response()->json(['ok' => true, 'skipped' => 'dedup']);
|
||||
}
|
||||
|
||||
$ip = $request->header('CF-Connecting-IP') ?? $request->header('X-Real-IP') ?? $request->ip();
|
||||
$geo = \App\Services\GeoIpService::lookup($ip);
|
||||
|
||||
\App\Models\ProfileVisit::create([
|
||||
'profile_user_id' => $user->id,
|
||||
'visitor_user_id' => $visitorId,
|
||||
'device_id' => $deviceId,
|
||||
'source_video_id' => $sourceVideoId,
|
||||
'ip_address' => $ip,
|
||||
'country' => $geo['country'] ?? null,
|
||||
'created_at' => now(),
|
||||
]);
|
||||
|
||||
return response()->json(['ok' => true]);
|
||||
}
|
||||
|
||||
public function toggleSubscribe(Request $request, User $user)
|
||||
{
|
||||
$me = Auth::user();
|
||||
|
||||
@@ -390,7 +459,11 @@ class UserController extends Controller
|
||||
$me->subscriptions()->detach($user->id);
|
||||
$subscribed = false;
|
||||
} else {
|
||||
$me->subscriptions()->attach($user->id);
|
||||
$sourceVideoId = $request->integer('source_video_id') ?: null;
|
||||
if ($sourceVideoId && ! \App\Models\Video::whereKey($sourceVideoId)->exists()) {
|
||||
$sourceVideoId = null;
|
||||
}
|
||||
$me->subscriptions()->attach($user->id, ['source_video_id' => $sourceVideoId]);
|
||||
$subscribed = true;
|
||||
try { $user->notify(new NewSubscriberNotification($me)); } catch (\Throwable) {}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user