/app/Http/Controllers/ChatsController.php
PHP | 425 lines | 336 code | 79 blank | 10 comment | 26 complexity | 14d28f12cb272affc324b01d0a43ee5c MD5 | raw file
- <?php
- declare(strict_types=1);
- namespace App\Http\Controllers;
- use App\Events\ChatFinished;
- use App\Events\MessageStatusChanged;
- use App\Models\Chat;
- use App\Models\ChatMessage;
- use App\Models\CoinTransaction;
- use App\Models\ContactsRequest;
- use App\Models\Notification;
- use App\Models\User;
- use App\Services\Chat\ChatElementsParameters;
- use App\Services\Chat\ChatsService;
- use App\Services\Chat\ContactsRequestService;
- use App\Services\Coins\Coins;
- use App\Services\Coins\CoinTransactionDto;
- use Carbon\Carbon;
- use DB;
- use Illuminate\Http\Request;
- use Throwable;
- class ChatsController extends Controller
- {
- public function __construct()
- {
- $this->middleware('auth');
- }
- public function chats(Request $request): array
- {
- $this->validate($request, [
- 'after_time' => ['string', 'date_format:' . DATE_TIME_WITH_MICROSECONDS_AND_TIME_ZONE_FORMAT],
- ]);
- $afterTime = $request->get('after_time');
- return ChatsService::getChats(current_user()->id, $afterTime, ChatsService::VIEW_TYPE_USER);
- }
- public function chatElements(Request $request): array
- {
- $this->validate($request, [
- 'chat_id' => ['required', 'integer'],
- 'after_time' => ['string', 'date_format:' . DATE_TIME_WITH_MICROSECONDS_AND_TIME_ZONE_FORMAT],
- 'before_time' => ['string', 'date_format:' . DATE_TIME_WITH_MICROSECONDS_AND_TIME_ZONE_FORMAT],
- ]);
- $chatId = (int) $request->get('chat_id');
- return ChatsService::getElements(new ChatElementsParameters([
- 'chatId' => $chatId,
- 'userId' => current_user()->id,
- 'respectMessageStatus' => true,
- 'afterTime' => $request->get('after_time'),
- 'beforeTime' => $request->get('before_time'),
- ]));
- }
- public function sendMessage(Request $request, Coins $coins): void
- {
- $this->validate($request, [
- 'chat_id' => ['required', 'integer'],
- 'text' => ['required', 'string', 'min:1', 'max:' . config('chats.max_message_length')],
- 'replied_message_id' => 'exists:chat_messages,id',
- ]);
- // don't allow to send messages if the current user profile is not accepted
- if (current_user()->status !== User::STATUS_ACCEPTED) {
- abort(403, __('errors.your-profile-is-not-accepted'));
- }
- $chat = (new Chat())
- ->whereActive()
- ->find($request->get('chat_id'), [
- 'id', 'requestor_id', 'target_user_id', 'last_activity_at',
- ])
- ;
- if ($chat === null) {
- abort(403);
- }
- // forbid sending messages to an arbitrary chat
- if ($chat->requestor_id !== current_user()->id && $chat->target_user_id !== current_user()->id) {
- abort(403);
- }
- $peerId = $chat->requestor_id === current_user()->id ? $chat->target_user_id : $chat->requestor_id;
- $peer = (new User())->find($peerId, ['status', 'ban_id']);
- if ($peer->status === User::STATUS_BANNED) {
- $isTemporaryBan = $peer->ban()->value('unban_at') !== null;
- abort(403, $isTemporaryBan ? __('profile:errors.user-temporary-banned') : __('profile:errors.user-banned'));
- }
- if ($peer->status === User::STATUS_DEACTIVATED) {
- abort(403, __('profile:errors.user-deactivated'));
- }
- if ($peer->status === User::STATUS_PENDING_DELETION || $peer->status === User::STATUS_DELETED) {
- abort(403, __('profile:errors.user-deleted'));
- }
- $repliedMessageId = $request->get('replied_message_id');
- $repliedMessageText = null;
- if ($repliedMessageId !== null) {
- $repliedMessageText = (new ChatMessage())->findOrFail($repliedMessageId, ['text'])->text;
- }
- DB::beginTransaction();
- try {
- $coinTransactionDto = (new CoinTransactionDto())
- ->setType(CoinTransaction::TYPE_CHAT_MESSAGES)
- ->setAmount(-1)
- ->setUserId(current_user()->id)
- ->setDescription(sprintf('Chat messages'))
- ;
- $transaction = (new CoinTransaction())
- ->where('type', CoinTransaction::TYPE_CHAT_MESSAGES)
- ->where('user_id', current_user()->id)
- ->orderBy('transaction_time', 'desc')
- ->first()
- ;
- if ($transaction === null) {
- $transaction = $coins->changeBalance($coinTransactionDto);
- } else {
- $transactionMessageCount = (new ChatMessage())
- ->where('transaction_id', $transaction->id)
- ->count()
- ;
- $messageCountPerCoin = config('chats.message_count_per_coin');
- // if the count of messages paid by the last transaction reached the specified number
- // the create a new one
- if ($transactionMessageCount >= $messageCountPerCoin) {
- $transaction = $coins->changeBalance($coinTransactionDto);
- }
- }
- $currentTime = Carbon::now();
- $message = (new ChatMessage())->create([
- 'chat_id' => $chat->id,
- 'user_id' => current_user()->id,
- 'peer_id' => $peerId,
- 'replied_message_id' => $repliedMessageId,
- 'replied_message_text' => $repliedMessageText,
- 'text' => $request->get('text'),
- 'sent_at' => $currentTime->format(DATE_TIME_WITH_MICROSECONDS_AND_TIME_ZONE_FORMAT),
- 'status' => ChatMessage::STATUS_PENDING_MODERATION,
- 'status_updated_at' => $currentTime->format(DATE_TIME_WITH_MICROSECONDS_AND_TIME_ZONE_FORMAT),
- 'transaction_id' => $transaction->id,
- ]);
- ChatsService::updateChatLastActivityTime($chat->id, $chat->last_activity_at, $currentTime);
- DB::commit();
- } catch (Throwable $exception) {
- DB::rollBack();
- throw $exception;
- }
- }
- public function cancelMessage(Request $request): void
- {
- $this->validate($request, [
- 'message_id' => ['required', 'integer'],
- ]);
- $message = (new ChatMessage())
- ->find($request->get('message_id'), [
- 'id',
- 'chat_id',
- 'user_id',
- 'status',
- 'status_updated_at',
- 'seen_at',
- ])
- ;
- if ($message === null) {
- abort(403);
- }
- // forbid cancelling an arbitrary message
- if ($message->user_id !== current_user()->id) {
- abort(403);
- }
- // forbid cancelling a rejected message, a message that already canceled
- // or if a message already seen by a peer
- if (
- in_array($message->status, [
- ChatMessage::STATUS_REJECTED_BY_MODERATOR,
- ChatMessage::STATUS_CANCELED,
- ], true) || $message->seen_at !== null
- ) {
- abort(403);
- }
- DB::beginTransaction();
- try {
- $currentTime = Carbon::now();
- $updatedMessageCount = (new ChatMessage())
- ->where('id', $message->id)
- ->where('status_updated_at', $message->status_updated_at->format(DATE_TIME_WITH_MICROSECONDS_AND_TIME_ZONE_FORMAT))
- ->update([
- 'status' => ChatMessage::STATUS_CANCELED,
- 'status_updated_at' => $currentTime->format(DATE_TIME_WITH_MICROSECONDS_AND_TIME_ZONE_FORMAT),
- ])
- ;
- // if the status of the message was already changed by someone else
- if ($updatedMessageCount === 0) {
- abort(403);
- }
- event(new MessageStatusChanged($message->id, $currentTime));
- DB::commit();
- } catch (Throwable $exception) {
- DB::rollBack();
- throw $exception;
- }
- }
- public function markAsRead(Request $request): void
- {
- $this->validate($request, [
- 'chat_id' => ['required', 'integer'],
- ]);
- $chat = (new Chat())
- ->whereActive()
- ->find($request->get('chat_id'), [
- 'id', 'requestor_id', 'target_user_id',
- ])
- ;
- if ($chat === null) {
- abort(403);
- }
- // forbid access to an arbitrary chat
- if ($chat->requestor_id !== current_user()->id && $chat->target_user_id !== current_user()->id) {
- abort(403);
- }
- DB::beginTransaction();
- try {
- $currentTime = Carbon::now();
- $updatedRowCount = (new ChatMessage())
- ->where('chat_id', $chat->id)
- ->where('user_id', '!=', current_user()->id)
- ->whereIn('status', [
- ChatMessage::STATUS_ACCEPTED,
- ChatMessage::STATUS_REJECTED_BY_MODERATOR,
- ])
- ->whereNull('seen_at')
- ->update([
- 'seen_at' => $currentTime->format(DATE_TIME_WITH_TIME_ZONE_FORMAT),
- ])
- ;
- if (current_user()->sex === User::SEX_MALE) {
- $updatedRowCount += (new ContactsRequest())
- ->where('chat_id', $chat->id)
- ->where('requestor_id', current_user()->id)
- ->whereIn('status', [ContactsRequest::STATUS_ACCEPTED, ContactsRequest::STATUS_REJECTED])
- ->whereNull('answer_seen_at')
- ->update([
- 'answer_seen_at' => $currentTime->format(DATE_TIME_WITH_TIME_ZONE_FORMAT),
- ])
- ;
- } else {
- $updatedRowCount += (new ContactsRequest())
- ->where('chat_id', $chat->id)
- ->where('target_user_id', current_user()->id)
- ->whereNull('seen_at')
- ->update([
- 'seen_at' => $currentTime->format(DATE_TIME_WITH_TIME_ZONE_FORMAT),
- ])
- ;
- }
- if ($updatedRowCount > 0) {
- ChatsService::updateChatLastActivityTime($chat->id, $chat->last_activity_at, $currentTime);
- cache_info()->newMessageCount(current_user()->id)->forget();
- }
- DB::commit();
- } catch (Throwable $exception) {
- DB::rollBack();
- throw $exception;
- }
- }
- public function finishChat(Request $request): void
- {
- $this->validate($request, [
- 'chat_id' => ['required', 'integer'],
- ]);
- $chat = (new Chat())
- ->with('chatRequest:id,chat_id,transaction_id')
- ->with('chatRequest.transaction:id,amount')
- ->whereActive()
- ->find($request->get('chat_id'), [
- 'id',
- 'chat_request_id',
- 'requestor_id',
- 'target_user_id',
- ])
- ;
- if ($chat === null) {
- abort(403);
- }
- // forbid access to an arbitrary chat
- if ($chat->requestor_id !== current_user()->id && $chat->target_user_id !== current_user()->id) {
- abort(403);
- }
- DB::beginTransaction();
- try {
- $currentTime = Carbon::now();
- $chat->update([
- 'finished_at' => $currentTime->format(DATE_TIME_WITH_MICROSECONDS_AND_TIME_ZONE_FORMAT),
- 'finished_by' => current_user()->id,
- 'is_finished_automatically' => false,
- 'moderation_moderator_id' => null,
- 'moderation_claimed_at' => null,
- 'moderation_forwarded_to' => null,
- 'moderation_forwarded_by' => null,
- 'moderation_forwarded_at' => null,
- ]);
- event(new ChatFinished($chat->id));
- $peerId = $chat->requestor_id === current_user()->id ? $chat->target_user_id : $chat->requestor_id;
- (new Notification())->create([
- 'user_id' => $peerId,
- 'type' => Notification::TYPE_CHAT_FINISHED,
- 'title' => 'notifications:chat-finished.title',
- 'body' => 'notifications:chat-finished.body',
- 'parameters' => [
- 'peerId' => current_user()->id,
- 'peerName' => current_user()->name,
- ],
- 'context' => current_user()->sex === User::SEX_MALE ? 'male' : 'female',
- 'additional_data' => [
- 'chat_id' => $chat->id,
- ],
- 'is_hidden' => false,
- 'created_at' => $currentTime->format(DATE_TIME_WITH_MICROSECONDS_AND_TIME_ZONE_FORMAT),
- ]);
- cache_info()->newMessageCount(current_user()->id)->forget();
- DB::commit();
- } catch (Throwable $exception) {
- DB::rollBack();
- throw $exception;
- }
- }
- public function sendContactsRequest(Request $request, ContactsRequestService $contactsRequests): void
- {
- $this->validate($request, [
- 'chat_id' => ['required', 'integer'],
- ]);
- $contactsRequests->sendRequest(current_user(), $request->get('chat_id'));
- }
- public function cancelContactsRequest(Request $request, ContactsRequestService $contactsRequests): void
- {
- $this->validate($request, [
- 'request_id' => ['required', 'integer'],
- ]);
- $contactsRequests->cancelRequest($request->get('request_id'));
- }
- public function acceptContactsRequest(Request $request, ContactsRequestService $contactsRequests): void
- {
- $this->validate($request, [
- 'request_id' => ['required', 'integer'],
- ]);
- $contactsRequests->answerRequest($request->get('request_id'), true);
- }
- public function rejectContactsRequest(Request $request, ContactsRequestService $contactsRequests): void
- {
- $this->validate($request, [
- 'request_id' => ['required', 'integer'],
- ]);
- $contactsRequests->answerRequest($request->get('request_id'), false);
- }
- }