/laravel-meeting

Handle online meeting with Laravel

Primary LanguagePHPMIT LicenseMIT

Laravel Meeting

Latest Version on Packagist License Total Downloads

Official Documentation

Introduction

This package can handle online meetings with Eloquent models. It provides a simple, fluent API to work with and by default uses Zoom as provider.

use Nncodes\Meeting\Models\Meeting;
use Nncodes\Meeting\Models\MeetingRoom;
use App\Models\Event;
use App\Models\Teacher;

$meeting = Meeting::schedule()
  	->withTopic('English class: verb to be')
  	->startingAt(now()->addMinutes(30))
  	->during(40) //in Minutes
  	->scheduledBy(Event::find(1))
  	->presentedBy(Teacher::find(1))
  	->hostedBy(MeetingRoom::find(1))
  	->save();

Requirements

This package requires PHP 7.3+ and Laravel 6+.

This package uses nncodes/meta-attributes to attach meta attributes to the models.

Installation & setup

You can install the package via composer:

composer require nncodes/laravel-meeting

The package will automatically register itself.

You can use the meeting:install command to publish the migrations and use --config if you also want to publish the config file.

php artisan meeting:install --config

Or you can publish by the traditional way:

php artisan vendor:publish --provider="Nncodes\Meeting\MeetingServiceProvider" --tag="migrations"
php artisan vendor:publish --provider="Nncodes\MetaAttributes\MetaAttributesServiceProvider" --tag="migrations"

After the migration has been published you can create the tables by running the migrations:

php artisan migrate

You can publish the config file with:

php artisan vendor:publish --provider="Nncodes\Meeting\MeetingServiceProvider" --tag="config"

This is the contents of the published config file:

/**
 * Default Meeting Provider
 * 
 * Here you can specify which meeting provider the package should use by 
 * default. Of course you may use many providers at once using the package.
 */
'default' => env('MEETING_PROVIDER', 'zoom'),

/**
 * Meeting Providers
 * 
 * Here are each of the meetings provider setup for the package.
 */

'providers' => [

    'zoom' => [

         /**
         * Provider class
         **/
        'type' => \Nncodes\Meeting\Providers\Zoom\ZoomProvider::class,

        /**
         * JWT Zoom Token 
         * @see https://marketplace.zoom.us/docs/guides/auth/jwt
         **/
        'jwt_token' => env('ZOOM_TOKEN'),

        /**
         * Zoom Group ID
         * 
         * @see https://marketplace.zoom.us/docs/api-reference/zoom-api/groups/group
         **/
        'group_id' => env('ZOOM_GROUP'),

         /**
         * Share Rooms
         * 
         * Delegate to the package the responsability of handling the allocations of rooms.
         **/
        'share_rooms' => true,

         /**
         * Meeting resource seetings
         * 
         * @see https://marketplace.zoom.us/docs/api-reference/zoom-api/meetings/meeting
         **/
        'meeting_settings' => [
            "host_video" => false,
            "participant_video" => false,
            "join_before_host" => false,
            "jbh_time" => 0,
            "mute_upon_entry" => true,
            "approval_type" => 0,
            "registration_type" => 1,
            "close_registration" => true,
            "waiting_room" => true,
            "registrants_confirmation_email" => false,
            "registrants_email_notification" => false,
            "meeting_authentication" => false
        ]
    ]
],

/**
 * Allow concurrent Meetings
 */
'allow_concurrent_meetings' => [
    'host' => false,
    'participant' => false,
    'presenter' => false,
    'scheduler' => true,
]

Preparing your models

Scheduler

Responsible for scheduling the meeting, the model must implement the following interface and trait:

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Nncodes\Meeting\Concerns\SchedulesMeetings;
use Nncodes\Meeting\Contracts\Scheduler;

class Event extends Model implements Scheduler
{
    use SchedulesMeetings;
}

Presenter

Responsible for present the meeting, the model must implement the following interface and trait:

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Nncodes\Meeting\Concerns\PresentsMeetings;
use Nncodes\Meeting\Contracts\Presenter;

class Teacher extends User implements Presenter
{
    use PresentsMeetings;
}

Host

Responsible for hosting the meeting, the model must implement the following interface and trait:

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Nncodes\Meeting\Concerns\HostsMeetings;
use Nncodes\Meeting\Contracts\Host;

class Room extends Model implements Host
{
    use HostsMeetings;
}

Participant

Allowed to join a meeting, the model must implement the following interface, trait and the getEmailAddress, getFirstName and getLastName methods:

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Nncodes\Meeting\Concerns\JoinsMeetings;
use Nncodes\Meeting\Contracts\Participant;

class Student extends User implements Participant
{
    use JoinsMeetings;

