Initial Ladill Meet release.
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>
This commit is contained in:
isaacclad
2026-06-30 23:35:29 +00:00
co-authored by Cursor
commit 965fb992e9
319 changed files with 30322 additions and 0 deletions
+39
View File
@@ -0,0 +1,39 @@
<?php
namespace App\Models;
use App\Models\Concerns\BelongsToOwner;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Str;
class ActionItem extends Model
{
use BelongsToOwner;
protected $table = 'meet_action_items';
protected $fillable = [
'uuid', 'owner_ref', 'ai_summary_id', 'assignee_ref', 'assignee_name',
'description', 'due_date', 'status',
];
protected function casts(): array
{
return ['due_date' => 'date'];
}
protected static function booted(): void
{
static::creating(function (ActionItem $item) {
if (empty($item->uuid)) {
$item->uuid = (string) Str::uuid();
}
});
}
public function aiSummary(): BelongsTo
{
return $this->belongsTo(AiSummary::class, 'ai_summary_id');
}
}
+55
View File
@@ -0,0 +1,55 @@
<?php
namespace App\Models;
use App\Models\Concerns\BelongsToOwner;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Str;
class AiSummary extends Model
{
use BelongsToOwner;
protected $table = 'meet_ai_summaries';
protected $fillable = [
'uuid', 'owner_ref', 'session_id', 'transcript_id', 'status',
'summary', 'decisions',
];
protected function casts(): array
{
return ['decisions' => 'array'];
}
protected static function booted(): void
{
static::creating(function (AiSummary $summary) {
if (empty($summary->uuid)) {
$summary->uuid = (string) Str::uuid();
}
});
}
public function getRouteKeyName(): string
{
return 'uuid';
}
public function session(): BelongsTo
{
return $this->belongsTo(Session::class, 'session_id');
}
public function transcript(): BelongsTo
{
return $this->belongsTo(Transcript::class, 'transcript_id');
}
public function actionItems(): HasMany
{
return $this->hasMany(ActionItem::class, 'ai_summary_id');
}
}
+56
View File
@@ -0,0 +1,56 @@
<?php
namespace App\Models;
use App\Models\Concerns\BelongsToOwner;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class AuditLog extends Model
{
use BelongsToOwner;
protected $table = 'meet_audit_logs';
public $timestamps = false;
protected $fillable = [
'owner_ref', 'organization_id', 'actor_ref', 'action',
'subject_type', 'subject_id', 'metadata', 'ip_address', 'created_at',
];
protected function casts(): array
{
return [
'metadata' => 'array',
'created_at' => 'datetime',
];
}
public function organization(): BelongsTo
{
return $this->belongsTo(Organization::class, 'organization_id');
}
public static function record(
string $ownerRef,
string $action,
?int $organizationId = null,
?string $actorRef = null,
?string $subjectType = null,
?int $subjectId = null,
?array $metadata = null,
): self {
return static::create([
'owner_ref' => $ownerRef,
'organization_id' => $organizationId,
'actor_ref' => $actorRef,
'action' => $action,
'subject_type' => $subjectType,
'subject_id' => $subjectId,
'metadata' => $metadata,
'ip_address' => request()?->ip(),
'created_at' => now(),
]);
}
}
+29
View File
@@ -0,0 +1,29 @@
<?php
namespace App\Models;
use App\Models\Concerns\BelongsToOwner;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\SoftDeletes;
class Branch extends Model
{
use BelongsToOwner, SoftDeletes;
protected $table = 'meet_branches';
protected $fillable = [
'owner_ref', 'organization_id', 'name', 'code', 'address', 'phone', 'is_active',
];
protected function casts(): array
{
return ['is_active' => 'boolean'];
}
public function organization(): BelongsTo
{
return $this->belongsTo(Organization::class, 'organization_id');
}
}
+23
View File
@@ -0,0 +1,23 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class BreakoutAssignment extends Model
{
protected $table = 'meet_breakout_assignments';
protected $fillable = ['breakout_room_id', 'participant_id'];
public function breakoutRoom(): BelongsTo
{
return $this->belongsTo(BreakoutRoom::class, 'breakout_room_id');
}
public function participant(): BelongsTo
{
return $this->belongsTo(Participant::class, 'participant_id');
}
}
+49
View File
@@ -0,0 +1,49 @@
<?php
namespace App\Models;
use App\Models\Concerns\BelongsToOwner;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Str;
class BreakoutRoom extends Model
{
use BelongsToOwner;
protected $table = 'meet_breakout_rooms';
protected $fillable = [
'uuid', 'owner_ref', 'session_id', 'name', 'media_room_name', 'status', 'closes_at',
];
protected function casts(): array
{
return ['closes_at' => 'datetime'];
}
protected static function booted(): void
{
static::creating(function (BreakoutRoom $room) {
if (empty($room->uuid)) {
$room->uuid = (string) Str::uuid();
}
});
}
public function getRouteKeyName(): string
{
return 'uuid';
}
public function session(): BelongsTo
{
return $this->belongsTo(Session::class, 'session_id');
}
public function assignments(): HasMany
{
return $this->hasMany(BreakoutAssignment::class, 'breakout_room_id');
}
}
+49
View File
@@ -0,0 +1,49 @@
<?php
namespace App\Models;
use App\Models\Concerns\BelongsToOwner;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Str;
class CalendarConnection extends Model
{
use BelongsToOwner;
protected $table = 'meet_calendar_connections';
protected $fillable = [
'uuid', 'owner_ref', 'organization_id', 'user_ref', 'provider',
'access_token', 'refresh_token', 'expires_at', 'calendar_id', 'metadata',
];
protected function casts(): array
{
return [
'expires_at' => 'datetime',
'metadata' => 'array',
'access_token' => 'encrypted',
'refresh_token' => 'encrypted',
];
}
protected static function booted(): void
{
static::creating(function (CalendarConnection $connection) {
if (empty($connection->uuid)) {
$connection->uuid = (string) Str::uuid();
}
});
}
public function organization(): BelongsTo
{
return $this->belongsTo(Organization::class, 'organization_id');
}
public function isExpired(): bool
{
return $this->expires_at !== null && $this->expires_at->isPast();
}
}
+54
View File
@@ -0,0 +1,54 @@
<?php
namespace App\Models;
use App\Models\Concerns\BelongsToOwner;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Str;
class Channel extends Model
{
use BelongsToOwner;
protected $table = 'meet_channels';
protected $fillable = [
'uuid', 'owner_ref', 'organization_id', 'branch_id', 'name', 'slug', 'visibility', 'description',
];
protected static function booted(): void
{
static::creating(function (Channel $channel) {
if (empty($channel->uuid)) {
$channel->uuid = (string) Str::uuid();
}
});
}
public function getRouteKeyName(): string
{
return 'uuid';
}
public function organization(): BelongsTo
{
return $this->belongsTo(Organization::class, 'organization_id');
}
public function members(): HasMany
{
return $this->hasMany(ChannelMember::class, 'channel_id');
}
public function messages(): HasMany
{
return $this->hasMany(ChannelMessage::class, 'channel_id');
}
public function isPrivate(): bool
{
return $this->visibility === 'private';
}
}
+18
View File
@@ -0,0 +1,18 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class ChannelMember extends Model
{
protected $table = 'meet_channel_members';
protected $fillable = ['channel_id', 'user_ref', 'role'];
public function channel(): BelongsTo
{
return $this->belongsTo(Channel::class, 'channel_id');
}
}
+49
View File
@@ -0,0 +1,49 @@
<?php
namespace App\Models;
use App\Models\Concerns\BelongsToOwner;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Str;
class ChannelMessage extends Model
{
use BelongsToOwner;
protected $table = 'meet_channel_messages';
protected $fillable = [
'uuid', 'owner_ref', 'channel_id', 'sender_ref', 'sender_name', 'body', 'parent_id', 'metadata',
];
protected function casts(): array
{
return ['metadata' => 'array'];
}
protected static function booted(): void
{
static::creating(function (ChannelMessage $message) {
if (empty($message->uuid)) {
$message->uuid = (string) Str::uuid();
}
});
}
public function channel(): BelongsTo
{
return $this->belongsTo(Channel::class, 'channel_id');
}
public function parent(): BelongsTo
{
return $this->belongsTo(ChannelMessage::class, 'parent_id');
}
public function replies(): HasMany
{
return $this->hasMany(ChannelMessage::class, 'parent_id');
}
}
+13
View File
@@ -0,0 +1,13 @@
<?php
namespace App\Models\Concerns;
use Illuminate\Database\Eloquent\Builder;
trait BelongsToOwner
{
public function scopeOwned(Builder $query, string $ownerRef): Builder
{
return $query->where($this->getTable().'.owner_ref', $ownerRef);
}
}
+44
View File
@@ -0,0 +1,44 @@
<?php
namespace App\Models;
use App\Models\Concerns\BelongsToOwner;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Str;
class ContactGroup extends Model
{
use BelongsToOwner;
protected $table = 'meet_contact_groups';
protected $fillable = [
'uuid', 'owner_ref', 'organization_id', 'name', 'description',
];
protected static function booted(): void
{
static::creating(function (ContactGroup $group) {
if (empty($group->uuid)) {
$group->uuid = (string) Str::uuid();
}
});
}
public function getRouteKeyName(): string
{
return 'uuid';
}
public function organization(): BelongsTo
{
return $this->belongsTo(Organization::class, 'organization_id');
}
public function members(): HasMany
{
return $this->hasMany(ContactGroupMember::class, 'contact_group_id');
}
}
+20
View File
@@ -0,0 +1,20 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class ContactGroupMember extends Model
{
protected $table = 'meet_contact_group_members';
protected $fillable = [
'contact_group_id', 'email', 'user_ref', 'display_name',
];
public function group(): BelongsTo
{
return $this->belongsTo(ContactGroup::class, 'contact_group_id');
}
}
+47
View File
@@ -0,0 +1,47 @@
<?php
namespace App\Models;
use App\Models\Concerns\BelongsToOwner;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Str;
class DirectConversation extends Model
{
use BelongsToOwner;
protected $table = 'meet_direct_conversations';
protected $fillable = ['uuid', 'owner_ref', 'organization_id', 'type', 'title'];
protected static function booted(): void
{
static::creating(function (DirectConversation $conversation) {
if (empty($conversation->uuid)) {
$conversation->uuid = (string) Str::uuid();
}
});
}
public function getRouteKeyName(): string
{
return 'uuid';
}
public function organization(): BelongsTo
{
return $this->belongsTo(Organization::class, 'organization_id');
}
public function participants(): HasMany
{
return $this->hasMany(DirectParticipant::class, 'conversation_id');
}
public function messages(): HasMany
{
return $this->hasMany(DirectMessage::class, 'conversation_id');
}
}
+44
View File
@@ -0,0 +1,44 @@
<?php
namespace App\Models;
use App\Models\Concerns\BelongsToOwner;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Str;
class DirectMessage extends Model
{
use BelongsToOwner;
protected $table = 'meet_direct_messages';
protected $fillable = [
'uuid', 'owner_ref', 'conversation_id', 'sender_ref', 'sender_name', 'body', 'parent_id',
];
protected static function booted(): void
{
static::creating(function (DirectMessage $message) {
if (empty($message->uuid)) {
$message->uuid = (string) Str::uuid();
}
});
}
public function conversation(): BelongsTo
{
return $this->belongsTo(DirectConversation::class, 'conversation_id');
}
public function parent(): BelongsTo
{
return $this->belongsTo(DirectMessage::class, 'parent_id');
}
public function replies(): HasMany
{
return $this->hasMany(DirectMessage::class, 'parent_id');
}
}
+18
View File
@@ -0,0 +1,18 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class DirectParticipant extends Model
{
protected $table = 'meet_direct_participants';
protected $fillable = ['conversation_id', 'user_ref'];
public function conversation(): BelongsTo
{
return $this->belongsTo(DirectConversation::class, 'conversation_id');
}
}
+27
View File
@@ -0,0 +1,27 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class FileDownload extends Model
{
public $timestamps = false;
protected $table = 'meet_file_downloads';
protected $fillable = [
'session_file_id', 'downloaded_by_ref', 'downloaded_by_name', 'ip_address', 'downloaded_at',
];
protected function casts(): array
{
return ['downloaded_at' => 'datetime'];
}
public function file(): BelongsTo
{
return $this->belongsTo(SessionFile::class, 'session_file_id');
}
}
+56
View File
@@ -0,0 +1,56 @@
<?php
namespace App\Models;
use App\Models\Concerns\BelongsToOwner;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Str;
class Invitation extends Model
{
use BelongsToOwner;
protected $table = 'meet_invitations';
protected $fillable = [
'uuid', 'owner_ref', 'room_id', 'email', 'user_ref',
'display_name', 'role', 'status', 'rsvp_token', 'responded_at',
'calendar_event_id', 'sent_at',
];
protected function casts(): array
{
return [
'sent_at' => 'datetime',
'responded_at' => 'datetime',
];
}
protected static function booted(): void
{
static::creating(function (Invitation $invitation) {
if (empty($invitation->uuid)) {
$invitation->uuid = (string) Str::uuid();
}
if (empty($invitation->rsvp_token)) {
$invitation->rsvp_token = Str::random(40);
}
});
}
public function getRouteKeyName(): string
{
return 'uuid';
}
public function rsvpUrl(): string
{
return route('meet.invitations.rsvp', ['invitation' => $this->uuid, 'token' => $this->rsvp_token]);
}
public function room(): BelongsTo
{
return $this->belongsTo(Room::class, 'room_id');
}
}
+61
View File
@@ -0,0 +1,61 @@
<?php
namespace App\Models;
use App\Models\Concerns\BelongsToOwner;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Str;
class LiveStream extends Model
{
use BelongsToOwner;
protected $table = 'meet_live_streams';
protected $fillable = [
'uuid', 'owner_ref', 'session_id', 'platform', 'rtmp_url', 'stream_key',
'status', 'layout', 'health', 'started_at', 'ended_at',
];
protected function casts(): array
{
return [
'started_at' => 'datetime',
'ended_at' => 'datetime',
];
}
protected static function booted(): void
{
static::creating(function (LiveStream $stream) {
if (empty($stream->uuid)) {
$stream->uuid = (string) Str::uuid();
}
});
}
public function session(): BelongsTo
{
return $this->belongsTo(Session::class, 'session_id');
}
public function setStreamKeyAttribute(?string $value): void
{
$this->attributes['stream_key'] = $value ? Crypt::encryptString($value) : null;
}
public function getStreamKeyAttribute(?string $value): ?string
{
if (! $value) {
return null;
}
try {
return Crypt::decryptString($value);
} catch (\Throwable) {
return null;
}
}
}
+49
View File
@@ -0,0 +1,49 @@
<?php
namespace App\Models;
use App\Models\Concerns\BelongsToOwner;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Str;
class MeetPoll extends Model
{
use BelongsToOwner;
protected $table = 'meet_polls';
protected $fillable = [
'uuid', 'owner_ref', 'session_id', 'question', 'options', 'status',
];
protected function casts(): array
{
return ['options' => 'array'];
}
protected static function booted(): void
{
static::creating(function (MeetPoll $poll) {
if (empty($poll->uuid)) {
$poll->uuid = (string) Str::uuid();
}
});
}
public function getRouteKeyName(): string
{
return 'uuid';
}
public function session(): BelongsTo
{
return $this->belongsTo(Session::class, 'session_id');
}
public function votes(): HasMany
{
return $this->hasMany(PollVote::class, 'poll_id');
}
}
+31
View File
@@ -0,0 +1,31 @@
<?php
namespace App\Models;
use App\Models\Concerns\BelongsToOwner;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Member extends Model
{
use BelongsToOwner;
protected $table = 'meet_members';
protected $fillable = ['owner_ref', 'organization_id', 'user_ref', 'role', 'branch_id'];
public function organization(): BelongsTo
{
return $this->belongsTo(Organization::class, 'organization_id');
}
public function branch(): BelongsTo
{
return $this->belongsTo(Branch::class, 'branch_id');
}
public function hasRole(string ...$roles): bool
{
return in_array($this->role, $roles, true);
}
}
+39
View File
@@ -0,0 +1,39 @@
<?php
namespace App\Models;
use App\Models\Concerns\BelongsToOwner;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Str;
class Message extends Model
{
use BelongsToOwner;
protected $table = 'meet_messages';
protected $fillable = [
'uuid', 'owner_ref', 'session_id', 'breakout_room_id', 'sender_ref', 'sender_name',
'type', 'recipient_ref', 'body', 'metadata',
];
protected function casts(): array
{
return ['metadata' => 'array'];
}
protected static function booted(): void
{
static::creating(function (Message $message) {
if (empty($message->uuid)) {
$message->uuid = (string) Str::uuid();
}
});
}
public function session(): BelongsTo
{
return $this->belongsTo(Session::class, 'session_id');
}
}
+49
View File
@@ -0,0 +1,49 @@
<?php
namespace App\Models;
use App\Models\Concerns\BelongsToOwner;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
class Organization extends Model
{
use BelongsToOwner, SoftDeletes;
protected $table = 'meet_organizations';
protected $fillable = [
'owner_ref', 'name', 'slug', 'logo_path', 'timezone', 'license_tier', 'usage_quotas',
'settings', 'security_policy',
];
protected function casts(): array
{
return [
'settings' => 'array',
'security_policy' => 'array',
'usage_quotas' => 'array',
];
}
public function branches(): HasMany
{
return $this->hasMany(Branch::class, 'organization_id');
}
public function members(): HasMany
{
return $this->hasMany(Member::class, 'organization_id');
}
public function templates(): HasMany
{
return $this->hasMany(Template::class, 'organization_id');
}
public function securitySetting(string $key, mixed $default = null): mixed
{
return data_get($this->security_policy, $key, data_get(config('meet.security_defaults'), $key, $default));
}
}
+81
View File
@@ -0,0 +1,81 @@
<?php
namespace App\Models;
use App\Models\Concerns\BelongsToOwner;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Str;
class Participant extends Model
{
use BelongsToOwner;
protected $table = 'meet_participants';
protected $fillable = [
'uuid', 'owner_ref', 'session_id', 'user_ref', 'display_name', 'email',
'role', 'status', 'joined_at', 'left_at', 'device_info', 'ip_address',
'hand_raised', 'is_muted', 'is_video_off', 'breakout_room_id',
];
protected function casts(): array
{
return [
'joined_at' => 'datetime',
'left_at' => 'datetime',
'hand_raised' => 'boolean',
'is_muted' => 'boolean',
'is_video_off' => 'boolean',
];
}
protected static function booted(): void
{
static::creating(function (Participant $participant) {
if (empty($participant->uuid)) {
$participant->uuid = (string) Str::uuid();
}
});
}
public function getRouteKeyName(): string
{
return 'uuid';
}
public function session(): BelongsTo
{
return $this->belongsTo(Session::class, 'session_id');
}
public function isHost(): bool
{
return in_array($this->role, ['host', 'co_host'], true);
}
public function isPanelist(): bool
{
return $this->role === 'panelist';
}
public function isAttendee(): bool
{
return $this->role === 'attendee';
}
public function canPublishMedia(): bool
{
return $this->role !== 'attendee';
}
public function breakoutRoom(): BelongsTo
{
return $this->belongsTo(BreakoutRoom::class, 'breakout_room_id');
}
public function isJoined(): bool
{
return $this->status === 'joined';
}
}
+18
View File
@@ -0,0 +1,18 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class PollVote extends Model
{
protected $table = 'meet_poll_votes';
protected $fillable = ['poll_id', 'participant_uuid', 'option_index'];
public function poll(): BelongsTo
{
return $this->belongsTo(MeetPoll::class, 'poll_id');
}
}
+39
View File
@@ -0,0 +1,39 @@
<?php
namespace App\Models;
use App\Models\Concerns\BelongsToOwner;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Str;
class QaQuestion extends Model
{
use BelongsToOwner;
protected $table = 'meet_qa_questions';
protected $fillable = [
'uuid', 'owner_ref', 'session_id', 'participant_uuid', 'asker_name',
'question', 'status', 'answer', 'answered_by', 'upvotes',
];
protected static function booted(): void
{
static::creating(function (QaQuestion $q) {
if (empty($q->uuid)) {
$q->uuid = (string) Str::uuid();
}
});
}
public function getRouteKeyName(): string
{
return 'uuid';
}
public function session(): BelongsTo
{
return $this->belongsTo(Session::class, 'session_id');
}
}
+23
View File
@@ -0,0 +1,23 @@
<?php
namespace App\Models;
use App\Models\Concerns\BelongsToOwner;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Reaction extends Model
{
use BelongsToOwner;
protected $table = 'meet_reactions';
protected $fillable = [
'owner_ref', 'session_id', 'participant_ref', 'sender_name', 'emoji',
];
public function session(): BelongsTo
{
return $this->belongsTo(Session::class, 'session_id');
}
}
+54
View File
@@ -0,0 +1,54 @@
<?php
namespace App\Models;
use App\Models\Concerns\BelongsToOwner;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Str;
class Recording extends Model
{
use BelongsToOwner;
protected $table = 'meet_recordings';
protected $fillable = [
'uuid', 'owner_ref', 'session_id', 'status', 'layout', 'storage_path',
'thumbnail_path', 'file_size', 'duration_seconds', 'started_by_ref',
'started_at', 'ended_at', 'failure_reason', 'metadata',
];
protected function casts(): array
{
return [
'started_at' => 'datetime',
'ended_at' => 'datetime',
'metadata' => 'array',
];
}
protected static function booted(): void
{
static::creating(function (Recording $recording) {
if (empty($recording->uuid)) {
$recording->uuid = (string) Str::uuid();
}
});
}
public function getRouteKeyName(): string
{
return 'uuid';
}
public function session(): BelongsTo
{
return $this->belongsTo(Session::class, 'session_id');
}
public function isReady(): bool
{
return $this->status === 'ready';
}
}
+118
View File
@@ -0,0 +1,118 @@
<?php
namespace App\Models;
use App\Models\Concerns\BelongsToOwner;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Str;
class Room extends Model
{
use BelongsToOwner, SoftDeletes;
protected $table = 'meet_rooms';
protected $fillable = [
'uuid', 'owner_ref', 'organization_id', 'branch_id', 'host_user_ref',
'title', 'description', 'type', 'status', 'slug', 'scheduled_at',
'duration_minutes', 'timezone', 'passcode', 'settings', 'source', 'calendar_event_id',
'template_id', 'recurring_series_id', 'recurrence_rule', 'recurrence_interval', 'recurrence_until',
];
protected function casts(): array
{
return [
'scheduled_at' => 'datetime',
'recurrence_until' => 'date',
'settings' => 'array',
'source' => 'array',
];
}
protected static function booted(): void
{
static::creating(function (Room $room) {
if (empty($room->uuid)) {
$room->uuid = (string) Str::uuid();
}
});
}
public function getRouteKeyName(): string
{
return 'uuid';
}
public function organization(): BelongsTo
{
return $this->belongsTo(Organization::class, 'organization_id');
}
public function branch(): BelongsTo
{
return $this->belongsTo(Branch::class, 'branch_id');
}
public function sessions(): HasMany
{
return $this->hasMany(Session::class, 'room_id');
}
public function invitations(): HasMany
{
return $this->hasMany(Invitation::class, 'room_id');
}
public function webinarRegistrations(): HasMany
{
return $this->hasMany(WebinarRegistration::class, 'room_id');
}
public function sessionFiles(): HasMany
{
return $this->hasMany(SessionFile::class, 'room_id');
}
public function isWebinar(): bool
{
return $this->type === 'webinar';
}
public function isTownHall(): bool
{
return $this->type === 'town_hall';
}
public function template(): BelongsTo
{
return $this->belongsTo(Template::class, 'template_id');
}
public function isRecurring(): bool
{
return filled($this->recurrence_rule);
}
public function activeSession(): ?Session
{
return $this->sessions()->where('status', 'live')->latest()->first();
}
public function setting(string $key, mixed $default = null): mixed
{
return data_get($this->settings, $key, data_get(config('meet.default_settings'), $key, $default));
}
public function isLive(): bool
{
return $this->status === 'live';
}
public function joinUrl(): string
{
return url('/r/'.$this->uuid);
}
}
+123
View File
@@ -0,0 +1,123 @@
<?php
namespace App\Models;
use App\Models\Concerns\BelongsToOwner;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Str;
class Session extends Model
{
use BelongsToOwner;
protected $table = 'meet_sessions';
protected $fillable = [
'uuid', 'owner_ref', 'room_id', 'parent_session_id', 'media_room_name', 'status',
'session_mode', 'presenter_refs', 'is_breakout', 'started_at', 'ended_at',
'peak_participants', 'is_locked', 'locked_at',
];
protected function casts(): array
{
return [
'started_at' => 'datetime',
'ended_at' => 'datetime',
'locked_at' => 'datetime',
'is_locked' => 'boolean',
'is_breakout' => 'boolean',
'presenter_refs' => 'array',
];
}
protected static function booted(): void
{
static::creating(function (Session $session) {
if (empty($session->uuid)) {
$session->uuid = (string) Str::uuid();
}
});
}
public function getRouteKeyName(): string
{
return 'uuid';
}
public function room(): BelongsTo
{
return $this->belongsTo(Room::class, 'room_id');
}
public function participants(): HasMany
{
return $this->hasMany(Participant::class, 'session_id');
}
public function messages(): HasMany
{
return $this->hasMany(Message::class, 'session_id');
}
public function reactions(): HasMany
{
return $this->hasMany(Reaction::class, 'session_id');
}
public function recordings(): HasMany
{
return $this->hasMany(Recording::class, 'session_id');
}
public function transcripts(): HasMany
{
return $this->hasMany(Transcript::class, 'session_id');
}
public function aiSummaries(): HasMany
{
return $this->hasMany(AiSummary::class, 'session_id');
}
public function qaQuestions(): HasMany
{
return $this->hasMany(QaQuestion::class, 'session_id');
}
public function polls(): HasMany
{
return $this->hasMany(MeetPoll::class, 'session_id');
}
public function breakoutRooms(): HasMany
{
return $this->hasMany(BreakoutRoom::class, 'session_id');
}
public function whiteboard(): \Illuminate\Database\Eloquent\Relations\HasOne
{
return $this->hasOne(Whiteboard::class, 'session_id');
}
public function sessionFiles(): HasMany
{
return $this->hasMany(SessionFile::class, 'session_id');
}
public function liveStreams(): HasMany
{
return $this->hasMany(LiveStream::class, 'session_id');
}
public function activeParticipants(): HasMany
{
return $this->participants()->where('status', 'joined');
}
public function isLive(): bool
{
return $this->status === 'live';
}
}
+60
View File
@@ -0,0 +1,60 @@
<?php
namespace App\Models;
use App\Models\Concerns\BelongsToOwner;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Str;
class SessionFile extends Model
{
use BelongsToOwner;
protected $table = 'meet_session_files';
protected $fillable = [
'uuid', 'owner_ref', 'room_id', 'session_id', 'uploaded_by_ref', 'uploaded_by_name',
'original_name', 'storage_path', 'mime_type', 'file_size', 'drive_file_id', 'expires_at',
];
protected function casts(): array
{
return ['expires_at' => 'datetime'];
}
protected static function booted(): void
{
static::creating(function (SessionFile $file) {
if (empty($file->uuid)) {
$file->uuid = (string) Str::uuid();
}
});
}
public function getRouteKeyName(): string
{
return 'uuid';
}
public function room(): BelongsTo
{
return $this->belongsTo(Room::class, 'room_id');
}
public function session(): BelongsTo
{
return $this->belongsTo(Session::class, 'session_id');
}
public function downloads(): HasMany
{
return $this->hasMany(FileDownload::class, 'session_file_id');
}
public function isExpired(): bool
{
return $this->expires_at !== null && $this->expires_at->isPast();
}
}
+50
View File
@@ -0,0 +1,50 @@
<?php
namespace App\Models;
use App\Models\Concerns\BelongsToOwner;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Str;
class Template extends Model
{
use BelongsToOwner, SoftDeletes;
protected $table = 'meet_templates';
protected $fillable = [
'uuid', 'owner_ref', 'organization_id', 'name', 'description',
'duration_minutes', 'settings', 'is_default',
];
protected function casts(): array
{
return ['settings' => 'array', 'is_default' => 'boolean'];
}
protected static function booted(): void
{
static::creating(function (Template $template) {
if (empty($template->uuid)) {
$template->uuid = (string) Str::uuid();
}
});
}
public function getRouteKeyName(): string
{
return 'uuid';
}
public function organization(): BelongsTo
{
return $this->belongsTo(Organization::class, 'organization_id');
}
public function mergedSettings(): array
{
return array_merge(config('meet.default_settings'), $this->settings ?? []);
}
}
+50
View File
@@ -0,0 +1,50 @@
<?php
namespace App\Models;
use App\Models\Concerns\BelongsToOwner;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Support\Str;
class Transcript extends Model
{
use BelongsToOwner;
protected $table = 'meet_transcripts';
protected $fillable = [
'uuid', 'owner_ref', 'session_id', 'recording_id', 'status',
'content', 'segments', 'live_captions_enabled',
];
protected function casts(): array
{
return ['segments' => 'array', 'live_captions_enabled' => 'boolean'];
}
protected static function booted(): void
{
static::creating(function (Transcript $transcript) {
if (empty($transcript->uuid)) {
$transcript->uuid = (string) Str::uuid();
}
});
}
public function session(): BelongsTo
{
return $this->belongsTo(Session::class, 'session_id');
}
public function recording(): BelongsTo
{
return $this->belongsTo(Recording::class, 'recording_id');
}
public function aiSummary(): HasOne
{
return $this->hasOne(AiSummary::class, 'transcript_id');
}
}
+31
View File
@@ -0,0 +1,31 @@
<?php
namespace App\Models;
use App\Models\Concerns\BelongsToOwner;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class UsageRecord extends Model
{
use BelongsToOwner;
protected $table = 'meet_usage_records';
protected $fillable = [
'owner_ref', 'organization_id', 'branch_id', 'metric', 'quantity', 'metadata', 'recorded_at',
];
protected function casts(): array
{
return [
'metadata' => 'array',
'recorded_at' => 'datetime',
];
}
public function organization(): BelongsTo
{
return $this->belongsTo(Organization::class, 'organization_id');
}
}
+41
View File
@@ -0,0 +1,41 @@
<?php
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
/**
* Thin local mirror of the platform identity (auth.ladill.com owns users).
* Keyed by the OIDC `sub` (public_id); every Care record is scoped to it.
*/
class User extends Authenticatable
{
use HasApiTokens, Notifiable;
protected $fillable = ['public_id', 'name', 'email', 'avatar_url', 'password', 'last_app_active_at'];
protected $hidden = ['password', 'remember_token'];
protected function casts(): array
{
return [
'email_verified_at' => 'datetime',
'last_app_active_at' => 'datetime',
'password' => 'hashed',
];
}
public function ownerRef(): string
{
return (string) $this->public_id;
}
public function avatarUrl(): ?string
{
$url = trim((string) $this->avatar_url);
return $url !== '' ? $url : null;
}
}
+51
View File
@@ -0,0 +1,51 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Str;
class WebhookEndpoint extends Model
{
protected $table = 'meet_webhook_endpoints';
protected $fillable = [
'uuid', 'owner_ref', 'organization_id', 'url', 'secret', 'events', 'is_active',
];
protected function casts(): array
{
return [
'events' => 'array',
'is_active' => 'boolean',
'secret' => 'encrypted',
];
}
protected static function booted(): void
{
static::creating(function (WebhookEndpoint $endpoint) {
if (empty($endpoint->uuid)) {
$endpoint->uuid = (string) Str::uuid();
}
});
}
public function getRouteKeyName(): string
{
return 'uuid';
}
public function organization(): BelongsTo
{
return $this->belongsTo(Organization::class, 'organization_id');
}
public function subscribesTo(string $event): bool
{
$events = $this->events ?? config('meet.webhook_events', []);
return in_array('*', $events, true) || in_array($event, $events, true);
}
}
+52
View File
@@ -0,0 +1,52 @@
<?php
namespace App\Models;
use App\Models\Concerns\BelongsToOwner;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Str;
class WebinarRegistration extends Model
{
use BelongsToOwner;
protected $table = 'meet_webinar_registrations';
protected $fillable = [
'uuid', 'owner_ref', 'room_id', 'email', 'display_name', 'status',
'access_token', 'approved_at', 'metadata',
];
protected function casts(): array
{
return ['approved_at' => 'datetime', 'metadata' => 'array'];
}
protected static function booted(): void
{
static::creating(function (WebinarRegistration $reg) {
if (empty($reg->uuid)) {
$reg->uuid = (string) Str::uuid();
}
if (empty($reg->access_token)) {
$reg->access_token = Str::random(48);
}
});
}
public function getRouteKeyName(): string
{
return 'uuid';
}
public function room(): BelongsTo
{
return $this->belongsTo(Room::class, 'room_id');
}
public function isApproved(): bool
{
return $this->status === 'approved';
}
}
+48
View File
@@ -0,0 +1,48 @@
<?php
namespace App\Models;
use App\Models\Concerns\BelongsToOwner;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Str;
class Whiteboard extends Model
{
use BelongsToOwner;
protected $table = 'meet_whiteboards';
protected $fillable = [
'uuid', 'owner_ref', 'room_id', 'session_id', 'template', 'state',
];
protected function casts(): array
{
return ['state' => 'array'];
}
protected static function booted(): void
{
static::creating(function (Whiteboard $board) {
if (empty($board->uuid)) {
$board->uuid = (string) Str::uuid();
}
});
}
public function getRouteKeyName(): string
{
return 'uuid';
}
public function room(): BelongsTo
{
return $this->belongsTo(Room::class, 'room_id');
}
public function session(): BelongsTo
{
return $this->belongsTo(Session::class, 'session_id');
}
}