288 lines
7.2 KiB
C++
288 lines
7.2 KiB
C++
#include "ChatService.h"
|
|
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <optional>
|
|
#include <drogon/DrClassMap.h>
|
|
#include <drogon/utils/Utilities.h>
|
|
#include "UserManager.h"
|
|
#include "controllers/ChatWebSocketController.h"
|
|
#include "dependency_injection/DependencyContainer.h"
|
|
#include "dto/ChatDto.h"
|
|
#include "dto/MessageDto.h"
|
|
#include "storages/IChatStorage.h"
|
|
|
|
#define STB_IMAGE_IMPLEMENTATION
|
|
#define STB_IMAGE_RESIZE_IMPLEMENTATION
|
|
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
|
#include "stb_image.h"
|
|
#include "stb_image_resize2.h"
|
|
#include "stb_image_write.h"
|
|
|
|
#include "base64.hpp"
|
|
|
|
#include "mp4_placeholder.h"
|
|
|
|
static bool validateFile(std::string fileExtension, const char* fileData, size_t fileLength)
|
|
{
|
|
if (fileExtension == "mp4")
|
|
{
|
|
if (fileLength > 536'870'912)
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
else if (fileExtension == "png")
|
|
{
|
|
if (fileLength > 52'428'800)
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
std::string generateThumbnailBase64(std::string fileExtension, const char* fileData, size_t fileLength)
|
|
{
|
|
if (fileExtension == "mp4")
|
|
{
|
|
std::string rawPlaceholder{ (char*)mp4_placeholder, sizeof(mp4_placeholder)};
|
|
return base64::to_base64(rawPlaceholder);
|
|
}
|
|
else if (fileExtension == "png")
|
|
{
|
|
const int max_dimension = 180;
|
|
|
|
int width, height, channels;
|
|
unsigned char* image_data = stbi_load_from_memory((unsigned char*)fileData, fileLength, &width, &height, &channels, 0);
|
|
if (!image_data)
|
|
{
|
|
return {};
|
|
}
|
|
|
|
int new_width, new_height;
|
|
if (width > height)
|
|
{
|
|
new_width = max_dimension;
|
|
new_height = (int)((float)height * ((float)max_dimension / (float)width));
|
|
}
|
|
else
|
|
{
|
|
new_height = max_dimension;
|
|
new_width = (int)((float)width * ((float)max_dimension / (float)height));
|
|
}
|
|
|
|
unsigned char* resized_data = new unsigned char[new_width * new_height * channels];
|
|
if (!resized_data)
|
|
{
|
|
stbi_image_free(image_data);
|
|
return {};
|
|
}
|
|
|
|
if (!stbir_resize_uint8_linear(image_data, width, height, 0, resized_data, new_width, new_height, 0, STBIR_4CHANNEL))
|
|
{
|
|
stbi_image_free(image_data);
|
|
delete[] resized_data;
|
|
return {};
|
|
}
|
|
|
|
int png_len;
|
|
unsigned char* png_data = stbi_write_png_to_mem(resized_data, new_width * channels,
|
|
new_width, new_height, channels, &png_len);
|
|
|
|
if (png_len <= 0)
|
|
{
|
|
stbi_image_free(image_data);
|
|
delete[] resized_data;
|
|
return {};
|
|
}
|
|
|
|
auto encoded = base64::to_base64(std::string_view((char*)png_data, png_len));
|
|
|
|
stbi_image_free(image_data);
|
|
delete[] resized_data;
|
|
STBI_FREE(png_data);
|
|
|
|
return encoded;
|
|
}
|
|
}
|
|
|
|
std::string getMessageType(const std::string& fileExtension)
|
|
{
|
|
if (fileExtension == "mp4")
|
|
return "VIDEO";
|
|
else
|
|
return "IMAGE";
|
|
}
|
|
|
|
ChatService::ChatService()
|
|
{
|
|
m_logger = DependencyContainer::Resolve<spdlog::logger>();
|
|
m_chatStorage = DependencyContainer::Resolve<IChatStorage>();
|
|
}
|
|
|
|
int ChatService::getChatId(int userId)
|
|
{
|
|
Chat chat = m_chatStorage->getOrCreateChatForUserId(userId);
|
|
return chat.id;
|
|
}
|
|
|
|
std::vector<MessageDto> ChatService::loadMessagesByUserId(int userId, int page, int pageSize)
|
|
{
|
|
std::optional<Chat> chat = m_chatStorage->getChatForUserId(userId);
|
|
if (!chat)
|
|
{
|
|
return {}; // no chat was created yet
|
|
}
|
|
|
|
std::vector<MessageDto> dtos;
|
|
auto models = m_chatStorage->loadMessages(chat.value().id, page, pageSize);
|
|
for (auto& model : models)
|
|
{
|
|
dtos.emplace_back(MessageDto::toDto(std::move(model)));
|
|
}
|
|
return dtos;
|
|
}
|
|
|
|
std::optional<MessageDto> ChatService::postTextMessageByChatId(int senderId, int chatId, std::string contentText)
|
|
{
|
|
Message messageToInsert;
|
|
messageToInsert.chatId = chatId;
|
|
messageToInsert.senderId = senderId;
|
|
messageToInsert.type = "TEXT";
|
|
messageToInsert.contentText = std::move(contentText);
|
|
std::optional<Message> insertedMessage = m_chatStorage->insertMessage(messageToInsert);
|
|
if (insertedMessage)
|
|
{
|
|
auto messageDto = MessageDto::toDto(std::move(insertedMessage.value()));
|
|
|
|
auto chatWsController = drogon::DrClassMap::getSingleInstance<ChatWebSocket>();
|
|
nlohmann::json j = messageDto;
|
|
chatWsController->newMessage(chatId, j.dump());
|
|
|
|
return messageDto;
|
|
}
|
|
|
|
return std::optional<MessageDto>();
|
|
}
|
|
|
|
std::optional<MessageDto> ChatService::postFileMessageByChatId(int senderId, int chatId, std::string fileExtension, const char* fileData, size_t fileLength)
|
|
{
|
|
// process file
|
|
if (!validateFile(fileExtension, fileData, fileLength))
|
|
{
|
|
return {};
|
|
}
|
|
|
|
auto thumbnail = generateThumbnailBase64(fileExtension, fileData, fileLength);
|
|
auto uuidName = drogon::utils::getUuid();
|
|
uuidName.append(".");
|
|
uuidName.append(fileExtension);
|
|
|
|
std::filesystem::path dirPath = std::filesystem::current_path() / "uploads" / std::to_string(chatId);
|
|
if (!std::filesystem::exists(dirPath))
|
|
{
|
|
std::filesystem::create_directories(dirPath);
|
|
}
|
|
|
|
std::filesystem::path filePath = dirPath / uuidName;
|
|
std::ofstream outFile(filePath, std::ios::binary);
|
|
if (!outFile)
|
|
{
|
|
return {};
|
|
}
|
|
outFile.write(fileData, fileLength);
|
|
|
|
Attachment attachmentToInsert;
|
|
attachmentToInsert.type = getMessageType(fileExtension);
|
|
attachmentToInsert.filePath = filePath.string();
|
|
attachmentToInsert.sizeBytes = fileLength;
|
|
|
|
std::optional<Attachment> insertedAttachment = m_chatStorage->insertAttachment(attachmentToInsert);
|
|
|
|
if (!insertedAttachment)
|
|
{
|
|
m_logger->error("Failed to insert attachment of type {} and size {}", attachmentToInsert.type, attachmentToInsert.sizeBytes);
|
|
return {};
|
|
}
|
|
|
|
Message messageToInsert;
|
|
messageToInsert.chatId = chatId;
|
|
messageToInsert.senderId = senderId;
|
|
messageToInsert.type = getMessageType(fileExtension);
|
|
messageToInsert.thumbnail = thumbnail;
|
|
messageToInsert.attachmentId = insertedAttachment.value().id;
|
|
messageToInsert.type = getMessageType(fileExtension);
|
|
|
|
std::optional<Message> insertedMessage = m_chatStorage->insertMessage(messageToInsert);
|
|
|
|
if (!insertedMessage)
|
|
{
|
|
m_logger->error("Failed to insert message with attachment of type {}", insertedMessage.value().type);
|
|
}
|
|
|
|
if (insertedMessage)
|
|
{
|
|
auto messageDto = MessageDto::toDto(std::move(insertedMessage.value()));
|
|
|
|
auto chatWsController = drogon::DrClassMap::getSingleInstance<ChatWebSocket>();
|
|
nlohmann::json j = messageDto;
|
|
chatWsController->newMessage(chatId, j.dump());
|
|
|
|
return messageDto;
|
|
}
|
|
|
|
return std::optional<MessageDto>();
|
|
}
|
|
|
|
std::vector<ChatDto> ChatService::loadChats()
|
|
{
|
|
std::vector<ChatDto> dtos;
|
|
auto models = m_chatStorage->loadChats();
|
|
for (auto& model : models)
|
|
{
|
|
dtos.emplace_back(ChatDto::toDto(std::move(model)));
|
|
}
|
|
return dtos;
|
|
}
|
|
|
|
std::vector<MessageDto> ChatService::loadMessagesByChatId(int chatId, int page, int pageSize)
|
|
{
|
|
std::vector<MessageDto> dtos;
|
|
auto models = m_chatStorage->loadMessages(chatId, page, pageSize);
|
|
for (auto& model : models)
|
|
{
|
|
dtos.emplace_back(MessageDto::toDto(std::move(model)));
|
|
}
|
|
return dtos;
|
|
}
|
|
|
|
std::string ChatService::loadAttachment(int attachmentId)
|
|
{
|
|
auto attachment = m_chatStorage->loadAttachment(attachmentId);
|
|
if (!attachment)
|
|
{
|
|
return {};
|
|
}
|
|
|
|
std::string fullPath = attachment.value().filePath;
|
|
|
|
if (!std::filesystem::exists(fullPath) || std::filesystem::is_directory(fullPath))
|
|
{
|
|
return {};
|
|
}
|
|
|
|
std::ifstream file(fullPath, std::ios::binary);
|
|
if (!file)
|
|
{
|
|
return {};
|
|
}
|
|
|
|
std::string fileContent((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
|
|
file.close();
|
|
|
|
return fileContent;
|
|
}
|