    /**
     * Email Address of the participant
     *
     * @return string
     */
    public function getParticipantEmailAddress(): string
    {
        return $this->email;
    }

    /**
     * First name of the participant
     *
     * @return string
     */
    public function getParticipantFirstName(): string
    {
        return $this->first_name;
    }

    /**
     * Last name of the participant
     *
     * @return string
     */
    public function getParticipantLastName(): string
    {
        return $this->last_name;
    }
}

Scheduling a meeting

To schedule a meeting you need to use the methods below to properly fill the meeting data:

use Nncodes\Meeting\Models\Meeting;
use App\Models\Event;
use App\Models\Teacher;
use App\Models\Room;

$event = Event::find(1);
$teacher = Teacher::find(1);
$room = Room::find(1);

$meeting = Meeting::schedule()
  	->withTopic('English class: verb to be')
  	->startingAt(now()->addMinutes(30))
  	->during(40) //minutes
  	->scheduledBy($event)
  	->presentedBy($teacher)
  	->hostedBy($room)
  	->save();

Or you can also schedule by the scheduler model:

use Nncodes\Meeting\Models\Meeting;
use App\Models\Event;
use App\Models\Teacher;
use App\Models\Room;

$event->scheduleMeeting()
  	->withTopic('English class: verb to be')
  	->startingAt(now()->addMinutes(30))
  	->during(40) //minutes
  	->presentedBy($teacher)
  	->hostedBy($room)
  	->save()

Of course if needed, you can update the meeting:

use Nncodes\Meeting\Models\Meeting;
use App\Models\Event;
use App\Models\Teacher;
use App\Models\Room;

$meeting = Meeting::find(1);

$meeting->updateTopic('English class: Introducing Yourself')
    ->updateDuration(60)
    ->updateStartTime(now())
    ->updateScheduler(Event::find(1))
    ->updatePresenter(Teacher::find(5))
    ->updateHost(Room::find(1))
    ->save();

Then you can add a participant:

use Nncodes\Meeting\Models\Meeting;
use App\Models\Student;

$meeting = Meeting::find(1);
$student = Student::find(1);

//By the meeting model
$meeting->addParticipant($student);

//Or by the participant model 
$student->bookMeeting($meeting);

To provide the access to the presenter use:

use Nncodes\Meeting\Models\Meeting;

Meeting::find(1)->getPresenterAccess();

And for the participant use:

use Nncodes\Meeting\Models\Meeting;
use App\Models\Student;

$student = Student::find(1);

Meeting::find(1)>getParticipantAccess($student);

More: handling a scheduled meeting.

Retrieving meetings

You can just call from the meeting model:

Scoping meetings by Nncodes\Meeting\Models\Meeting.

$query = Meeting::query();

or call meetings() from any actor:

Scoping meetings from scheduler model, e.g. App\Models\Event with id:1.

$query = Event::find(1)->meetings();

Scoping meetings from presenter model, e.g. App\Models\Teacher with id:1.

$query = Teacher::find(1)->meetings();

Scoping meetings from host model, e.g. App\Models\Room with id:1.

$query = Room::find(1)->meetings();

Scoping meetings from participant model, e.g. App\Models\Student with id:1.

$query = Student::find(1)->meetings();

Eloquent scopes

General scopes

scoping by uuid, e.g b33cac3a-c8da-4b33-a296-30a6acff5af6.

$query->byUuid('b33cac3a-c8da-4b33-a296-30a6acff5af6');

scoping by id, e.g 1.

$query->byId(1);

scoping by provider, e.g. zoom.

$query->provider('zoom');

Scopes for start_time, started_at and ended_at

scoping by start time from, e.g. 15 days ago.

$query->startsFrom(Carbon::now()->sub('15 days'));

scoping by start time until, e.g. 15 days from now.

$query->startsUntil(Carbon::now()->add('15 days'));

Or scoping by start time within a period, e.g. from 15 days ago and 15 days from now.

$query->startsBetween(
    Carbon::now()->sub('15 days'),
    Carbon::now()->add('15 days')
);

scoping by status live, the started but not ended meetings.

$query->live();

scoping by status past, the started and ended meetings.

$query->past();

scoping by status scheduled, the not started meetings.

$query->scheduled();

scoping by scheduled status and where start_time is past. Queries the late to start meetings.

$query->late();

scoping by live status and where started_at + duration is past. Queries the meetings that had exceeded the scheduled duration.

$query->exceeded();

scoping by scheduled status ordering by start_time asc. Queries the next neetings

$query->next();

scoping by last status ordering by ended_at desc queries the last meetings

$query->last();

Scopes for actors

scoping by scheduler, e.g. App\Models\Event with id:1.

$query->scheduler(Event::find(1));

scoping by host, e.g. App\Models\Room with id:1.

$query->host(Room::find(1));

