174 lines
5.0 KiB
PHP
174 lines
5.0 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;
|
|
|
|
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,
|
|
]);
|
|
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* Retrieve all activities for a given entity.
|
|
*/
|
|
public function getActivitiesForEntity($entity): array
|
|
{
|
|
if ($entity instanceof Dynamic) {
|
|
$chatId = $entity->chat->id;
|
|
} elseif ($entity instanceof Ledger) {
|
|
$chatId = $entity->dynamic->chat->id;
|
|
} else {
|
|
return [];
|
|
}
|
|
|
|
$messages = Message::where('chat_id', $chatId)
|
|
->with(['user', 'subject'])
|
|
->latest()
|
|
->get();
|
|
|
|
return $messages->map(function ($message) {
|
|
$messageData = $message->toArray();
|
|
$messageData['url'] = $this->getUrlForMessage($message);
|
|
return $messageData;
|
|
})->all();
|
|
}
|
|
|
|
/**
|
|
* Get unread activities grouped by active entities (Dynamics, Ledgers) for the given user.
|
|
*/
|
|
public function getUnreadEntitiesGrouped(User $user): array
|
|
{
|
|
$groupedEntities = [];
|
|
$participatingDynamics = $user->dynamics()->with('ledgers')->get();
|
|
|
|
$entities = $participatingDynamics->concat($participatingDynamics->flatMap(fn ($d) => $d->ledgers));
|
|
|
|
foreach ($entities as $entity) {
|
|
$readAt = $this->getCursorReadAt($user, $entity);
|
|
$activities = $this->getActivitiesForEntity($entity);
|
|
|
|
$this->partitionActivities($activities, $readAt, $entity, get_class($entity), $this->getUrlForEntity($entity), $groupedEntities);
|
|
}
|
|
|
|
return $groupedEntities;
|
|
}
|
|
|
|
/**
|
|
* Partition activities into read and unread, and construct the grouped entity metadata.
|
|
*/
|
|
private function partitionActivities(array $activities, \Carbon\CarbonInterface $readAt, $entity, string $type, string $url, array &$groupedEntities): 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);
|
|
|
|
$groupedEntities[] = [
|
|
'id' => $entity->id,
|
|
'name' => $entity->name,
|
|
'type' => Str::afterLast($type, '\\'),
|
|
'url' => $url,
|
|
'unread_count' => count($unread),
|
|
'context_activities' => $context,
|
|
'new_activities' => $unread,
|
|
];
|
|
}
|
|
}
|
|
|
|
private function getUrlForEntity($entity): string
|
|
{
|
|
if ($entity instanceof Dynamic) {
|
|
return route('dynamics.show', $entity->id);
|
|
}
|
|
|
|
if ($entity instanceof Ledger) {
|
|
return route('dynamics.ledgers.show', [$entity->dynamic_id, $entity->id]);
|
|
}
|
|
|
|
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 '';
|
|
}
|
|
} |