Improve conferences, guest entry, Afia, and cross-app scheduling.
Deploy Ladill Meet / deploy (push) Successful in 50s
Deploy Ladill Meet / deploy (push) Successful in 50s
Route guests through silent SSO to the Meet product page, fix Afia context query, add conference green-room UX and copy, and extend service API for Care/CRM/Invoice calendar integration. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -17,6 +17,11 @@ class MailCalendarProvider
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->createEventForMailbox($mailbox, $room);
|
||||
}
|
||||
|
||||
public function createEventForMailbox(string $mailbox, Room $room): ?string
|
||||
{
|
||||
$response = Http::withToken((string) config('meet.calendar.mail.api_key'))
|
||||
->acceptJson()
|
||||
->timeout(10)
|
||||
@@ -32,13 +37,18 @@ class MailCalendarProvider
|
||||
}
|
||||
|
||||
public function updateEvent(CalendarConnection $connection, Room $room, User $host): bool
|
||||
{
|
||||
$mailbox = $connection->calendar_id ?? $host->email;
|
||||
|
||||
return $mailbox ? $this->updateEventForMailbox($mailbox, $room) : false;
|
||||
}
|
||||
|
||||
public function updateEventForMailbox(string $mailbox, Room $room): bool
|
||||
{
|
||||
if (! $room->calendar_event_id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$mailbox = $connection->calendar_id ?? $host->email;
|
||||
|
||||
return Http::withToken((string) config('meet.calendar.mail.api_key'))
|
||||
->acceptJson()
|
||||
->timeout(10)
|
||||
@@ -47,6 +57,11 @@ class MailCalendarProvider
|
||||
}
|
||||
|
||||
public function deleteEvent(CalendarConnection $connection, Room $room): bool
|
||||
{
|
||||
return $this->deleteEventForMailbox($connection->calendar_id, $room);
|
||||
}
|
||||
|
||||
public function deleteEventForMailbox(?string $mailbox, Room $room): bool
|
||||
{
|
||||
if (! $room->calendar_event_id) {
|
||||
return false;
|
||||
@@ -55,10 +70,10 @@ class MailCalendarProvider
|
||||
return Http::withToken((string) config('meet.calendar.mail.api_key'))
|
||||
->acceptJson()
|
||||
->timeout(10)
|
||||
->delete($this->baseUrl().'/calendar/events/'.$room->calendar_event_id, [
|
||||
'mailbox_email' => $connection->calendar_id,
|
||||
->delete($this->baseUrl().'/calendar/events/'.$room->calendar_event_id, array_filter([
|
||||
'mailbox_email' => $mailbox ? strtolower(trim($mailbox)) : null,
|
||||
'external_ref' => $room->uuid,
|
||||
])
|
||||
]))
|
||||
->successful();
|
||||
}
|
||||
|
||||
|
||||
@@ -20,35 +20,54 @@ class CalendarService
|
||||
|
||||
public function syncRoomCreated(Room $room, User $host): void
|
||||
{
|
||||
$connection = $this->connectionFor($host);
|
||||
if (! $connection) {
|
||||
if (! $room->scheduled_at) {
|
||||
return;
|
||||
}
|
||||
|
||||
$eventId = $this->provider($connection)->createEvent($connection, $room, $host);
|
||||
if ($eventId) {
|
||||
$room->update(['calendar_event_id' => $eventId]);
|
||||
if ($host->email) {
|
||||
$eventId = $this->mail->createEventForMailbox($host->email, $room);
|
||||
if ($eventId) {
|
||||
$room->update(['calendar_event_id' => $eventId]);
|
||||
}
|
||||
}
|
||||
|
||||
$connection = $this->connectionFor($host);
|
||||
if ($connection && in_array($connection->provider, ['google', 'microsoft'], true)) {
|
||||
$this->provider($connection)->createEvent($connection, $room, $host);
|
||||
}
|
||||
}
|
||||
|
||||
public function syncRoomUpdated(Room $room, User $host): void
|
||||
{
|
||||
$connection = $this->connectionFor($host);
|
||||
if (! $connection || ! $room->calendar_event_id) {
|
||||
if (! $room->scheduled_at) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->provider($connection)->updateEvent($connection, $room, $host);
|
||||
if ($host->email) {
|
||||
if ($room->calendar_event_id) {
|
||||
$this->mail->updateEventForMailbox($host->email, $room);
|
||||
} else {
|
||||
$this->syncRoomCreated($room, $host);
|
||||
}
|
||||
}
|
||||
|
||||
$connection = $this->connectionFor($host);
|
||||
if ($connection && $room->calendar_event_id && in_array($connection->provider, ['google', 'microsoft'], true)) {
|
||||
$this->provider($connection)->updateEvent($connection, $room, $host);
|
||||
}
|
||||
}
|
||||
|
||||
public function syncRoomCancelled(Room $room, User $host): void
|
||||
{
|
||||
if ($host->email && $room->calendar_event_id) {
|
||||
$this->mail->deleteEventForMailbox($host->email, $room);
|
||||
}
|
||||
|
||||
$connection = $this->connectionFor($host);
|
||||
if (! $connection || ! $room->calendar_event_id) {
|
||||
return;
|
||||
if ($connection && $room->calendar_event_id && in_array($connection->provider, ['google', 'microsoft'], true)) {
|
||||
$this->provider($connection)->deleteEvent($connection, $room);
|
||||
}
|
||||
|
||||
$this->provider($connection)->deleteEvent($connection, $room);
|
||||
$room->update(['calendar_event_id' => null]);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Meet;
|
||||
|
||||
use App\Models\Invitation;
|
||||
use App\Models\Participant;
|
||||
use App\Models\Room;
|
||||
use App\Models\Session;
|
||||
use App\Models\User;
|
||||
|
||||
class ConferenceService
|
||||
{
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
public function presenterRefs(Room $room): array
|
||||
{
|
||||
return array_values(array_unique(array_filter(
|
||||
(array) $room->setting('presenter_refs', []),
|
||||
)));
|
||||
}
|
||||
|
||||
public function resolveJoinRole(Room $room, ?User $user, ?string $email, string $fallbackRole = 'guest'): string
|
||||
{
|
||||
if ($user && $user->ownerRef() === $room->host_user_ref) {
|
||||
return 'host';
|
||||
}
|
||||
|
||||
$presenterRefs = $this->presenterRefs($room);
|
||||
|
||||
if ($user && in_array($user->ownerRef(), $presenterRefs, true)) {
|
||||
return 'panelist';
|
||||
}
|
||||
|
||||
if ($email && $this->invitationRole($room, $email) === 'panelist') {
|
||||
return 'panelist';
|
||||
}
|
||||
|
||||
if ($room->isTownHall()) {
|
||||
return 'attendee';
|
||||
}
|
||||
|
||||
return $fallbackRole;
|
||||
}
|
||||
|
||||
public function canJoinSession(Room $room, Session $session, string $role): bool
|
||||
{
|
||||
if ($session->session_mode !== 'green_room') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return in_array($role, ['host', 'co_host', 'panelist'], true);
|
||||
}
|
||||
|
||||
public function isPresenter(Session $session, Participant $participant): bool
|
||||
{
|
||||
if (in_array($participant->role, ['host', 'co_host', 'panelist'], true)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$refs = (array) $session->presenter_refs;
|
||||
|
||||
return $participant->user_ref && in_array($participant->user_ref, $refs, true);
|
||||
}
|
||||
|
||||
public function canPublish(Room $room, Participant $participant): bool
|
||||
{
|
||||
return in_array($participant->role, ['host', 'co_host', 'panelist'], true);
|
||||
}
|
||||
|
||||
public function registerPresenter(Session $session, ?User $user, string $role): void
|
||||
{
|
||||
if (! $user || ! in_array($role, ['host', 'co_host', 'panelist'], true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
app(TownHallService::class)->addPresenter($session, $user->ownerRef());
|
||||
}
|
||||
|
||||
protected function invitationRole(Room $room, string $email): ?string
|
||||
{
|
||||
return Invitation::query()
|
||||
->where('room_id', $room->id)
|
||||
->where('email', strtolower(trim($email)))
|
||||
->value('role');
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,7 @@ class ParticipantPresenter
|
||||
{
|
||||
return [
|
||||
'uuid' => $participant->uuid,
|
||||
'user_ref' => $participant->user_ref,
|
||||
'display_name' => $participant->display_name,
|
||||
'role' => $participant->role,
|
||||
'status' => $participant->status,
|
||||
|
||||
@@ -13,20 +13,21 @@ class SecurityPolicyService
|
||||
{
|
||||
$org = $room->organization;
|
||||
$policy = $this->mergedPolicy($org, $room);
|
||||
$kind = $room->isConference() ? 'conference' : 'meeting';
|
||||
|
||||
if ($policy['org_only'] && ! $user) {
|
||||
return [false, 'This meeting requires a Ladill account.'];
|
||||
return [false, "This {$kind} requires a Ladill account."];
|
||||
}
|
||||
|
||||
if ($policy['authenticated_only'] && ! $user) {
|
||||
return [false, 'Please sign in to join this meeting.'];
|
||||
return [false, "Please sign in to join this {$kind}."];
|
||||
}
|
||||
|
||||
if (! empty($policy['allowed_domains']) && $email) {
|
||||
$domain = Str($email)->after('@')->lower()->toString();
|
||||
$allowed = collect($policy['allowed_domains'])->map(fn ($d) => strtolower(trim($d)))->filter();
|
||||
if ($allowed->isNotEmpty() && ! $allowed->contains($domain)) {
|
||||
return [false, 'Your email domain is not allowed to join this meeting.'];
|
||||
return [false, "Your email domain is not allowed to join this {$kind}."];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +36,7 @@ class SecurityPolicyService
|
||||
}
|
||||
|
||||
if ($room->activeSession()?->is_locked && ! $this->isHost($room, $user)) {
|
||||
return [false, 'This meeting is locked.'];
|
||||
return [false, "This {$kind} is locked."];
|
||||
}
|
||||
|
||||
if ($policy['recording_host_only'] && ! $this->isHost($room, $user)) {
|
||||
|
||||
Reference in New Issue
Block a user