Extend service API for Events and fix panel layout reverting during edit.
Deploy Ladill Meet / deploy (push) Successful in 48s
Deploy Ladill Meet / deploy (push) Successful in 48s
Service rooms support town_hall/webinar types with source lookup, update, and cancel; stage poll no longer overwrites layout while the modal is open. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -13,9 +13,12 @@ use App\Services\Meet\RoomService;
|
|||||||
use App\Services\Meet\SessionService;
|
use App\Services\Meet\SessionService;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
class ServiceRoomController extends Controller
|
class ServiceRoomController extends Controller
|
||||||
{
|
{
|
||||||
|
private const SERVICE_TYPES = ['instant', 'scheduled', 'town_hall', 'webinar'];
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
protected RoomService $rooms,
|
protected RoomService $rooms,
|
||||||
protected SessionService $sessions,
|
protected SessionService $sessions,
|
||||||
@@ -26,60 +29,85 @@ class ServiceRoomController extends Controller
|
|||||||
|
|
||||||
public function store(Request $request): JsonResponse
|
public function store(Request $request): JsonResponse
|
||||||
{
|
{
|
||||||
$validated = $request->validate([
|
$validated = $this->validateRoomPayload($request);
|
||||||
'owner_ref' => ['required', 'string'],
|
|
||||||
'organization_id' => ['nullable', 'integer', 'exists:meet_organizations,id'],
|
|
||||||
'host_user_ref' => ['required', 'string'],
|
|
||||||
'title' => ['required', 'string', 'max:255'],
|
|
||||||
'description' => ['nullable', 'string'],
|
|
||||||
'scheduled_at' => ['nullable', 'date'],
|
|
||||||
'duration_minutes' => ['nullable', 'integer', 'min:15', 'max:480'],
|
|
||||||
'passcode' => ['nullable', 'string', 'max:20'],
|
|
||||||
'settings' => ['nullable', 'array'],
|
|
||||||
'source' => ['required', 'array'],
|
|
||||||
'source.app' => ['required', 'string'],
|
|
||||||
'source.entity_type' => ['nullable', 'string'],
|
|
||||||
'source.entity_id' => ['nullable', 'string'],
|
|
||||||
'invite_emails' => ['nullable', 'array'],
|
|
||||||
'invite_emails.*' => ['email'],
|
|
||||||
]);
|
|
||||||
|
|
||||||
$organization = isset($validated['organization_id'])
|
$organization = $this->resolveOrganization($validated);
|
||||||
? Organization::findOrFail($validated['organization_id'])
|
|
||||||
: Organization::owned($validated['owner_ref'])->firstOrFail();
|
|
||||||
abort_unless($organization->owner_ref === $validated['owner_ref'], 403);
|
abort_unless($organization->owner_ref === $validated['owner_ref'], 403);
|
||||||
|
|
||||||
$host = User::where('public_id', $validated['host_user_ref'])->firstOrFail();
|
$entityId = (string) ($validated['source']['entity_id'] ?? '');
|
||||||
|
if ($entityId !== '') {
|
||||||
|
$existing = Room::query()
|
||||||
|
->forSource((string) $validated['source']['app'], $entityId, $validated['owner_ref'])
|
||||||
|
->first();
|
||||||
|
|
||||||
$room = empty($validated['scheduled_at'])
|
if ($existing) {
|
||||||
? $this->rooms->createInstant($host, $organization, $validated)
|
return $this->respondWithRoom(
|
||||||
: $this->rooms->createScheduled($host, $organization, $validated);
|
$this->persistRoomUpdate($existing, $validated, $organization),
|
||||||
|
);
|
||||||
if (! empty($validated['invite_emails'])) {
|
}
|
||||||
$invites = collect($validated['invite_emails'])->map(fn ($email) => ['email' => $email])->all();
|
|
||||||
$this->invitations->inviteToRoom($room, $invites, $host);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($room->scheduled_at) {
|
return $this->respondWithRoom(
|
||||||
$this->calendar->syncRoomCreated($room, $host);
|
$this->persistRoomCreate($validated, $organization),
|
||||||
|
201,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->webhooks->roomCreated($room);
|
public function lookup(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$validated = $request->validate([
|
||||||
|
'source_app' => ['required', 'string'],
|
||||||
|
'entity_id' => ['required', 'string'],
|
||||||
|
'owner_ref' => ['nullable', 'string'],
|
||||||
|
]);
|
||||||
|
|
||||||
return response()->json([
|
$room = Room::query()
|
||||||
'room' => $room->fresh(),
|
->forSource(
|
||||||
'join_url' => $room->joinUrl(),
|
$validated['source_app'],
|
||||||
'embed_url' => route('meet.join', $room),
|
$validated['entity_id'],
|
||||||
], 201);
|
$validated['owner_ref'] ?? null,
|
||||||
|
)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if (! $room) {
|
||||||
|
return response()->json(['error' => 'Room not found.'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->respondWithRoom($room);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function show(Room $room): JsonResponse
|
public function show(Room $room): JsonResponse
|
||||||
{
|
{
|
||||||
return response()->json([
|
return $this->respondWithRoom($room->load('sessions'));
|
||||||
'room' => $room->load('sessions'),
|
}
|
||||||
'join_url' => $room->joinUrl(),
|
|
||||||
'embed_url' => route('meet.join', $room),
|
public function update(Request $request, Room $room): JsonResponse
|
||||||
|
{
|
||||||
|
$validated = $this->validateRoomPayload($request, partial: true);
|
||||||
|
abort_unless($room->owner_ref === ($validated['owner_ref'] ?? $room->owner_ref), 403);
|
||||||
|
|
||||||
|
$organization = isset($validated['organization_id'])
|
||||||
|
? Organization::findOrFail($validated['organization_id'])
|
||||||
|
: $room->organization;
|
||||||
|
|
||||||
|
return $this->respondWithRoom(
|
||||||
|
$this->persistRoomUpdate($room, $validated, $organization),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function cancel(Request $request, Room $room): JsonResponse
|
||||||
|
{
|
||||||
|
$validated = $request->validate([
|
||||||
|
'owner_ref' => ['required', 'string'],
|
||||||
|
'host_user_ref' => ['nullable', 'string'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
abort_unless($room->owner_ref === $validated['owner_ref'], 403);
|
||||||
|
|
||||||
|
$host = User::where('public_id', $validated['host_user_ref'] ?? $room->host_user_ref)->first();
|
||||||
|
$this->rooms->cancel($room, $validated['owner_ref'], $host);
|
||||||
|
|
||||||
|
return $this->respondWithRoom($room->fresh());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function start(Request $request, Room $room): JsonResponse
|
public function start(Request $request, Room $room): JsonResponse
|
||||||
@@ -95,4 +123,149 @@ class ServiceRoomController extends Controller
|
|||||||
'join_url' => $room->joinUrl(),
|
'join_url' => $room->joinUrl(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @return array<string, mixed> */
|
||||||
|
private function validateRoomPayload(Request $request, bool $partial = false): array
|
||||||
|
{
|
||||||
|
$required = $partial ? 'sometimes' : 'required';
|
||||||
|
|
||||||
|
return $request->validate([
|
||||||
|
'owner_ref' => [$required, 'string'],
|
||||||
|
'organization_id' => ['nullable', 'integer', 'exists:meet_organizations,id'],
|
||||||
|
'host_user_ref' => [$partial ? 'sometimes' : 'required', 'string'],
|
||||||
|
'title' => [$partial ? 'sometimes' : 'required', 'string', 'max:255'],
|
||||||
|
'description' => ['nullable', 'string'],
|
||||||
|
'type' => ['nullable', 'string', Rule::in(self::SERVICE_TYPES)],
|
||||||
|
'scheduled_at' => ['nullable', 'date'],
|
||||||
|
'duration_minutes' => ['nullable', 'integer', 'min:15', 'max:480'],
|
||||||
|
'timezone' => ['nullable', 'timezone'],
|
||||||
|
'passcode' => ['nullable', 'string', 'max:20'],
|
||||||
|
'settings' => ['nullable', 'array'],
|
||||||
|
'source' => [$partial ? 'sometimes' : 'required', 'array'],
|
||||||
|
'source.app' => [$partial ? 'sometimes' : 'required', 'string'],
|
||||||
|
'source.entity_type' => ['nullable', 'string'],
|
||||||
|
'source.entity_id' => ['nullable', 'string'],
|
||||||
|
'source.event_id' => ['nullable', 'string'],
|
||||||
|
'invite_emails' => ['nullable', 'array'],
|
||||||
|
'invite_emails.*' => ['email'],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param array<string, mixed> $validated */
|
||||||
|
private function resolveOrganization(array $validated): Organization
|
||||||
|
{
|
||||||
|
return isset($validated['organization_id'])
|
||||||
|
? Organization::findOrFail($validated['organization_id'])
|
||||||
|
: Organization::owned($validated['owner_ref'])->firstOrFail();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param array<string, mixed> $validated */
|
||||||
|
private function persistRoomCreate(array $validated, Organization $organization): Room
|
||||||
|
{
|
||||||
|
$host = User::where('public_id', $validated['host_user_ref'])->firstOrFail();
|
||||||
|
$type = $this->resolveType($validated);
|
||||||
|
|
||||||
|
$payload = array_merge($validated, [
|
||||||
|
'type' => $type,
|
||||||
|
'status' => 'scheduled',
|
||||||
|
'settings' => $this->mergeSettingsForType($type, $validated['settings'] ?? []),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$room = $type === 'instant' && empty($validated['scheduled_at'])
|
||||||
|
? $this->rooms->createInstant($host, $organization, $payload)
|
||||||
|
: $this->rooms->create($host, $organization, $payload);
|
||||||
|
|
||||||
|
$this->afterRoomPersisted($room, $validated, $host);
|
||||||
|
|
||||||
|
return $room->fresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param array<string, mixed> $validated */
|
||||||
|
private function persistRoomUpdate(Room $room, array $validated, Organization $organization): Room
|
||||||
|
{
|
||||||
|
$hostRef = $validated['host_user_ref'] ?? $room->host_user_ref;
|
||||||
|
$host = User::where('public_id', $hostRef)->firstOrFail();
|
||||||
|
|
||||||
|
$update = array_filter([
|
||||||
|
'title' => $validated['title'] ?? null,
|
||||||
|
'description' => array_key_exists('description', $validated) ? $validated['description'] : null,
|
||||||
|
'scheduled_at' => $validated['scheduled_at'] ?? null,
|
||||||
|
'duration_minutes' => $validated['duration_minutes'] ?? null,
|
||||||
|
'timezone' => $validated['timezone'] ?? null,
|
||||||
|
'passcode' => array_key_exists('passcode', $validated) ? $validated['passcode'] : null,
|
||||||
|
'host_user_ref' => $hostRef,
|
||||||
|
'source' => $validated['source'] ?? null,
|
||||||
|
], fn ($value) => $value !== null);
|
||||||
|
|
||||||
|
if (isset($validated['settings'])) {
|
||||||
|
$type = $this->resolveType(array_merge(['type' => $room->type], $validated));
|
||||||
|
$update['settings'] = $this->mergeSettingsForType($type, $validated['settings']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$room = $this->rooms->update($room, $update, $host);
|
||||||
|
$this->afterRoomPersisted($room, $validated, $host);
|
||||||
|
|
||||||
|
return $room;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param array<string, mixed> $validated */
|
||||||
|
private function afterRoomPersisted(Room $room, array $validated, User $host): void
|
||||||
|
{
|
||||||
|
if (! empty($validated['invite_emails'])) {
|
||||||
|
$invites = collect($validated['invite_emails'])->map(fn ($email) => ['email' => $email])->all();
|
||||||
|
$this->invitations->inviteToRoom($room, $invites, $host);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($room->scheduled_at) {
|
||||||
|
$this->calendar->syncRoomCreated($room, $host);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param array<string, mixed> $validated */
|
||||||
|
private function resolveType(array $validated): string
|
||||||
|
{
|
||||||
|
if (! empty($validated['type'])) {
|
||||||
|
return (string) $validated['type'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return empty($validated['scheduled_at']) ? 'instant' : 'scheduled';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param array<string, mixed> $settings */
|
||||||
|
private function mergeSettingsForType(string $type, array $settings): array
|
||||||
|
{
|
||||||
|
$defaults = match ($type) {
|
||||||
|
'town_hall' => [
|
||||||
|
'waiting_room' => true,
|
||||||
|
'mute_on_join' => true,
|
||||||
|
'green_room' => true,
|
||||||
|
'stage_layout' => 'plenary',
|
||||||
|
'stage_mode' => true,
|
||||||
|
'panel_discussions' => false,
|
||||||
|
'panels' => [],
|
||||||
|
'presenter_refs' => [],
|
||||||
|
],
|
||||||
|
'webinar' => [
|
||||||
|
'waiting_room' => true,
|
||||||
|
'mute_on_join' => true,
|
||||||
|
'webinar_auto_approve' => false,
|
||||||
|
'stage_layout' => 'plenary',
|
||||||
|
'stage_mode' => true,
|
||||||
|
'panel_discussions' => false,
|
||||||
|
'panels' => [],
|
||||||
|
],
|
||||||
|
default => [],
|
||||||
|
};
|
||||||
|
|
||||||
|
return array_merge(config('meet.default_settings', []), $defaults, $settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function respondWithRoom(Room $room, int $status = 200): JsonResponse
|
||||||
|
{
|
||||||
|
return response()->json([
|
||||||
|
'room' => $room->loadMissing('sessions'),
|
||||||
|
'join_url' => $room->joinUrl(),
|
||||||
|
'embed_url' => route('meet.join', $room),
|
||||||
|
], $status);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,6 +76,19 @@ class Room extends Model
|
|||||||
return $this->hasMany(SessionFile::class, 'room_id');
|
return $this->hasMany(SessionFile::class, 'room_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \Illuminate\Database\Eloquent\Builder<self> $query
|
||||||
|
*/
|
||||||
|
public function scopeForSource($query, string $app, string $entityId, ?string $ownerRef = null): void
|
||||||
|
{
|
||||||
|
$query->where('source->app', $app)
|
||||||
|
->where('source->entity_id', $entityId);
|
||||||
|
|
||||||
|
if ($ownerRef !== null) {
|
||||||
|
$query->where('owner_ref', $ownerRef);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function isWebinar(): bool
|
public function isWebinar(): bool
|
||||||
{
|
{
|
||||||
return $this->type === 'webinar';
|
return $this->type === 'webinar';
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ class RoomService
|
|||||||
'owner_ref' => $ownerRef,
|
'owner_ref' => $ownerRef,
|
||||||
'organization_id' => $organization->id,
|
'organization_id' => $organization->id,
|
||||||
'branch_id' => $data['branch_id'] ?? null,
|
'branch_id' => $data['branch_id'] ?? null,
|
||||||
'host_user_ref' => $ownerRef,
|
'host_user_ref' => $data['host_user_ref'] ?? $ownerRef,
|
||||||
'title' => $data['title'],
|
'title' => $data['title'],
|
||||||
'description' => $data['description'] ?? null,
|
'description' => $data['description'] ?? null,
|
||||||
'type' => $data['type'] ?? 'instant',
|
'type' => $data['type'] ?? 'instant',
|
||||||
@@ -70,6 +70,36 @@ class RoomService
|
|||||||
return $room;
|
return $room;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, mixed> $data
|
||||||
|
*/
|
||||||
|
public function update(Room $room, array $data, ?User $host = null): Room
|
||||||
|
{
|
||||||
|
$settings = isset($data['settings'])
|
||||||
|
? array_merge($room->settings ?? [], $data['settings'])
|
||||||
|
: $room->settings;
|
||||||
|
|
||||||
|
$room->update(array_filter([
|
||||||
|
'title' => $data['title'] ?? null,
|
||||||
|
'description' => array_key_exists('description', $data) ? $data['description'] : null,
|
||||||
|
'scheduled_at' => $data['scheduled_at'] ?? null,
|
||||||
|
'duration_minutes' => $data['duration_minutes'] ?? null,
|
||||||
|
'timezone' => $data['timezone'] ?? null,
|
||||||
|
'passcode' => array_key_exists('passcode', $data) ? ($data['passcode'] !== null ? (string) $data['passcode'] : null) : null,
|
||||||
|
'settings' => $settings,
|
||||||
|
'source' => $data['source'] ?? null,
|
||||||
|
'host_user_ref' => $data['host_user_ref'] ?? null,
|
||||||
|
], fn ($value) => $value !== null));
|
||||||
|
|
||||||
|
AuditLogger::record($room->owner_ref, 'room.updated', $room->organization_id, $room->host_user_ref, Room::class, $room->id);
|
||||||
|
|
||||||
|
if ($host && $room->scheduled_at) {
|
||||||
|
app(CalendarService::class)->syncRoomCreated($room->fresh(), $host);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $room->fresh();
|
||||||
|
}
|
||||||
|
|
||||||
public function ensurePersonalRoom(User $user, Organization $organization): Room
|
public function ensurePersonalRoom(User $user, Organization $organization): Room
|
||||||
{
|
{
|
||||||
$ownerRef = $user->ownerRef();
|
$ownerRef = $user->ownerRef();
|
||||||
|
|||||||
@@ -42,6 +42,10 @@ class StageLayoutService
|
|||||||
$layout = (string) $data['stage_layout'];
|
$layout = (string) $data['stage_layout'];
|
||||||
abort_unless(in_array($layout, ['plenary', 'panel'], true), 422);
|
abort_unless(in_array($layout, ['plenary', 'panel'], true), 422);
|
||||||
$settings['stage_layout'] = $layout;
|
$settings['stage_layout'] = $layout;
|
||||||
|
|
||||||
|
if ($layout === 'panel') {
|
||||||
|
$settings['panel_discussions'] = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (array_key_exists('active_panel_id', $data)) {
|
if (array_key_exists('active_panel_id', $data)) {
|
||||||
|
|||||||
@@ -615,18 +615,23 @@ function meetRoom() {
|
|||||||
},
|
},
|
||||||
|
|
||||||
mergeStageConfig(data) {
|
mergeStageConfig(data) {
|
||||||
|
const editingStage = this.panelSetupOpen;
|
||||||
|
|
||||||
|
if (!editingStage) {
|
||||||
if (typeof data.stage_layout === 'string') {
|
if (typeof data.stage_layout === 'string') {
|
||||||
this.stageLayout = data.stage_layout;
|
this.stageLayout = data.stage_layout;
|
||||||
}
|
}
|
||||||
if (typeof data.panel_discussions === 'boolean') {
|
|
||||||
this.panelDiscussions = data.panel_discussions;
|
|
||||||
}
|
|
||||||
if (Array.isArray(data.panels)) {
|
if (Array.isArray(data.panels)) {
|
||||||
this.panels = data.panels;
|
this.panels = data.panels;
|
||||||
}
|
}
|
||||||
if (data.active_panel_id !== undefined) {
|
if (data.active_panel_id !== undefined) {
|
||||||
this.activePanelId = data.active_panel_id || '';
|
this.activePanelId = data.active_panel_id || '';
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof data.panel_discussions === 'boolean') {
|
||||||
|
this.panelDiscussions = data.panel_discussions;
|
||||||
|
}
|
||||||
if (data.spotlight_speaker_ref !== undefined) {
|
if (data.spotlight_speaker_ref !== undefined) {
|
||||||
this.spotlightSpeakerRef = data.spotlight_speaker_ref || '';
|
this.spotlightSpeakerRef = data.spotlight_speaker_ref || '';
|
||||||
}
|
}
|
||||||
@@ -783,6 +788,7 @@ function meetRoom() {
|
|||||||
headers: this.headers(),
|
headers: this.headers(),
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
stage_layout: this.stageLayout,
|
stage_layout: this.stageLayout,
|
||||||
|
panel_discussions: this.panelDiscussions || this.stageLayout === 'panel',
|
||||||
panels: this.panels,
|
panels: this.panels,
|
||||||
active_panel_id: this.activePanelId || null,
|
active_panel_id: this.activePanelId || null,
|
||||||
spotlight_speaker_ref: this.spotlightSpeakerRef || null,
|
spotlight_speaker_ref: this.spotlightSpeakerRef || null,
|
||||||
|
|||||||
@@ -524,7 +524,7 @@
|
|||||||
:class="stageLayout === 'plenary' ? 'bg-violet-600 text-white' : 'bg-slate-800 text-slate-300 hover:bg-slate-700'">
|
:class="stageLayout === 'plenary' ? 'bg-violet-600 text-white' : 'bg-slate-800 text-slate-300 hover:bg-slate-700'">
|
||||||
Plenary
|
Plenary
|
||||||
</button>
|
</button>
|
||||||
<button type="button" @click="stageLayout = 'panel'; applyStageLayout()"
|
<button type="button" @click="panelDiscussions = true; stageLayout = 'panel'; applyStageLayout()"
|
||||||
class="flex-1 rounded-xl px-3 py-2.5 text-sm font-medium transition-colors"
|
class="flex-1 rounded-xl px-3 py-2.5 text-sm font-medium transition-colors"
|
||||||
:class="stageLayout === 'panel' ? 'bg-violet-600 text-white' : 'bg-slate-800 text-slate-300 hover:bg-slate-700'">
|
:class="stageLayout === 'panel' ? 'bg-violet-600 text-white' : 'bg-slate-800 text-slate-300 hover:bg-slate-700'">
|
||||||
Panel discussion
|
Panel discussion
|
||||||
|
|||||||
@@ -10,8 +10,11 @@ Route::get('/health', fn () => response()->json(['status' => 'ok', 'app' => 'mee
|
|||||||
Route::post('/service-events', ServiceEventController::class)->name('api.service-events');
|
Route::post('/service-events', ServiceEventController::class)->name('api.service-events');
|
||||||
|
|
||||||
Route::middleware(['auth.service:meet'])->prefix('service/v1')->group(function () {
|
Route::middleware(['auth.service:meet'])->prefix('service/v1')->group(function () {
|
||||||
|
Route::get('/rooms/by-source', [ServiceRoomController::class, 'lookup'])->name('api.service.rooms.lookup');
|
||||||
Route::post('/rooms', [ServiceRoomController::class, 'store'])->name('api.service.rooms.store');
|
Route::post('/rooms', [ServiceRoomController::class, 'store'])->name('api.service.rooms.store');
|
||||||
Route::get('/rooms/{room}', [ServiceRoomController::class, 'show'])->name('api.service.rooms.show');
|
Route::get('/rooms/{room}', [ServiceRoomController::class, 'show'])->name('api.service.rooms.show');
|
||||||
|
Route::patch('/rooms/{room}', [ServiceRoomController::class, 'update'])->name('api.service.rooms.update');
|
||||||
|
Route::post('/rooms/{room}/cancel', [ServiceRoomController::class, 'cancel'])->name('api.service.rooms.cancel');
|
||||||
Route::post('/rooms/{room}/start', [ServiceRoomController::class, 'start'])->name('api.service.rooms.start');
|
Route::post('/rooms/{room}/start', [ServiceRoomController::class, 'start'])->name('api.service.rooms.start');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -513,6 +513,73 @@ class MeetWebTest extends TestCase
|
|||||||
->assertJsonPath('room.organization_id', $this->organization->id);
|
->assertJsonPath('room.organization_id', $this->organization->id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_service_api_can_create_town_hall_room(): void
|
||||||
|
{
|
||||||
|
\Illuminate\Support\Facades\Http::fake();
|
||||||
|
config(['meet.service_api_keys.events' => 'test-events-key']);
|
||||||
|
|
||||||
|
$this->postJson('/api/service/v1/rooms', [
|
||||||
|
'owner_ref' => $this->user->public_id,
|
||||||
|
'host_user_ref' => $this->user->public_id,
|
||||||
|
'title' => 'Opening keynote',
|
||||||
|
'type' => 'town_hall',
|
||||||
|
'scheduled_at' => now()->addDay()->toIso8601String(),
|
||||||
|
'duration_minutes' => 90,
|
||||||
|
'source' => ['app' => 'events', 'entity_type' => 'virtual_session', 'entity_id' => 'vs-1'],
|
||||||
|
], ['Authorization' => 'Bearer test-events-key'])
|
||||||
|
->assertCreated()
|
||||||
|
->assertJsonPath('room.type', 'town_hall');
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('meet_rooms', ['type' => 'town_hall', 'title' => 'Opening keynote']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_service_api_upserts_room_by_source_entity_id(): void
|
||||||
|
{
|
||||||
|
\Illuminate\Support\Facades\Http::fake();
|
||||||
|
config(['meet.service_api_keys.events' => 'test-events-key']);
|
||||||
|
|
||||||
|
$payload = [
|
||||||
|
'owner_ref' => $this->user->public_id,
|
||||||
|
'host_user_ref' => $this->user->public_id,
|
||||||
|
'title' => 'Session A',
|
||||||
|
'type' => 'webinar',
|
||||||
|
'scheduled_at' => now()->addDay()->toIso8601String(),
|
||||||
|
'source' => ['app' => 'events', 'entity_type' => 'virtual_session', 'entity_id' => 'vs-dup'],
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->postJson('/api/service/v1/rooms', $payload, ['Authorization' => 'Bearer test-events-key'])
|
||||||
|
->assertCreated();
|
||||||
|
|
||||||
|
$payload['title'] = 'Session A (updated)';
|
||||||
|
|
||||||
|
$this->postJson('/api/service/v1/rooms', $payload, ['Authorization' => 'Bearer test-events-key'])
|
||||||
|
->assertOk()
|
||||||
|
->assertJsonPath('room.title', 'Session A (updated)');
|
||||||
|
|
||||||
|
$this->assertSame(1, \App\Models\Room::where('source->entity_id', 'vs-dup')->count());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_service_api_lookup_by_source(): void
|
||||||
|
{
|
||||||
|
\Illuminate\Support\Facades\Http::fake();
|
||||||
|
config(['meet.service_api_keys.events' => 'test-events-key']);
|
||||||
|
|
||||||
|
$this->postJson('/api/service/v1/rooms', [
|
||||||
|
'owner_ref' => $this->user->public_id,
|
||||||
|
'host_user_ref' => $this->user->public_id,
|
||||||
|
'title' => 'Lookup test',
|
||||||
|
'type' => 'town_hall',
|
||||||
|
'scheduled_at' => now()->addDay()->toIso8601String(),
|
||||||
|
'source' => ['app' => 'events', 'entity_type' => 'virtual_session', 'entity_id' => 'vs-lookup'],
|
||||||
|
], ['Authorization' => 'Bearer test-events-key'])->assertCreated();
|
||||||
|
|
||||||
|
$this->getJson('/api/service/v1/rooms/by-source?source_app=events&entity_id=vs-lookup&owner_ref='.$this->user->public_id, [
|
||||||
|
'Authorization' => 'Bearer test-events-key',
|
||||||
|
])
|
||||||
|
->assertOk()
|
||||||
|
->assertJsonPath('room.title', 'Lookup test');
|
||||||
|
}
|
||||||
|
|
||||||
public function test_scheduled_room_syncs_to_ladill_mail_calendar(): void
|
public function test_scheduled_room_syncs_to_ladill_mail_calendar(): void
|
||||||
{
|
{
|
||||||
\Illuminate\Support\Facades\Http::fake([
|
\Illuminate\Support\Facades\Http::fake([
|
||||||
|
|||||||
Reference in New Issue
Block a user