• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

RonasIT / laravel-chat / 27735745224

18 Jun 2026 03:59AM UTC coverage: 99.658% (-0.2%) from 99.811%
27735745224

Pull #65

github

web-flow
Merge fa5d3a42b into 7d73c18bc
Pull Request #65: feat: resolve title and cover for private conversations from the other participant

69 of 70 new or added lines in 11 files covered. (98.57%)

582 of 584 relevant lines covered (99.66%)

31.9 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.39
/src/Models/Conversation.php
1
<?php
2

3
namespace RonasIT\Chat\Models;
4

5
use Illuminate\Database\Eloquent\Builder;
6
use Illuminate\Database\Eloquent\Casts\Attribute;
7
use Illuminate\Database\Eloquent\Model;
8
use Illuminate\Database\Eloquent\Relations\BelongsTo;
9
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
10
use Illuminate\Database\Eloquent\Relations\HasMany;
11
use Illuminate\Database\Eloquent\Relations\HasOne;
12
use Illuminate\Support\Arr;
13
use RonasIT\Chat\Contracts\Models\ConversationModelContract;
14
use RonasIT\Chat\Contracts\Models\MessageModelContract;
15
use RonasIT\Chat\Enums\Conversation\TypeEnum;
16
use RonasIT\Support\Traits\ModelTrait;
17

18
class Conversation extends Model implements ConversationModelContract
19
{
20
    use ModelTrait;
21

22
    protected $fillable = [
23
        'creator_id',
24
        'type',
25
        'title',
26
        'cover_id',
27
        'last_updated_at',
28
    ];
29

30
    protected $hidden = ['pivot'];
31

32
    protected $casts = [
33
        'type' => TypeEnum::class,
34
    ];
35

36
    public function last_message(): HasOne
37
    {
38
        return $this->hasOne(app()->getAlias(MessageModelContract::class))->latest();
18✔
39
    }
40

41
    public function messages(): HasMany
42
    {
43
        return $this->hasMany(app()->getAlias(MessageModelContract::class));
21✔
44
    }
45

46
    public function creator(): BelongsTo
47
    {
48
        return $this->belongsTo(config('chat.classes.user.model'));
6✔
49
    }
50

51
    public function members(): BelongsToMany
52
    {
53
        return $this->belongsToMany(
95✔
54
            related: config('chat.classes.user.model'),
95✔
55
            table: 'conversation_member',
95✔
56
            foreignPivotKey: 'conversation_id',
95✔
57
            relatedPivotKey: 'member_id',
95✔
58
        );
95✔
59
    }
60

61
    public function cover(): BelongsTo
62
    {
63
        return $this->belongsTo(config('chat.classes.media.model'), 'cover_id');
8✔
64
    }
65

66
    protected function title(): Attribute
67
    {
68
        return Attribute::make(
47✔
69
            get: function ($value): ?string {
47✔
70
                return ($this->isPrivate())
47✔
71
                    ? Arr::get($this->attributes, 'calculated_title')
45✔
72
                    : $value;
47✔
73
            },
47✔
74
        );
47✔
75
    }
76

77
    protected function coverId(): Attribute
78
    {
79
        return Attribute::make(
8✔
80
            get: function ($value): ?int {
8✔
81
                return ($this->isPrivate())
8✔
82
                    ? Arr::get($this->attributes, 'calculated_cover_id')
8✔
83
                    : $value;
8✔
84
            },
8✔
85
        );
8✔
86
    }
87

88
    public function scopeWithCalculatedIdentity(Builder $query, int $forMemberId): Builder
89
    {
90
        $constraint = fn (Builder $query) => $query->where('member_id', '!=', $forMemberId);
45✔
91

92
        if ($titleColumns = config('chat.classes.user.columns.full_name')) {
45✔
93
            $titleColumn = $this->buildTitleExpression($titleColumns);
45✔
94

95
            $query->withAggregate(['members as calculated_title' => $constraint], $titleColumn);
45✔
96
        } else {
NEW
97
            trigger_error('chat.classes.user.columns.full_name is not configured, calculated_title will be null.', E_USER_WARNING);
×
98
        }
99

100
        if ($avatarColumn = config('chat.classes.user.columns.avatar')) {
45✔
101
            $query->withAggregate(['members as calculated_cover_id' => $constraint], $avatarColumn);
45✔
102
        }
103

104
        return $query;
45✔
105
    }
106

107
    public function scopeWithUnreadMessagesCount(Builder $query, int $memberId): Builder
108
    {
109
        return $query->withCount([
14✔
110
            'messages as unread_messages_count' => fn ($query) => $query
14✔
111
                ->whereNot('sender_id', $memberId)
14✔
112
                ->whereDoesntHave('reads', fn ($query) => $query->where('read_messages.member_id', $memberId)),
14✔
113
        ]);
14✔
114
    }
115

116
    public function pinned_messages(): BelongsToMany
117
    {
118
        return $this
24✔
119
            ->belongsToMany(
24✔
120
                related: app()->getAlias(MessageModelContract::class),
24✔
121
                table: 'pinned_messages',
24✔
122
                foreignPivotKey: 'conversation_id',
24✔
123
                relatedPivotKey: 'message_id',
24✔
124
            )
24✔
125
            ->orderByPivot('id', 'desc')
24✔
126
            ->withTimestamps();
24✔
127
    }
128

129
    public function hasMember(Model $member): bool
130
    {
131
        return $this->members()->where('member_id', $member->id)->exists();
44✔
132
    }
133

134
    public function isCreator(Model $member): bool
135
    {
136
        return $this->getAttribute('creator_id') === $member->id;
4✔
137
    }
138

139
    public function isGroup(): bool
140
    {
141
        return $this->getAttribute('type') === TypeEnum::Group;
2✔
142
    }
143

144
    public function isPrivate(): bool
145
    {
146
        return $this->getAttribute('type') === TypeEnum::Private;
49✔
147
    }
148

149
    public function hasPinnedMessage(int $messageId): bool
150
    {
151
        return $this
5✔
152
            ->pinned_messages()
5✔
153
            ->whereKey($messageId)
5✔
154
            ->exists();
5✔
155
    }
156

157
    private function buildTitleExpression(array $columns): string
158
    {
159
        $expression = array_shift($columns);
48✔
160

161
        if (!empty($columns)) {
48✔
162
            $separators = config('chat.classes.user.columns.full_name_separator');
47✔
163

164
            $expression = "COALESCE({$expression}, '')";
47✔
165

166
            foreach ($columns as $i => $column) {
47✔
167
                $sep = str_replace("'", "''", ($separators[$i] ?? end($separators)) ?: '');
47✔
168
                $expression .= " || COALESCE('{$sep}' || {$column}, '')";
47✔
169
            }
170
        }
171

172
        return $expression;
48✔
173
    }
174
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc