Deploy Ladill Meet / deploy (push) Failing after 7s
Phases 0–18: core meetings, webinar, breakouts, team chat, live streaming, town hall, billing, and Ladill Mail calendar wiring. Co-authored-by: Cursor <cursoragent@cursor.com>
214 lines
6.6 KiB
PHP
214 lines
6.6 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Meet;
|
|
|
|
use App\Models\Participant;
|
|
use App\Models\Room;
|
|
use App\Models\Session;
|
|
use App\Models\User;
|
|
use App\Services\Meet\Media\MediaProviderInterface;
|
|
use Illuminate\Support\Str;
|
|
|
|
class SessionService
|
|
{
|
|
public function __construct(
|
|
protected MediaProviderInterface $media,
|
|
protected RoomService $rooms,
|
|
) {}
|
|
|
|
public function start(Room $room, User $host): Session
|
|
{
|
|
$existing = $room->activeSession();
|
|
if ($existing) {
|
|
return $existing;
|
|
}
|
|
|
|
app(LicenseService::class)->assertCanStartSession($room->organization, $room->branch_id);
|
|
|
|
$session = Session::create([
|
|
'owner_ref' => $room->owner_ref,
|
|
'room_id' => $room->id,
|
|
'media_room_name' => $this->rooms->mediaRoomName($room),
|
|
'status' => 'live',
|
|
'started_at' => now(),
|
|
]);
|
|
|
|
$room->update(['status' => 'live']);
|
|
|
|
$this->media->createRoom($session->media_room_name);
|
|
|
|
$this->joinParticipant($session, $host, 'host');
|
|
|
|
if ($room->setting('auto_record', false)) {
|
|
app(RecordingService::class)->start($session, $host);
|
|
}
|
|
|
|
app(MeetNotificationService::class)->meetingStarted($session, $host);
|
|
|
|
app(\App\Services\Integrations\WebhookDispatcher::class)->meetingStarted($session);
|
|
|
|
AuditLogger::record($room->owner_ref, 'session.started', $room->organization_id, $host->ownerRef(), Session::class, $session->id);
|
|
|
|
return $session;
|
|
}
|
|
|
|
public function end(Session $session, string $actorRef): Session
|
|
{
|
|
$session->update([
|
|
'status' => 'ended',
|
|
'ended_at' => now(),
|
|
]);
|
|
|
|
$session->room->update(['status' => 'ended']);
|
|
|
|
$session->participants()
|
|
->where('status', 'joined')
|
|
->update(['status' => 'left', 'left_at' => now()]);
|
|
|
|
foreach ($session->recordings()->where('status', 'recording')->get() as $recording) {
|
|
app(RecordingService::class)->stop($recording, $actorRef);
|
|
}
|
|
|
|
if ($session->room->isRecurring()) {
|
|
app(RecurringMeetingService::class)->spawnNextOccurrences($session->room, 1);
|
|
}
|
|
|
|
AuditLogger::record($session->owner_ref, 'session.ended', $session->room->organization_id, $actorRef, Session::class, $session->id);
|
|
|
|
app(\App\Services\Integrations\WebhookDispatcher::class)->meetingEnded($session);
|
|
|
|
app(UsageMeteringService::class)->recordSessionUsage($session);
|
|
|
|
return $session;
|
|
}
|
|
|
|
public function joinParticipant(
|
|
Session $session,
|
|
?User $user,
|
|
string $role,
|
|
?string $displayName = null,
|
|
?string $email = null,
|
|
): Participant {
|
|
$userRef = $user?->ownerRef();
|
|
|
|
$existing = $session->participants()
|
|
->when($userRef, fn ($q) => $q->where('user_ref', $userRef))
|
|
->when(! $userRef && $email, fn ($q) => $q->where('email', $email))
|
|
->whereIn('status', ['invited', 'waiting', 'joined'])
|
|
->first();
|
|
|
|
if ($existing) {
|
|
$existing->update([
|
|
'status' => 'joined',
|
|
'joined_at' => now(),
|
|
'left_at' => null,
|
|
'display_name' => $displayName ?? $existing->display_name,
|
|
'ip_address' => request()?->ip(),
|
|
]);
|
|
|
|
return $existing;
|
|
}
|
|
|
|
$participant = Participant::create([
|
|
'owner_ref' => $session->owner_ref,
|
|
'session_id' => $session->id,
|
|
'user_ref' => $userRef,
|
|
'display_name' => $displayName ?? $user?->name ?? 'Guest',
|
|
'email' => $email,
|
|
'role' => $role,
|
|
'status' => 'joined',
|
|
'joined_at' => now(),
|
|
'ip_address' => request()?->ip(),
|
|
]);
|
|
|
|
$this->updatePeakParticipants($session);
|
|
|
|
AuditLogger::record(
|
|
$session->owner_ref,
|
|
'participant.joined',
|
|
$session->room->organization_id,
|
|
$userRef,
|
|
Participant::class,
|
|
$participant->id,
|
|
);
|
|
|
|
return $participant;
|
|
}
|
|
|
|
public function leaveParticipant(Participant $participant): Participant
|
|
{
|
|
$participant->update([
|
|
'status' => 'left',
|
|
'left_at' => now(),
|
|
'hand_raised' => false,
|
|
]);
|
|
|
|
AuditLogger::record(
|
|
$participant->owner_ref,
|
|
'participant.left',
|
|
$participant->session->room->organization_id,
|
|
$participant->user_ref,
|
|
Participant::class,
|
|
$participant->id,
|
|
);
|
|
|
|
return $participant;
|
|
}
|
|
|
|
public function generateMediaToken(Participant $participant): string
|
|
{
|
|
$session = $participant->session;
|
|
$room = $session->room;
|
|
$isHost = $participant->isHost();
|
|
$breakoutService = app(BreakoutService::class);
|
|
$webinarService = app(WebinarService::class);
|
|
$mediaRoom = $breakoutService->mediaRoomForParticipant($participant);
|
|
$canPublish = $webinarService->canPublish($room, $participant) && $participant->canPublishMedia();
|
|
|
|
return $this->media->generateToken(
|
|
$mediaRoom,
|
|
$participant->uuid,
|
|
$participant->display_name,
|
|
[
|
|
'can_publish' => $canPublish,
|
|
'can_subscribe' => true,
|
|
'can_publish_data' => true,
|
|
'room_admin' => $isHost,
|
|
],
|
|
);
|
|
}
|
|
|
|
public function getOrStartSession(Room $room, User $host): Session
|
|
{
|
|
if ($room->activeSession()) {
|
|
return $room->activeSession();
|
|
}
|
|
|
|
return $this->start($room, $host);
|
|
}
|
|
|
|
public function lock(Session $session, string $actorRef): Session
|
|
{
|
|
$session->update(['is_locked' => true, 'locked_at' => now()]);
|
|
AuditLogger::record($session->owner_ref, 'meeting.locked', $session->room->organization_id, $actorRef, Session::class, $session->id);
|
|
|
|
return $session;
|
|
}
|
|
|
|
public function unlock(Session $session, string $actorRef): Session
|
|
{
|
|
$session->update(['is_locked' => false, 'locked_at' => null]);
|
|
AuditLogger::record($session->owner_ref, 'meeting.unlocked', $session->room->organization_id, $actorRef, Session::class, $session->id);
|
|
|
|
return $session;
|
|
}
|
|
|
|
protected function updatePeakParticipants(Session $session): void
|
|
{
|
|
$count = $session->participants()->where('status', 'joined')->count();
|
|
if ($count > $session->peak_participants) {
|
|
$session->update(['peak_participants' => $count]);
|
|
}
|
|
}
|
|
}
|