Files
HousingManagement-server/src/services/ChatService.cpp

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;
}