diff --git a/CHANGELOG.md b/CHANGELOG.md index 6091cf7f4..48bcf667d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Improved error handling when a room requires an access code but none was provided ([#3035]) +- GL2- / GL3-Import commands now prepare import of existing recordings ([#2877], ([#3034]) ## [v4.14.2] - 2026-04-10 @@ -756,6 +757,7 @@ You can find the changelog for older versions there [here](https://github.com/TH [#2857]: https://github.com/THM-Health/PILOS/pull/2857 [#2873]: https://github.com/THM-Health/PILOS/issues/2873 [#2874]: https://github.com/THM-Health/PILOS/pull/2874 +[#2877]: https://github.com/THM-Health/PILOS/issues/2877 [#2879]: https://github.com/THM-Health/PILOS/issues/2879 [#2880]: https://github.com/THM-Health/PILOS/pull/2880 [#2901]: https://github.com/THM-Health/PILOS/pull/2901 @@ -773,6 +775,7 @@ You can find the changelog for older versions there [here](https://github.com/TH [#3014]: https://github.com/THM-Health/PILOS/pull/3014 [#3028]: https://github.com/THM-Health/PILOS/issues/3028 [#3029]: https://github.com/THM-Health/PILOS/pull/3029 +[#3034]: https://github.com/THM-Health/PILOS/pull/3034 [#3035]: https://github.com/THM-Health/PILOS/pull/3035 [#3039]: https://github.com/THM-Health/PILOS/issues/3039 [#3040]: https://github.com/THM-Health/PILOS/pull/3040 diff --git a/app/Console/Commands/ImportGreenlight2Command.php b/app/Console/Commands/ImportGreenlight2Command.php index 7b431ef1b..a0bf01ea1 100644 --- a/app/Console/Commands/ImportGreenlight2Command.php +++ b/app/Console/Commands/ImportGreenlight2Command.php @@ -6,6 +6,7 @@ use App\Enums\RoomLobby; use App\Enums\RoomUserRole; +use App\Models\Meeting; use App\Models\Role; use App\Models\Room; use App\Models\RoomFile; @@ -126,7 +127,7 @@ public function handle() $requireAuth = DB::connection('greenlight')->table('features')->where('name', 'Room Authentication')->first('value')?->value == true; $users = DB::connection('greenlight')->table('users')->where('deleted', false)->get(['id', 'provider', 'username', 'social_uid', 'email', 'name', 'password_digest']); - $rooms = DB::connection('greenlight')->table('rooms')->where('deleted', false)->get(['id', 'uid', 'user_id', 'name', 'room_settings', 'access_code']); + $rooms = DB::connection('greenlight')->table('rooms')->where('deleted', false)->get(['id', 'uid', 'bbb_id', 'user_id', 'name', 'room_settings', 'access_code']); $sharedAccesses = DB::connection('greenlight')->table('shared_accesses')->get(['room_id', 'user_id']); $socialProviders = DB::connection('greenlight')->table('users')->select('provider')->whereNotIn('provider', ['greenlight', 'ldap'])->distinct()->get(); @@ -387,6 +388,12 @@ protected function importRooms(Collection $rooms, int $roomType, array $userMap, $dbRoom->roomType()->associate($roomType); $dbRoom->save(); + // Create meeting + $dbMeeting = new Meeting; + $dbMeeting->id = $room->bbb_id; + $dbMeeting->room()->associate($dbRoom); + $dbMeeting->save(); + // increase counter and add room to room map (key = greenlight db id, value = new db id) $created++; $roomMap[$room->id] = $room->uid; diff --git a/app/Console/Commands/ImportGreenlight3Command.php b/app/Console/Commands/ImportGreenlight3Command.php index 4054b54c5..d36e1fb58 100644 --- a/app/Console/Commands/ImportGreenlight3Command.php +++ b/app/Console/Commands/ImportGreenlight3Command.php @@ -6,6 +6,7 @@ use App\Enums\RoomLobby; use App\Enums\RoomUserRole; +use App\Models\Meeting; use App\Models\Role; use App\Models\Room; use App\Models\RoomFile; @@ -108,7 +109,7 @@ public function handle() ]]); $users = DB::connection('greenlight')->table('users')->where('provider', 'greenlight')->get(['id', 'name', 'email', 'external_id', 'password_digest']); - $rooms = DB::connection('greenlight')->table('rooms')->get(['id', 'friendly_id', 'user_id', 'name']); + $rooms = DB::connection('greenlight')->table('rooms')->get(['id', 'friendly_id', 'meeting_id', 'user_id', 'name']); $sharedAccesses = DB::connection('greenlight')->table('shared_accesses')->get(['room_id', 'user_id']); // Start transaction to rollback if import fails or user cancels @@ -301,6 +302,12 @@ protected function importRooms(Collection $rooms, int $roomType, array $userMap, $dbRoom->roomType()->associate($roomType); $dbRoom->save(); + // Create meeting + $dbMeeting = new Meeting; + $dbMeeting->id = $room->meeting_id; + $dbMeeting->room()->associate($dbRoom); + $dbMeeting->save(); + // increase counter and add room to room map (key = greenlight db id, value = new db id) $created++; $roomMap[$room->id] = $room->friendly_id; diff --git a/app/Models/RecordingFormat.php b/app/Models/RecordingFormat.php index 28e121089..0effdad46 100644 --- a/app/Models/RecordingFormat.php +++ b/app/Models/RecordingFormat.php @@ -6,6 +6,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Support\Facades\Date; use SimpleXMLElement; @@ -44,20 +45,11 @@ public static function createFromRecordingXML(SimpleXMLElement $xml) $meetingId = (string) $xml->meta->meetingId; $meetingName = (string) $xml->meta->meetingName; - // Find the meeting with the given id - $meeting = Meeting::where('id', $meetingId)->first(); - - // If the meeting exists, get the room from the meeting - // Otherwise, find the room with the given id - if ($meeting != null) { - $room = $meeting->room; - } else { - // Fallback to greenlight behaviour (using the persistent room id as meeting id) - $room = Room::where('id', $meetingId)->first(); - } - - // If the room does not exist, we can't associate the recording format with a room - if ($room == null) { + try { + // Find the meeting with the given id + $meeting = Meeting::findOrFail($meetingId); + } catch (ModelNotFoundException) { + // If the meeting does not exist, we can't associate the recording format with anything return null; } @@ -69,7 +61,7 @@ public static function createFromRecordingXML(SimpleXMLElement $xml) $recording->description = $meetingName; $recording->start = $start; $recording->end = $end; - $recording->room()->associate($room); + $recording->room()->associate($meeting->room); $recording->meeting()->associate($meeting); $recording->save(); } diff --git a/docs/docs/administration/08-advanced/06-migrate-greenlight.md b/docs/docs/administration/08-advanced/06-migrate-greenlight.md index dd8b3a52e..229fdf049 100644 --- a/docs/docs/administration/08-advanced/06-migrate-greenlight.md +++ b/docs/docs/administration/08-advanced/06-migrate-greenlight.md @@ -3,7 +3,7 @@ title: Migrate from Greenlight description: Step by step guide to migrate from Greenlight to PILOS --- -PILOS provides an easy to use command to import all Greenlight users (incl. ldap), rooms and shared accesses. +PILOS provides an easy to use command to import all Greenlight users (incl. ldap), rooms (incl. default presentation) and shared accesses. ## Preparing migration from other host @@ -31,9 +31,9 @@ Successfully imported presentation files will be copied to a different location. ## Running migration command -The command will output the process of the import and informs about failed user, room and shared access import. +The command will output the process of the import and informs about failed user, room, room presentation and shared access import. -**Note** If a room with the same room id already exists in PILOS it will NOT be imported and the shared accesses are ignored. +**Note** If a room with the same room id already exists in PILOS it will NOT be imported and its default presentation and shared accesses are ignored. ### Greenlight 2 @@ -135,6 +135,25 @@ docker compose exec app pilos-cli import:greenlight-v3 \ 12345678 ``` +## Importing recordings + +You can also import recordings for existing rooms. To make this possible the import command creates a meeting with the BBB meeting ID for every imported room. +This meeting does not have a start- or end timestamp, so it not visible in the frontend. Associated recordings _will_ be listed, however. + +To import existing recordings, you have to + +1. find the meeting ID (column `bbb_id` (GL2) or `meeting_id` (GL3) in the `rooms` table) +2. find all recordings with this meeting ID (XPath: `/recording/meta/meetingId` in metadata.xml) +3. pack matching recordings into tar files and +4. move or copy those tar files to PILOS' `recordings-spool` directory. + +Existing recordings prepared like this will be imported just like new ones would. + +**Note** You _may_ have to temporarily increase RAM allocated to horizon if it is limited. + +**Note** YMMV, depending on your BBB loadbalancer. If, for example, you run b3scale, you need to extract the "simple" Greenlight meeting ID from the +more complex b3scale meeting ID. + ## Adjust nginx ### PILOS is running on the same host diff --git a/tests/Backend/Unit/Console/ImportGreenlight2Test.php b/tests/Backend/Unit/Console/ImportGreenlight2Test.php index b8610fa0d..696077a00 100644 --- a/tests/Backend/Unit/Console/ImportGreenlight2Test.php +++ b/tests/Backend/Unit/Console/ImportGreenlight2Test.php @@ -6,6 +6,7 @@ use App\Enums\RoomLobby; use App\Enums\RoomUserRole; +use App\Models\Meeting; use App\Models\Role; use App\Models\Room; use App\Models\RoomType; @@ -99,7 +100,7 @@ private function fakeDatabase(bool $roomAuth, Collection $users, Collection $roo ->with('deleted', false) ->andReturn(Mockery::mock('Illuminate\Database\Query\Builder', function ($mock) use ($rooms) { $mock->shouldReceive('get') - ->with(['id', 'uid', 'user_id', 'name', 'room_settings', 'access_code']) + ->with(['id', 'uid', 'bbb_id', 'user_id', 'name', 'room_settings', 'access_code']) ->andReturn($rooms); })); })); @@ -238,17 +239,17 @@ protected function test_helper_full(array $cmdOptions, callable $expectHook, boo // Create fake rooms $rooms = []; - $rooms[] = new Greenlight2Room(1, $users[0]->id, 'Test Room 1', 'abc-def-xyz-123'); - $rooms[] = new Greenlight2Room(2, $users[1]->id, 'Test Room 2', 'abc-def-xyz-234'); - $rooms[] = new Greenlight2Room(3, $users[2]->id, 'Test Room 3', 'abc-def-xyz-345'); - $rooms[] = new Greenlight2Room(4, $users[3]->id, 'Test Room 4', 'abc-def-xyz-456'); - $rooms[] = new Greenlight2Room(5, $users[4]->id, 'Test Room 5', 'abc-def-xyz-567'); - $rooms[] = new Greenlight2Room(6, $users[5]->id, 'Test Room 6', 'abc-def-xyz-678'); - - $rooms[] = new Greenlight2Room(7, $users[0]->id, 'Test Room 7', 'hij-klm-xyz-123', '012345', ['muteOnStart' => false, 'requireModeratorApproval' => true, 'anyoneCanStart' => false, 'joinModerator' => true]); - $rooms[] = new Greenlight2Room(8, $users[0]->id, 'Test Room 8', 'hij-klm-xyz-234', null, ['muteOnStart' => true, 'requireModeratorApproval' => false, 'anyoneCanStart' => true, 'joinModerator' => false]); - $rooms[] = new Greenlight2Room(9, 99, 'Test Room 9', 'hij-klm-xyz-456', '012345'); - $rooms[] = new Greenlight2Room(10, $users[0]->id, 'Test Room 10', $existingRoom->id); + $rooms[] = new Greenlight2Room(1, 'abc-def-xyz-123', 'thue5aivaiyohreetu1zaipe4iez7eengoopoohi', 'Test Room 1', $users[0]->id); + $rooms[] = new Greenlight2Room(2, 'abc-def-xyz-234', 'aef9eiquoo8oph9oon4oovit8oid9ree7caigahw', 'Test Room 2', $users[1]->id); + $rooms[] = new Greenlight2Room(3, 'abc-def-xyz-345', 'iekoongaicahhaivah0xaud3tha3iem8eathoaxu', 'Test Room 3', $users[2]->id); + $rooms[] = new Greenlight2Room(4, 'abc-def-xyz-456', 'oeph7ohhoochoy7ruoshae9uephie0tha8aup3oo', 'Test Room 4', $users[3]->id); + $rooms[] = new Greenlight2Room(5, 'abc-def-xyz-567', 'eecae0kugoo3aes2aiquaif8aeraiy1aiva2ohje', 'Test Room 5', $users[4]->id); + $rooms[] = new Greenlight2Room(6, 'abc-def-xyz-678', 'eizaipaikohheizohvaehiech5ach3el9haech5g', 'Test Room 6', $users[5]->id); + + $rooms[] = new Greenlight2Room(7, 'hij-klm-xyz-123', 'aebaoj6phak4eaghoizeiwaecudei6hishochua7', 'Test Room 7', $users[0]->id, '012345', ['muteOnStart' => false, 'requireModeratorApproval' => true, 'anyoneCanStart' => false, 'joinModerator' => true]); + $rooms[] = new Greenlight2Room(8, 'hij-klm-xyz-234', 'aicha2vahw1zei7aecoo1ainoph1ietei3nei4la', 'Test Room 8', $users[0]->id, null, ['muteOnStart' => true, 'requireModeratorApproval' => false, 'anyoneCanStart' => true, 'joinModerator' => false]); + $rooms[] = new Greenlight2Room(9, 'hij-klm-xyz-456', 'quohseseey2aheicoc3eedaedei4kif8zo4xaiki', 'Test Room 9', 99, '012345'); + $rooms[] = new Greenlight2Room(10, $existingRoom->id, 'aima0eiv6uyaif6ien8ahchoothohkeiphaegh0u', 'Test Room 10', $users[0]->id); // Create fake presentations $presentations = []; @@ -286,6 +287,7 @@ protected function test_helper_full(array $cmdOptions, callable $expectHook, boo // check amount of rooms and users $this->assertCount(9, Room::all()); + $this->assertCount(8, Meeting::all()); $this->assertCount(2, User::where('authenticator', 'local')->get()); $this->assertCount(2, User::where('authenticator', 'ldap')->get()); $this->assertCount(1, User::where('authenticator', 'shibboleth')->get()); @@ -297,6 +299,21 @@ protected function test_helper_full(array $cmdOptions, callable $expectHook, boo Room::all()->pluck('id')->toArray() ); + // check if all meetings are created + $this->assertEqualsCanonicalizing( + [ + 'thue5aivaiyohreetu1zaipe4iez7eengoopoohi', + 'aef9eiquoo8oph9oon4oovit8oid9ree7caigahw', + 'iekoongaicahhaivah0xaud3tha3iem8eathoaxu', + 'oeph7ohhoochoy7ruoshae9uephie0tha8aup3oo', + 'eecae0kugoo3aes2aiquaif8aeraiy1aiva2ohje', + 'eizaipaikohheizohvaehiech5ach3el9haech5g', + 'aebaoj6phak4eaghoizeiwaecudei6hishochua7', + 'aicha2vahw1zei7aecoo1ainoph1ietei3nei4la', + ], + Meeting::all()->pluck('id')->toArray() + ); + // check if allow guest setting is correct foreach (Room::where('id', '!=', $existingRoom->id)->get() as $room) { $this->assertEquals(! $roomAuth, $room->allow_guests); @@ -398,11 +415,7 @@ public function test_interactive() ->expectsOutput('Importing rooms') ->expectsOutput('8 created, 1 skipped (already existed)') ->expectsOutput('Room import failed for the following 1 rooms, because no room owner was found:') - ->expectsOutput('+-------------+-----------------+-------------+') - ->expectsOutput('| Name | ID | Access code |') - ->expectsOutput('+-------------+-----------------+-------------+') - ->expectsOutput('| Test Room 9 | hij-klm-xyz-456 | 012345 |') - ->expectsOutput('+-------------+-----------------+-------------+') + ->expectsTable(['Name', 'ID', 'Access code'], [['Test Room 9', 'hij-klm-xyz-456', '012345']]) ->expectsOutput('Importing presentations for rooms') ->expectsOutput('2 created, 1 skipped (file not found)') ->expectsOutput('Importing shared room accesses') @@ -430,11 +443,7 @@ public function test_non_interactive() ->expectsOutput('Importing rooms') ->expectsOutput('8 created, 1 skipped (already existed)') ->expectsOutput('Room import failed for the following 1 rooms, because no room owner was found:') - ->expectsOutput('+-------------+-----------------+-------------+') - ->expectsOutput('| Name | ID | Access code |') - ->expectsOutput('+-------------+-----------------+-------------+') - ->expectsOutput('| Test Room 9 | hij-klm-xyz-456 | 012345 |') - ->expectsOutput('+-------------+-----------------+-------------+') + ->expectsTable(['Name', 'ID', 'Access code'], [['Test Room 9', 'hij-klm-xyz-456', '012345']]) ->expectsOutput('Importing presentations for rooms') ->expectsOutput('2 created, 1 skipped (file not found)') ->expectsOutput('Importing shared room accesses') @@ -478,11 +487,7 @@ public function test_rollback() ->expectsOutput('Importing rooms') ->expectsOutput('8 created, 1 skipped (already existed)') ->expectsOutput('Room import failed for the following 1 rooms, because no room owner was found:') - ->expectsOutput('+-------------+-----------------+-------------+') - ->expectsOutput('| Name | ID | Access code |') - ->expectsOutput('+-------------+-----------------+-------------+') - ->expectsOutput('| Test Room 9 | hij-klm-xyz-456 | 012345 |') - ->expectsOutput('+-------------+-----------------+-------------+') + ->expectsTable(['Name', 'ID', 'Access code'], [['Test Room 9', 'hij-klm-xyz-456', '012345']]) ->expectsOutput('Importing presentations for rooms') ->expectsOutput('2 created, 1 skipped (file not found)') ->expectsOutput('Importing shared room accesses') diff --git a/tests/Backend/Unit/Console/ImportGreenlight3Test.php b/tests/Backend/Unit/Console/ImportGreenlight3Test.php index 5f57a0bd5..20f4b710d 100644 --- a/tests/Backend/Unit/Console/ImportGreenlight3Test.php +++ b/tests/Backend/Unit/Console/ImportGreenlight3Test.php @@ -6,6 +6,7 @@ use App\Enums\RoomLobby; use App\Enums\RoomUserRole; +use App\Models\Meeting; use App\Models\Role; use App\Models\Room; use App\Models\RoomType; @@ -80,7 +81,7 @@ private function fakeDatabase(Collection $users, Collection $rooms, Collection $ ->once() ->andReturn(Mockery::mock('Illuminate\Database\Query\Builder', function ($mock) use ($rooms) { $mock->shouldReceive('get') - ->with(['id', 'friendly_id', 'user_id', 'name']) + ->with(['id', 'friendly_id', 'meeting_id', 'user_id', 'name']) ->andReturn($rooms); })); @@ -237,15 +238,14 @@ protected function test_helper_full(array $cmdOptions, callable $expectHook, ?st // Create fake rooms $rooms = []; - $rooms[] = new Greenlight3Room('1', 'abc-def-xyz-123', $users[0]->id, 'Test Room 1'); - $rooms[] = new Greenlight3Room('2', 'abc-def-xyz-234', $users[1]->id, 'Test Room 2'); - $rooms[] = new Greenlight3Room('3', 'abc-def-xyz-345', $users[2]->id, 'Test Room 3'); - $rooms[] = new Greenlight3Room('4', 'abc-def-xyz-456', $users[3]->id, 'Test Room 4'); - - $rooms[] = new Greenlight3Room('5', 'hij-klm-xyz-123', $users[0]->id, 'Test Room 5'); - $rooms[] = new Greenlight3Room('6', 'hij-klm-xyz-234', $users[0]->id, 'Test Room 6'); - $rooms[] = new Greenlight3Room('7', 'hij-klm-xyz-456', '99', 'Test Room 9', true); - $rooms[] = new Greenlight3Room('8', $existingRoom->id, $users[0]->id, 'Test Room 10'); + $rooms[] = new Greenlight3Room('1', 'abc-def-xyz-123', 'kah3caebohzosei4ohd5vadai5xeech4iephieha', 'Test Room 1', $users[0]->id); + $rooms[] = new Greenlight3Room('2', 'abc-def-xyz-234', 'shuuchuk3ahchu2xai3hienae1eghohngueleih9', 'Test Room 2', $users[1]->id); + $rooms[] = new Greenlight3Room('3', 'abc-def-xyz-345', 'aepoh2etaira5ahjootoh2naedahno6fieghaibi', 'Test Room 3', $users[2]->id); + $rooms[] = new Greenlight3Room('4', 'abc-def-xyz-456', 'jee8sha6koh9iechik4thohjahv2biedua8shiep', 'Test Room 4', $users[3]->id); + $rooms[] = new Greenlight3Room('5', 'hij-klm-xyz-123', 'jiel3oe0gohvohmei2aew0ooghahwiejaileeghu', 'Test Room 5', $users[0]->id); + $rooms[] = new Greenlight3Room('6', 'hij-klm-xyz-234', 'ies7oroizuulaiqu3cheeshoogahh1mae1aew0ee', 'Test Room 6', $users[0]->id); + $rooms[] = new Greenlight3Room('7', 'hij-klm-xyz-456', 'eu6ahs7eephahhain6thae6thodu7xoophooghei', 'Test Room 9', '99'); + $rooms[] = new Greenlight3Room('8', $existingRoom->id, 'gaezohvohsh6lougho8coongaebiech0wu6jukia', 'Test Room 10', $users[0]->id); // Create fake presentations $presentations = []; @@ -305,6 +305,19 @@ protected function test_helper_full(array $cmdOptions, callable $expectHook, ?st Room::all()->pluck('id')->toArray() ); + // Check if all meetings are created + $this->assertEqualsCanonicalizing( + [ + 'kah3caebohzosei4ohd5vadai5xeech4iephieha', + 'shuuchuk3ahchu2xai3hienae1eghohngueleih9', + 'aepoh2etaira5ahjootoh2naedahno6fieghaibi', + 'jee8sha6koh9iechik4thohjahv2biedua8shiep', + 'jiel3oe0gohvohmei2aew0ooghahwiejaileeghu', + 'ies7oroizuulaiqu3cheeshoogahh1mae1aew0ee', + ], + Meeting::all()->pluck('id')->toArray() + ); + // Check access code $this->assertNull(Room::find('abc-def-xyz-123')->access_code); $this->assertNull(Room::find('abc-def-xyz-234')->access_code); diff --git a/tests/Backend/Unit/Console/helper/Greenlight2Room.php b/tests/Backend/Unit/Console/helper/Greenlight2Room.php index 64788f508..c9b842ac3 100644 --- a/tests/Backend/Unit/Console/helper/Greenlight2Room.php +++ b/tests/Backend/Unit/Console/helper/Greenlight2Room.php @@ -14,6 +14,8 @@ class Greenlight2Room public $uid; + public $bbb_id; + public $room_settings; public $access_code; @@ -23,14 +25,15 @@ class Greenlight2Room /** * Greenlight2Room constructor. */ - public function __construct(int $id, int $user_id, string $name, string $uid, ?string $access_code = null, array $room_settings = [], bool $deleted = false) + public function __construct(int $id, string $uid, string $bbb_id, string $name, int $user_id, ?string $access_code = null, array $room_settings = [], bool $deleted = false) { $this->id = $id; - $this->user_id = $user_id; - $this->name = $name; $this->uid = $uid; - $this->room_settings = json_encode($room_settings); + $this->bbb_id = $bbb_id; + $this->name = $name; + $this->user_id = $user_id; $this->access_code = $access_code; + $this->room_settings = json_encode($room_settings); $this->deleted = $deleted; } } diff --git a/tests/Backend/Unit/Console/helper/Greenlight3Room.php b/tests/Backend/Unit/Console/helper/Greenlight3Room.php index 0f1b67c6b..b4c1362d5 100644 --- a/tests/Backend/Unit/Console/helper/Greenlight3Room.php +++ b/tests/Backend/Unit/Console/helper/Greenlight3Room.php @@ -10,6 +10,8 @@ class Greenlight3Room public $friendly_id; + public $meeting_id; + public $user_id; public $name; @@ -17,11 +19,12 @@ class Greenlight3Room /** * Greenlight3Room constructor. */ - public function __construct(string $id, string $friendly_id, string $user_id, string $name) + public function __construct(string $id, string $friendly_id, string $meeting_id, string $name, string $user_id) { $this->id = $id; $this->friendly_id = $friendly_id; - $this->user_id = $user_id; + $this->meeting_id = $meeting_id; $this->name = $name; + $this->user_id = $user_id; } }