add chat system
This commit is contained in:
parent
6a4254050d
commit
532ca041f6
40
hesabixCore/migrations/Version20241220000000.php
Normal file
40
hesabixCore/migrations/Version20241220000000.php
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace DoctrineMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-generated Migration: Please modify to your needs!
|
||||||
|
*/
|
||||||
|
final class Version20241220000000 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return 'Add memberCount field to chat_channel table';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
// Add memberCount column to chat_channel table
|
||||||
|
$this->addSql('ALTER TABLE chat_channel ADD member_count INT NOT NULL DEFAULT 0');
|
||||||
|
|
||||||
|
// Update existing channels with correct member count
|
||||||
|
$this->addSql('
|
||||||
|
UPDATE chat_channel c
|
||||||
|
SET member_count = (
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM chat_channel_member m
|
||||||
|
WHERE m.channel_id = c.id AND m.is_active = 1
|
||||||
|
)
|
||||||
|
');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('ALTER TABLE chat_channel DROP member_count');
|
||||||
|
}
|
||||||
|
}
|
|
@ -54,6 +54,7 @@ class ChatController extends AbstractController
|
||||||
'isPublic' => $channel->isPublic(),
|
'isPublic' => $channel->isPublic(),
|
||||||
'avatar' => $channel->getAvatar(),
|
'avatar' => $channel->getAvatar(),
|
||||||
'messageCount' => $channel->getMessageCount(),
|
'messageCount' => $channel->getMessageCount(),
|
||||||
|
'memberCount' => $channel->getMemberCount(),
|
||||||
'lastMessageAt' => $channel->getLastMessageAt()?->format('Y-m-d H:i:s'),
|
'lastMessageAt' => $channel->getLastMessageAt()?->format('Y-m-d H:i:s'),
|
||||||
'createdAt' => $channel->getCreatedAt()->format('Y-m-d H:i:s'),
|
'createdAt' => $channel->getCreatedAt()->format('Y-m-d H:i:s'),
|
||||||
'isAdmin' => $this->chatService->isUserAdmin($channel, $user),
|
'isAdmin' => $this->chatService->isUserAdmin($channel, $user),
|
||||||
|
@ -128,7 +129,7 @@ class ChatController extends AbstractController
|
||||||
'description' => $channel->getDescription(),
|
'description' => $channel->getDescription(),
|
||||||
'isPublic' => $channel->isPublic(),
|
'isPublic' => $channel->isPublic(),
|
||||||
'messageCount' => $channel->getMessageCount(),
|
'messageCount' => $channel->getMessageCount(),
|
||||||
'memberCount' => $this->chatService->getChannelStats($channel)['memberCount'],
|
'memberCount' => $channel->getMemberCount(),
|
||||||
'lastMessageAt' => $channel->getLastMessageAt()?->format('Y-m-d H:i:s'),
|
'lastMessageAt' => $channel->getLastMessageAt()?->format('Y-m-d H:i:s'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -387,10 +388,13 @@ class ChatController extends AbstractController
|
||||||
], Response::HTTP_FORBIDDEN);
|
], Response::HTTP_FORBIDDEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
$limit = (int) $request->query->get('limit', 50);
|
$limit = (int) $request->query->get('limit', 30);
|
||||||
$offset = (int) $request->query->get('offset', 0);
|
$offset = (int) $request->query->get('offset', 0);
|
||||||
|
|
||||||
$messages = $this->chatService->getChannelMessages($channel, $limit, $offset);
|
$messages = $this->chatService->getChannelMessages($channel, $limit, $offset);
|
||||||
|
|
||||||
|
// Get total message count for pagination info
|
||||||
|
$totalMessages = $this->chatService->getChannelMessageCount($channel);
|
||||||
|
|
||||||
$data = [];
|
$data = [];
|
||||||
foreach ($messages as $message) {
|
foreach ($messages as $message) {
|
||||||
|
@ -418,7 +422,13 @@ class ChatController extends AbstractController
|
||||||
|
|
||||||
return $this->json([
|
return $this->json([
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'data' => $data
|
'data' => $data,
|
||||||
|
'pagination' => [
|
||||||
|
'limit' => $limit,
|
||||||
|
'offset' => $offset,
|
||||||
|
'total' => $totalMessages,
|
||||||
|
'hasMore' => ($offset + $limit) < $totalMessages
|
||||||
|
]
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -478,6 +488,11 @@ class ChatController extends AbstractController
|
||||||
'id' => $message->getSender()->getId(),
|
'id' => $message->getSender()->getId(),
|
||||||
'fullName' => $message->getSender()->getFullName(),
|
'fullName' => $message->getSender()->getFullName(),
|
||||||
],
|
],
|
||||||
|
'quotedMessage' => $message->getQuotedMessage() ? [
|
||||||
|
'id' => $message->getQuotedMessage()->getId(),
|
||||||
|
'content' => $message->getQuotedMessage()->getContent(),
|
||||||
|
'sender' => $message->getQuotedMessage()->getSender()->getFullName(),
|
||||||
|
] : null,
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
|
@ -564,6 +579,44 @@ class ChatController extends AbstractController
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Route('/messages/{messageId}/reactions', name: 'chat_remove_reaction', methods: ['DELETE'])]
|
||||||
|
public function removeReaction(int $messageId, Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$message = $this->messageRepository->find($messageId);
|
||||||
|
if (!$message) {
|
||||||
|
return $this->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'پیام یافت نشد'
|
||||||
|
], Response::HTTP_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = json_decode($request->getContent(), true);
|
||||||
|
|
||||||
|
if (!isset($data['emoji']) || empty($data['emoji'])) {
|
||||||
|
return $this->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'ایموجی الزامی است'
|
||||||
|
], Response::HTTP_BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var User $user */
|
||||||
|
$user = $this->security->getUser();
|
||||||
|
|
||||||
|
$success = $this->chatService->removeReaction($message, $user, $data['emoji']);
|
||||||
|
|
||||||
|
if ($success) {
|
||||||
|
return $this->json([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'واکنش حذف شد'
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
return $this->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'خطا در حذف واکنش'
|
||||||
|
], Response::HTTP_BAD_REQUEST);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[Route('/users/search', name: 'chat_search_users', methods: ['GET'])]
|
#[Route('/users/search', name: 'chat_search_users', methods: ['GET'])]
|
||||||
public function searchUsers(Request $request): JsonResponse
|
public function searchUsers(Request $request): JsonResponse
|
||||||
{
|
{
|
||||||
|
|
|
@ -52,6 +52,9 @@ class ChatChannel
|
||||||
#[ORM\Column]
|
#[ORM\Column]
|
||||||
private int $messageCount = 0;
|
private int $messageCount = 0;
|
||||||
|
|
||||||
|
#[ORM\Column]
|
||||||
|
private int $memberCount = 0;
|
||||||
|
|
||||||
#[ORM\Column(nullable: true)]
|
#[ORM\Column(nullable: true)]
|
||||||
private ?\DateTimeImmutable $lastMessageAt = null;
|
private ?\DateTimeImmutable $lastMessageAt = null;
|
||||||
|
|
||||||
|
@ -236,6 +239,17 @@ class ChatChannel
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getMemberCount(): int
|
||||||
|
{
|
||||||
|
return $this->memberCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setMemberCount(int $memberCount): static
|
||||||
|
{
|
||||||
|
$this->memberCount = $memberCount;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
public function getLastMessageAt(): ?\DateTimeImmutable
|
public function getLastMessageAt(): ?\DateTimeImmutable
|
||||||
{
|
{
|
||||||
return $this->lastMessageAt;
|
return $this->lastMessageAt;
|
||||||
|
|
|
@ -130,13 +130,28 @@ class ChatMessageRepository extends ServiceEntityRepository
|
||||||
->getResult();
|
->getResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get total message count for a channel
|
||||||
|
*/
|
||||||
|
public function getChannelMessageCount(ChatChannel $channel): int
|
||||||
|
{
|
||||||
|
return $this->createQueryBuilder('m')
|
||||||
|
->select('COUNT(m.id)')
|
||||||
|
->where('m.channel = :channel')
|
||||||
|
->andWhere('m.isDeleted = :isDeleted')
|
||||||
|
->setParameter('channel', $channel)
|
||||||
|
->setParameter('isDeleted', false)
|
||||||
|
->getQuery()
|
||||||
|
->getSingleScalarResult();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get message statistics for a channel
|
* Get message statistics for a channel
|
||||||
*/
|
*/
|
||||||
public function getChannelMessageStats(ChatChannel $channel): array
|
public function getChannelMessageStats(ChatChannel $channel): array
|
||||||
{
|
{
|
||||||
$qb = $this->createQueryBuilder('m')
|
$qb = $this->createQueryBuilder('m')
|
||||||
->select('COUNT(m.id) as total, COUNT(CASE WHEN m.messageType = :emoji THEN 1 END) as emoji_count')
|
->select('COUNT(m.id) as total, SUM(CASE WHEN m.messageType = :emoji THEN 1 ELSE 0 END) as emoji_count')
|
||||||
->where('m.channel = :channel')
|
->where('m.channel = :channel')
|
||||||
->andWhere('m.isDeleted = :isDeleted')
|
->andWhere('m.isDeleted = :isDeleted')
|
||||||
->setParameter('channel', $channel)
|
->setParameter('channel', $channel)
|
||||||
|
|
|
@ -34,6 +34,7 @@ class ChatService
|
||||||
$channel->setDescription($description);
|
$channel->setDescription($description);
|
||||||
$channel->setIsPublic($isPublic);
|
$channel->setIsPublic($isPublic);
|
||||||
$channel->setCreatedBy($creator);
|
$channel->setCreatedBy($creator);
|
||||||
|
$channel->setMemberCount(1); // Creator is the first member
|
||||||
|
|
||||||
// Add creator as admin member
|
// Add creator as admin member
|
||||||
$member = new ChatChannelMember();
|
$member = new ChatChannelMember();
|
||||||
|
@ -72,6 +73,10 @@ class ChatService
|
||||||
$member->setIsAdmin(false);
|
$member->setIsAdmin(false);
|
||||||
|
|
||||||
$this->entityManager->persist($member);
|
$this->entityManager->persist($member);
|
||||||
|
|
||||||
|
// Update channel member count
|
||||||
|
$channel->setMemberCount($channel->getMemberCount() + 1);
|
||||||
|
|
||||||
$this->entityManager->flush();
|
$this->entityManager->flush();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -88,6 +93,10 @@ class ChatService
|
||||||
}
|
}
|
||||||
|
|
||||||
$member->setIsActive(false);
|
$member->setIsActive(false);
|
||||||
|
|
||||||
|
// Update channel member count
|
||||||
|
$channel->setMemberCount($channel->getMemberCount() - 1);
|
||||||
|
|
||||||
$this->entityManager->flush();
|
$this->entityManager->flush();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -120,6 +129,10 @@ class ChatService
|
||||||
}
|
}
|
||||||
|
|
||||||
$member->setIsActive(false);
|
$member->setIsActive(false);
|
||||||
|
|
||||||
|
// Update channel member count
|
||||||
|
$channel->setMemberCount($channel->getMemberCount() - 1);
|
||||||
|
|
||||||
$this->entityManager->flush();
|
$this->entityManager->flush();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -258,6 +271,14 @@ class ChatService
|
||||||
return $this->messageRepository->findChannelMessages($channel, $limit, $offset);
|
return $this->messageRepository->findChannelMessages($channel, $limit, $offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get total message count for a channel
|
||||||
|
*/
|
||||||
|
public function getChannelMessageCount(ChatChannel $channel): int
|
||||||
|
{
|
||||||
|
return $this->messageRepository->getChannelMessageCount($channel);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Search public channels
|
* Search public channels
|
||||||
*/
|
*/
|
||||||
|
@ -279,7 +300,10 @@ class ChatService
|
||||||
*/
|
*/
|
||||||
public function getChannelStats(ChatChannel $channel): array
|
public function getChannelStats(ChatChannel $channel): array
|
||||||
{
|
{
|
||||||
return $this->channelRepository->getChannelStats($channel);
|
return [
|
||||||
|
'memberCount' => $channel->getMemberCount(),
|
||||||
|
'messageCount' => $channel->getMessageCount()
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue