Initial commit

This commit is contained in:
Ronald Huynen
2026-03-23 21:37:59 +01:00
commit 2547717edb
2193 changed files with 972171 additions and 0 deletions

View File

@@ -0,0 +1,56 @@
<?php
namespace App\Services;
use App\Models\Call;
use App\Models\Transaction;
use Illuminate\Support\Facades\Cache;
class CallCreditService
{
/**
* Check whether a profile has sufficient credits to publish calls.
* Uses the same budget logic as Pay.php: balance - limit_min > 0.
*/
public static function profileHasCredits(string $profileType, int $profileId): bool
{
$profile = $profileType::find($profileId);
if (!$profile) {
return false;
}
$accounts = $profile->accounts()->notRemoved()->get();
if ($accounts->isEmpty()) {
return false;
}
foreach ($accounts as $account) {
$balance = Transaction::where('from_account_id', $account->id)
->orWhere('to_account_id', $account->id)
->selectRaw('SUM(CASE WHEN to_account_id = ? THEN amount ELSE -amount END) as balance', [$account->id])
->value('balance') ?? 0;
if (($balance - $account->limit_min) > 0) {
return true;
}
}
return false;
}
/**
* Pause all active calls for a profile (set till = now, remove from index).
*/
public static function pauseAllActiveCalls(string $profileType, int $profileId): void
{
$activeCalls = Call::where('callable_type', $profileType)
->where('callable_id', $profileId)
->where(fn ($q) => $q->whereNull('till')->orWhere('till', '>', now()))
->get();
foreach ($activeCalls as $call) {
$call->update(['till' => now()]);
$call->unsearchable();
}
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace App\Services;
use Enflow\SocialShare\SocialShare;
class CustomSocialShare extends SocialShare
{
public function __call(string $service, array $parameters = []): self
{
$this->services->push(new CustomSocialShareService($this, $service, $parameters));
return $this;
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace App\Services;
use Enflow\SocialShare\ConfiguredSocialShareService;
class CustomSocialShareService extends ConfiguredSocialShareService
{
public function icon(): ?string
{
// First check for custom icon in resources
$customIconPath = resource_path('views/vendor/social-share/icons/' . $this->name . '.svg');
if (file_exists($customIconPath)) {
return file_get_contents($customIconPath);
}
// Fallback to vendor package icon
if (file_exists($iconPath = __DIR__ . '/../../vendor/enflow/laravel-social-share/resources/icons/' . $this->name . '.svg')) {
return file_get_contents($iconPath);
}
return null;
}
}

View File

@@ -0,0 +1,220 @@
<?php
// 1. Enhanced Activity Log Service
namespace App\Services;
use Carbon\Carbon;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;
use Spatie\Activitylog\Models\Activity;
class PresenceService
{
public const PRESENCE_ACTIVITY = 'presence_update';
public const ONLINE_THRESHOLD_MINUTES = 5;
public function updatePresence($user = null, $guard = null)
{
$user = $user ?? Auth::user();
$guard = $guard ?? Auth::getDefaultDriver();
if (!$user) {
return;
}
// Log activity using Spatie
activity(self::PRESENCE_ACTIVITY)
->performedOn($user)
->causedBy($user)
->withProperties([
'guard' => $guard,
'ip_address' => request()->ip(),
'user_agent' => request()->userAgent(),
'timestamp' => now(),
'status' => 'online',
])
->event('updated')
->log('User online');
// Cache for quick access
$cacheKey = "presence_{$guard}_{$user->id}";
Cache::put($cacheKey, [
'user_id' => $user->id,
'user_type' => get_class($user),
'guard' => $guard,
'name' => $user->name,
'avatar' => $user->avatar ?? null,
'last_seen' => now(),
'status' => 'online',
], now()->addMinutes(self::ONLINE_THRESHOLD_MINUTES + 2));
// Broadcast presence update
broadcast(new \App\Events\UserPresenceUpdated($user, $guard, 'online'));
// Clear cache for online users to force refresh
Cache::forget("online_users_{$guard}_" . self::ONLINE_THRESHOLD_MINUTES);
// Cleanup old presence records for this user
$this->cleanupUserPresenceRecords($user, $guard);
}
public function setUserOffline($user, $guard = 'web')
{
if (!$user) {
return;
}
// Log offline activity
activity(self::PRESENCE_ACTIVITY)
->performedOn($user)
->causedBy($user)
->withProperties([
'guard' => $guard,
'status' => 'offline',
'timestamp' => now(),
])
->log('User offline');
// Remove from cache
$cacheKey = "presence_{$guard}_{$user->id}";
Cache::forget($cacheKey);
// Broadcast offline status
broadcast(new \App\Events\UserPresenceUpdated($user, $guard, 'offline'));
// Clear online users cache
Cache::forget("online_users_{$guard}_" . self::ONLINE_THRESHOLD_MINUTES);
// Don't cleanup on offline - preserve presence history
}
public function getOnlineUsers($guard = 'web', $minutes = null)
{
$minutes = $minutes ?? self::ONLINE_THRESHOLD_MINUTES;
$cacheKey = "online_users_{$guard}_{$minutes}";
return Cache::remember($cacheKey, 30, function () use ($guard, $minutes) {
try {
// Simple and reliable database query
$activities = Activity::where('log_name', self::PRESENCE_ACTIVITY)
->where('properties->guard', $guard)
->where('created_at', '>=', now()->subMinutes($minutes))
->with('subject')
->latest()
->get();
// Group by user and get the latest activity for each
$userActivities = $activities->groupBy('subject_id')->map(function ($userActivities) {
return $userActivities->first(); // Get the most recent activity
});
// Filter out offline users and map to user data
$onlineUsers = $userActivities->map(function ($activity) {
$user = $activity->subject;
if (!$user) {
return null;
}
// Skip if latest activity is "offline"
$properties = is_string($activity->properties) ? json_decode($activity->properties, true) : $activity->properties;
if (isset($properties['status']) && $properties['status'] === 'offline') {
return null;
}
return [
'id' => $user->id,
'name' => $user->name,
'avatar' => $user->avatar ?? null,
'guard' => $properties['guard'] ?? $guard,
'last_seen' => $activity->created_at,
'user_type' => $activity->subject_type,
'status' => $properties['status'] ?? 'online',
];
})->filter()->values();
return $onlineUsers;
} catch (\Exception $e) {
// Return empty collection on error
\Log::error('PresenceService getOnlineUsers error: ' . $e->getMessage());
return collect([]);
}
});
}
public function isUserOnline($user, $guard = 'web', $minutes = null)
{
$minutes = $minutes ?? self::ONLINE_THRESHOLD_MINUTES;
$cacheKey = "presence_{$guard}_{$user->id}";
// Check cache first, but only for online status
$cachedData = Cache::get($cacheKey);
if ($cachedData && isset($cachedData['status']) && $cachedData['status'] === 'online') {
return true;
}
// Get the most recent presence activity for this user
$latestActivity = Activity::where('log_name', self::PRESENCE_ACTIVITY)
->where('subject_id', $user->id)
->where('subject_type', get_class($user))
->where('properties->guard', $guard)
->where('created_at', '>=', now()->subMinutes($minutes))
->latest()
->first();
if (!$latestActivity) {
return false;
}
// Check if the latest activity is "offline"
$properties = is_string($latestActivity->properties)
? json_decode($latestActivity->properties, true)
: $latestActivity->properties;
return isset($properties['status']) && $properties['status'] === 'online';
}
public function getUserLastSeen($user, $guard = 'web')
{
$activity = Activity::where('log_name', self::PRESENCE_ACTIVITY)
->where('subject_id', $user->id)
->where('subject_type', get_class($user))
->where('properties->guard', $guard)
->latest()
->first();
return $activity ? $activity->created_at : null;
}
public function getPresenceStats($guard = 'web')
{
$onlineUsers = $this->getOnlineUsers($guard);
return [
'online_count' => $onlineUsers->count(),
'online_users' => $onlineUsers,
'guard' => $guard,
'updated_at' => now(),
];
}
/**
* Clean up old presence records for a specific user, keeping only the latest, as defined in config
*/
protected function cleanupUserPresenceRecords($user, $guard = 'web')
{
$keepCount = timebank_config('presence_settings.keep_last_presence_updates', timebank_config('presence_settings.keep_last_presence_updates'));
$userActivities = Activity::where('log_name', self::PRESENCE_ACTIVITY)
->where('causer_id', $user->id)
->where('causer_type', get_class($user))
->where('properties->guard', $guard)
->orderBy('created_at', 'desc')
->get();
if ($userActivities->count() > $keepCount) {
$toDelete = $userActivities->skip($keepCount)->pluck('id');
Activity::whereIn('id', $toDelete)->delete();
}
}
}