scoping by participant, e.g. App\Models\Student with id:1.

$query->participant(Student::find(1));

scoping by presenter, e.g. App\Models\Teacher with id:1.

$query->presenter(Teacher::find(1));

Finally to retrieve the data you can call any eloquent retriever method, e.g. count, first, get, paginate and etc.

Handling a scheduled meeting

Meeting

When using zoom provider, you can set share_rooms to true, then you don't need to inform a host when scheduling a meeting. The package handles the allocation of rooms.

In this case you can schedule using:

use Nncodes\Meeting\Models\Meeting;
use App\Models\Event;
use App\Models\Teacher;

$meeting = Meeting::schedule()
  	->withTopic('English class: verb to be')
  	->startingAt(now()->addMinutes(30))
  	->during(40) //minutes
  	->scheduledBy(Event::find(1))
  	->presentedBy(Teacher::find(1))
  	->save();

If no rooms is available the expcetion \Nncodes\Meeting\Exceptions\NoZoomRoomAvailable is thrown.

use Nncodes\Meeting\Models\Meeting;

Starting a meeting.

Meeting::find(1)->start();

Ending a meeting.

Meeting::find(1)->end();

Canceling a meeting.

Meeting::find(1)->cancel();

Participants

Add a participant

Adding a participant by Nncodes\Meeting\Models\Meeting

$student = Student::find(1);
Meeting::find(1)->addParticipant($student);

Adding a participant by participant model App\Models\Student

$meeting = Meeting::find(1);
Student::find(1)->bookMeeting($meeting);

Cancel a participation

Canceling a participation by Nncodes\Meeting\Models\Meeting

$student = Student::find(1);
Meeting::find(1)->cancelParticipation($student);

Adding a participant by participant model App\Models\Student

$meeting = Meeting::find(1);
Student::find(1)->cancelMeetingParticipation($meeting);

Join meeting

Joining by Nncodes\Meeting\Models\Meeting

$student = Student::find(1);
Meeting::find(1)->joinParticipant($student);

Joining by participant model App\Models\Student

$meeting = Meeting::find(1);
Student::find(1)->joinMeeting($meeting);
Leave meeting

Leaving by Nncodes\Meeting\Models\Meeting

$student = Student::find(1);
Meeting::find(1)->leaveParticipant($student);

Leaving by participant model App\Models\Student

$meeting = Meeting::find(1);
Student::find(1)->leaveMeeting($meeting);
Getting participants

Getting a participant

$student = Student::find(1);
$participant = Meeting::find(1)->participant($student);

Checking if a meeting has a participant:

$student = Student::find(1);
$bool = Meeting::find(1)->hasParticipant($student);

Getting a list of participants using the morphMany relationship:

//Must inform the participant model type
$participants = Meeting::find(1)->participants(App\Models\Student::class)->get();

Or using the participantsPivot relation.

//Doesn't need to inform participant model type, it gets all types. 
$participants = Meeting::find(1)->participantsPivot;

Getting the first participant ordering by created_at desc, it allows to use a meeting as queue mode service.

$participant = Meeting::find(1)->getNextParticipant();

Hosts

Scoping and verification methods

Given the code:

use Nncodes\Meeting\Models\MeetingRoom;

$startTime = Carbon::now()->addMinutes(30);
$duration = 40;
$endTime = (clone $startTime)->addMinutes($duration);

Scoping an available host:

MeetingRoom::availableBetween($startTime, $endTime);

Scoping a busy host:

MeetingRoom::busyBetween($startTime, $endTime);

Scoping busy and available hosts except for a meeting

use Nncodes\Meeting\Models\Meeting;

$except = Meeting::find(1);

MeetingRoom::availableBetween($startTime, $endTime, $except);
MeetingRoom::busyBetween($startTime, $endTime, $except);

Then you can call any eloquent retriever method, e.g. count, first, get, paginate and etc.

You can also check if a room instance is busy or available:

use Nncodes\Meeting\Models\MeetingRoom;

MeetingRoom::find(1)->isAvailableBetween($startTime, $endTime);
MeetingRoom::find(1)->isBusyBetween($startTime, $endTime);

As the scope methods, you can also specify meeting to exclude from the query:

use Nncodes\Meeting\Models\MeetingRoom;
use Nncodes\Meeting\Models\Meeting;

$except = Meeting::find(1);

MeetingRoom::find(1)->isAvailableBetween($startTime, $endTime, $except);
MeetingRoom::find(1)->isBusyBetween($startTime, $endTime, $except);

Changelog

Please see CHANGELOG for more information on what has changed recently.

Contributing

Please see CONTRIBUTING for details.

Security Vulnerabilities

Please review our security policy on how to report security vulnerabilities.

Credits

License

The MIT License (MIT). Please see License File for more information.