messages implemented

This commit is contained in:
2026-02-26 21:12:32 +01:00
parent d0aefc5ddc
commit 15b7b77d20
168 changed files with 14728 additions and 6786 deletions

View File

@@ -0,0 +1,144 @@
<?php
namespace App\Services;
use Illuminate\Support\Facades\DB;
/**
* FollowService
*
* Manages follow / unfollow operations on the user_followers table.
* Convention:
* follower_id = the user doing the following
* user_id = the user being followed
*
* Counters in user_statistics are kept in sync atomically inside a transaction.
*/
final class FollowService
{
/**
* Follow $targetId on behalf of $actorId.
*
* @return bool true if a new follow was created, false if already following
*
* @throws \InvalidArgumentException if self-follow attempted
*/
public function follow(int $actorId, int $targetId): bool
{
if ($actorId === $targetId) {
throw new \InvalidArgumentException('Cannot follow yourself.');
}
$inserted = false;
DB::transaction(function () use ($actorId, $targetId, &$inserted) {
$rows = DB::table('user_followers')->insertOrIgnore([
'user_id' => $targetId,
'follower_id' => $actorId,
'created_at' => now(),
]);
if ($rows === 0) {
// Already following nothing to do
return;
}
$inserted = true;
// Increment following_count for actor, followers_count for target
$this->incrementCounter($actorId, 'following_count');
$this->incrementCounter($targetId, 'followers_count');
});
return $inserted;
}
/**
* Unfollow $targetId on behalf of $actorId.
*
* @return bool true if a follow row was removed, false if wasn't following
*/
public function unfollow(int $actorId, int $targetId): bool
{
if ($actorId === $targetId) {
return false;
}
$deleted = false;
DB::transaction(function () use ($actorId, $targetId, &$deleted) {
$rows = DB::table('user_followers')
->where('user_id', $targetId)
->where('follower_id', $actorId)
->delete();
if ($rows === 0) {
return;
}
$deleted = true;
$this->decrementCounter($actorId, 'following_count');
$this->decrementCounter($targetId, 'followers_count');
});
return $deleted;
}
/**
* Toggle follow state. Returns the new following state.
*/
public function toggle(int $actorId, int $targetId): bool
{
if ($this->isFollowing($actorId, $targetId)) {
$this->unfollow($actorId, $targetId);
return false;
}
$this->follow($actorId, $targetId);
return true;
}
public function isFollowing(int $actorId, int $targetId): bool
{
return DB::table('user_followers')
->where('user_id', $targetId)
->where('follower_id', $actorId)
->exists();
}
/**
* Current followers_count for a user (from cached column, not live count).
*/
public function followersCount(int $userId): int
{
return (int) DB::table('user_statistics')
->where('user_id', $userId)
->value('followers_count');
}
// ─── Private helpers ─────────────────────────────────────────────────────
private function incrementCounter(int $userId, string $column): void
{
DB::table('user_statistics')->updateOrInsert(
['user_id' => $userId],
[
$column => DB::raw("COALESCE({$column}, 0) + 1"),
'updated_at' => now(),
'created_at' => now(), // ignored on update
]
);
}
private function decrementCounter(int $userId, string $column): void
{
DB::table('user_statistics')
->where('user_id', $userId)
->where($column, '>', 0)
->update([
$column => DB::raw("{$column} - 1"),
'updated_at' => now(),
]);
}
}