195 lines
5.8 KiB
PHP
195 lines
5.8 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\User;
|
|
use App\Models\Dynamic;
|
|
use App\Models\Ledger;
|
|
use App\Models\Mutation;
|
|
use App\Models\Message;
|
|
use App\Models\ReadCursor;
|
|
use Illuminate\Support\Carbon;
|
|
use Illuminate\Support\Str;
|
|
|
|
use App\Notifications\NewActivityNotification;
|
|
|
|
class ActivityService
|
|
{
|
|
/**
|
|
* Update the read cursor for a user on a specific entity.
|
|
*/
|
|
public function updateCursor(User $user, $entity): void
|
|
{
|
|
ReadCursor::updateOrCreate(
|
|
[
|
|
'user_id' => $user->id,
|
|
'cursorable_id' => $entity->id,
|
|
'cursorable_type' => get_class($entity),
|
|
],
|
|
[
|
|
'read_at' => Carbon::now(),
|
|
]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get the read cursor timestamp for a user on a specific entity.
|
|
*/
|
|
public function getCursorReadAt(User $user, $entity): \Carbon\CarbonInterface
|
|
{
|
|
$cursor = ReadCursor::where([
|
|
'user_id' => $user->id,
|
|
'cursorable_id' => $entity->id,
|
|
'cursorable_type' => get_class($entity),
|
|
])->first();
|
|
|
|
// If no cursor exists, default to epoch (all activities are unread)
|
|
return $cursor ? $cursor->read_at : Carbon::parse('1970-01-01');
|
|
}
|
|
|
|
public function createMessage($chat, $user, $content, $subject = null)
|
|
{
|
|
$message = $chat->messages()->create([
|
|
'user_id' => $user ? $user->id : null,
|
|
'content' => $content,
|
|
'subject_id' => $subject ? $subject->id : null,
|
|
'subject_type' => $subject ? get_class($subject) : null,
|
|
]);
|
|
|
|
$this->notify($message);
|
|
|
|
return $message;
|
|
}
|
|
|
|
public function createMutation($ledger, $user, $type, $amount, $description, $status)
|
|
{
|
|
$mutation = $ledger->mutations()->create([
|
|
'user_id' => $user->id,
|
|
'type' => $type,
|
|
'amount' => $amount,
|
|
'description' => $description,
|
|
'status' => $status,
|
|
]);
|
|
|
|
return $mutation;
|
|
}
|
|
|
|
public function notify(Message $message)
|
|
{
|
|
$dynamic = $message->chat->chatable;
|
|
|
|
if ($dynamic instanceof Dynamic) {
|
|
$participants = $dynamic->participants;
|
|
|
|
foreach ($participants as $participant) {
|
|
if ($message->user_id !== $participant->id) {
|
|
$participant->notify(new NewActivityNotification($message));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieve all activities for a given entity.
|
|
*/
|
|
public function getActivitiesForDynamic(Dynamic $dynamic): array
|
|
{
|
|
$participants = $dynamic->participants()->withPivot('display_name')->get();
|
|
$participantsMap = $participants->reduce(function ($acc, $p) {
|
|
$acc[$p->id] = $p->pivot->display_name ?? $p->name;
|
|
return $acc;
|
|
}, []);
|
|
|
|
$messages = Message::where('chat_id', $dynamic->chat->id)
|
|
->with(['user', 'subject'])
|
|
->latest()
|
|
->get();
|
|
|
|
return $messages->map(function ($message) use ($participantsMap) {
|
|
$messageData = $message->toArray();
|
|
$messageData['url'] = $this->getUrlForMessage($message);
|
|
|
|
// Resolve <user:id> placeholders to actual names/display names
|
|
$messageData['content'] = preg_replace_callback('/<user:(\d+)>/', function ($matches) use ($participantsMap) {
|
|
$userId = $matches[1];
|
|
return $participantsMap[$userId] ?? "User #{$userId}";
|
|
}, $message->content);
|
|
|
|
return $messageData;
|
|
})->all();
|
|
}
|
|
|
|
/**
|
|
* Get unread activities grouped by active entities (Dynamics, Ledgers) for the given user.
|
|
*/
|
|
public function getUnreadDynamicsGrouped(User $user): array
|
|
{
|
|
$groupedDynamics = [];
|
|
$participatingDynamics = $user->dynamics()->with('ledgers')->get();
|
|
|
|
foreach ($participatingDynamics as $dynamic) {
|
|
$readAt = $this->getCursorReadAt($user, $dynamic);
|
|
$activities = $this->getActivitiesForDynamic($dynamic);
|
|
|
|
$this->partitionAndGroupActivities($activities, $readAt, $dynamic, $groupedDynamics);
|
|
}
|
|
|
|
return $groupedDynamics;
|
|
}
|
|
|
|
/**
|
|
* Partition activities into read and unread, and construct the grouped entity metadata.
|
|
*/
|
|
private function partitionAndGroupActivities(array $activities, \Carbon\CarbonInterface $readAt, Dynamic $dynamic, array &$groupedDynamics): void
|
|
{
|
|
$alreadyRead = [];
|
|
$unread = [];
|
|
|
|
foreach ($activities as $act) {
|
|
if (Carbon::parse($act['created_at'])->gt($readAt)) {
|
|
$unread[] = $act;
|
|
} else {
|
|
$alreadyRead[] = $act;
|
|
}
|
|
}
|
|
|
|
if (!empty($unread)) {
|
|
$context = array_slice($alreadyRead, 0, 2);
|
|
|
|
$groupedDynamics[] = [
|
|
'id' => $dynamic->id,
|
|
'name' => $dynamic->name,
|
|
'url' => route('dynamics.show', $dynamic->uuid),
|
|
'unread_count' => count($unread),
|
|
'context_activities' => $context,
|
|
'new_activities' => array_reverse($unread),
|
|
];
|
|
}
|
|
}
|
|
|
|
private function getUrlForEntity($entity): string
|
|
{
|
|
if ($entity instanceof Dynamic) {
|
|
return route('dynamics.show', $entity->uuid);
|
|
}
|
|
|
|
if ($entity instanceof Ledger) {
|
|
return route('dynamics.ledgers.show', [$entity->dynamic->uuid, $entity->uuid]);
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
private function getUrlForMessage(Message $message): string
|
|
{
|
|
if ($message->subject) {
|
|
return $this->getUrlForEntity($message->subject);
|
|
}
|
|
|
|
if ($message->chat->chatable) {
|
|
return $this->getUrlForEntity($message->chat->chatable);
|
|
}
|
|
|
|
return '';
|
|
}
|
|
} |