narasiahli-be/app/module/chat/service/chat.service.go

419 lines
13 KiB
Go

package service
import (
"errors"
"narasi-ahli-be/app/database/entity"
"narasi-ahli-be/app/module/chat/mapper"
"narasi-ahli-be/app/module/chat/repository"
"narasi-ahli-be/app/module/chat/request"
"narasi-ahli-be/app/module/chat/response"
usersRepository "narasi-ahli-be/app/module/users/repository"
"narasi-ahli-be/utils/paginator"
utilSvc "narasi-ahli-be/utils/service"
"time"
"github.com/rs/zerolog"
)
type chatService struct {
Repo repository.ChatRepository
UsersRepo usersRepository.UsersRepository
Log zerolog.Logger
}
type ChatService interface {
// Chat Session methods
GetAllChatSessions(authToken string, req request.ChatSessionQueryRequest) (chatSessions []*response.ChatSessionResponse, paging paginator.Pagination, err error)
GetChatSessionByID(authToken string, id uint) (chatSession *response.ChatSessionResponse, err error)
CreateChatSession(authToken string, req request.ChatSessionCreateRequest) (chatSession *response.ChatSessionResponse, err error)
UpdateChatSession(authToken string, id uint, req request.ChatSessionUpdateRequest) (err error)
DeleteChatSession(authToken string, id uint) error
// Chat Message methods
GetAllChatMessages(authToken string, req request.ChatMessageQueryRequest) (chatMessages []*response.ChatMessageResponse, paging paginator.Pagination, err error)
GetChatMessageByID(authToken string, id uint) (chatMessage *response.ChatMessageResponse, err error)
CreateChatMessage(authToken string, req request.ChatMessageCreateRequest) (chatMessage *response.ChatMessageResponse, err error)
UpdateChatMessage(authToken string, id uint, req request.ChatMessageUpdateRequest) (err error)
DeleteChatMessage(authToken string, id uint) error
// Chat Participant methods
AddParticipantToChat(authToken string, chatSessionID uint, participantUserID uint) error
RemoveParticipantFromChat(authToken string, chatSessionID uint, participantUserID uint) error
}
func NewChatService(repo repository.ChatRepository, usersRepo usersRepository.UsersRepository, log zerolog.Logger) ChatService {
return &chatService{
Repo: repo,
UsersRepo: usersRepo,
Log: log,
}
}
// Chat Session Service Methods
func (_i *chatService) GetAllChatSessions(authToken string, req request.ChatSessionQueryRequest) (chatSessions []*response.ChatSessionResponse, paging paginator.Pagination, err error) {
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if userInfo == nil {
return nil, paginator.Pagination{}, errors.New("user not found")
}
results, paging, err := _i.Repo.GetAllChatSessions(userInfo.ID, req)
if err != nil {
return
}
for _, result := range results {
chatSessions = append(chatSessions, mapper.ChatSessionResponseMapper(result))
}
return
}
func (_i *chatService) GetChatSessionByID(authToken string, id uint) (chatSession *response.ChatSessionResponse, err error) {
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if userInfo == nil {
return nil, errors.New("user not found")
}
result, err := _i.Repo.FindChatSessionByUserAndID(userInfo.ID, id)
if err != nil {
return nil, err
}
if result == nil {
return nil, errors.New("chat session not found or access denied")
}
return mapper.ChatSessionResponseMapper(result), nil
}
func (_i *chatService) CreateChatSession(authToken string, req request.ChatSessionCreateRequest) (chatSession *response.ChatSessionResponse, err error) {
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if userInfo == nil {
return nil, errors.New("user not found")
}
_i.Log.Info().Interface("data", req).Msg("Creating chat session")
// Validate business rules
if req.Type == "personal" && len(req.UserIDs) != 1 {
return nil, errors.New("personal chat must have exactly one other participant")
}
if req.Type == "group" && len(req.UserIDs) < 1 {
return nil, errors.New("group chat must have at least one participant")
}
// Check if personal chat already exists
if req.Type == "personal" {
existingChat, err := _i.Repo.FindPersonalChatSession(userInfo.ID, req.UserIDs[0])
if err == nil && existingChat != nil {
return mapper.ChatSessionResponseMapper(existingChat), nil
}
}
// Validate all user IDs exist
for _, userID := range req.UserIDs {
user, err := _i.UsersRepo.FindOne(userID)
if err != nil || user == nil {
return nil, errors.New("invalid user ID: " + string(rune(userID)))
}
}
// Create chat session
entity := req.ToEntity(userInfo.ID)
result, err := _i.Repo.CreateChatSession(entity)
if err != nil {
return nil, err
}
// Add creator as participant
creatorParticipant := &request.ChatParticipantCreateRequest{
ChatSessionID: result.ID,
UserID: userInfo.ID,
}
_, err = _i.Repo.CreateChatParticipant(creatorParticipant.ToEntity())
if err != nil {
return nil, err
}
// Add other participants
for _, userID := range req.UserIDs {
participant := &request.ChatParticipantCreateRequest{
ChatSessionID: result.ID,
UserID: userID,
}
_, err = _i.Repo.CreateChatParticipant(participant.ToEntity())
if err != nil {
return nil, err
}
}
// Reload with all relationships
finalResult, err := _i.Repo.FindChatSessionByID(result.ID)
if err != nil {
return nil, err
}
return mapper.ChatSessionResponseMapper(finalResult), nil
}
func (_i *chatService) UpdateChatSession(authToken string, id uint, req request.ChatSessionUpdateRequest) (err error) {
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if userInfo == nil {
return errors.New("user not found")
}
_i.Log.Info().Interface("data", req).Msg("Updating chat session")
// Check if chat session exists and user has access
existing, err := _i.Repo.FindChatSessionByUserAndID(userInfo.ID, id)
if err != nil {
return err
}
if existing == nil {
return errors.New("chat session not found or access denied")
}
// Only creator can update chat session
if existing.CreatedBy != userInfo.ID {
return errors.New("only chat creator can update chat session")
}
entity := req.ToEntity()
return _i.Repo.UpdateChatSession(id, entity)
}
func (_i *chatService) DeleteChatSession(authToken string, id uint) error {
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if userInfo == nil {
return errors.New("user not found")
}
_i.Log.Info().Uint("userId", userInfo.ID).Uint("id", id).Msg("Deleting chat session")
// Check if chat session exists and user has access
existing, err := _i.Repo.FindChatSessionByUserAndID(userInfo.ID, id)
if err != nil {
return err
}
if existing == nil {
return errors.New("chat session not found or access denied")
}
// Only creator can delete chat session
if existing.CreatedBy != userInfo.ID {
return errors.New("only chat creator can delete chat session")
}
return _i.Repo.DeleteChatSession(id)
}
// Chat Message Service Methods
func (_i *chatService) GetAllChatMessages(authToken string, req request.ChatMessageQueryRequest) (chatMessages []*response.ChatMessageResponse, paging paginator.Pagination, err error) {
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if userInfo == nil {
return nil, paginator.Pagination{}, errors.New("user not found")
}
// Set user ID in request for repository
req.UserID = userInfo.ID
results, paging, err := _i.Repo.GetAllChatMessages(req)
if err != nil {
return
}
for _, result := range results {
chatMessages = append(chatMessages, mapper.ChatMessageResponseMapper(result))
}
return
}
func (_i *chatService) GetChatMessageByID(authToken string, id uint) (chatMessage *response.ChatMessageResponse, err error) {
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if userInfo == nil {
return nil, errors.New("user not found")
}
result, err := _i.Repo.FindChatMessageByUserAndID(userInfo.ID, id)
if err != nil {
return nil, err
}
if result == nil {
return nil, errors.New("chat message not found or access denied")
}
return mapper.ChatMessageResponseMapper(result), nil
}
func (_i *chatService) CreateChatMessage(authToken string, req request.ChatMessageCreateRequest) (chatMessage *response.ChatMessageResponse, err error) {
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if userInfo == nil {
return nil, errors.New("user not found")
}
_i.Log.Info().Interface("data", req).Msg("Creating chat message")
// Check if user is participant in the chat session
isParticipant, err := _i.Repo.CheckUserInChatSession(userInfo.ID, req.ChatSessionID)
if err != nil {
return nil, err
}
if !isParticipant {
return nil, errors.New("user is not a participant in this chat session")
}
entity := req.ToEntity(userInfo.ID)
result, err := _i.Repo.CreateChatMessage(entity)
if err != nil {
return nil, err
}
return mapper.ChatMessageResponseMapper(result), nil
}
func (_i *chatService) UpdateChatMessage(authToken string, id uint, req request.ChatMessageUpdateRequest) (err error) {
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if userInfo == nil {
return errors.New("user not found")
}
_i.Log.Info().Interface("data", req).Msg("Updating chat message")
// Check if message exists and user has access
existing, err := _i.Repo.FindChatMessageByUserAndID(userInfo.ID, id)
if err != nil {
return err
}
if existing == nil {
return errors.New("chat message not found or access denied")
}
// Only sender can update message
if existing.SenderID != userInfo.ID {
return errors.New("only message sender can update message")
}
now := time.Now()
entity := req.ToEntity()
entity.EditedAt = &now
return _i.Repo.UpdateChatMessage(id, entity)
}
func (_i *chatService) DeleteChatMessage(authToken string, id uint) error {
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if userInfo == nil {
return errors.New("user not found")
}
_i.Log.Info().Uint("userId", userInfo.ID).Uint("id", id).Msg("Deleting chat message")
// Check if message exists and user has access
existing, err := _i.Repo.FindChatMessageByUserAndID(userInfo.ID, id)
if err != nil {
return err
}
if existing == nil {
return errors.New("chat message not found or access denied")
}
// Only sender can delete message
if existing.SenderID != userInfo.ID {
return errors.New("only message sender can delete message")
}
return _i.Repo.DeleteChatMessage(id)
}
// Chat Participant Service Methods
func (_i *chatService) AddParticipantToChat(authToken string, chatSessionID uint, participantUserID uint) error {
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if userInfo == nil {
return errors.New("user not found")
}
_i.Log.Info().Uint("userId", userInfo.ID).Uint("chatSessionID", chatSessionID).Uint("participantUserID", participantUserID).Msg("Adding participant to chat")
// Check if user has access to chat session
existing, err := _i.Repo.FindChatSessionByUserAndID(userInfo.ID, chatSessionID)
if err != nil {
return err
}
if existing == nil {
return errors.New("chat session not found or access denied")
}
// Only creator can add participants
if existing.CreatedBy != userInfo.ID {
return errors.New("only chat creator can add participants")
}
// Validate participant user exists
user, err := _i.UsersRepo.FindOne(participantUserID)
if err != nil || user == nil {
return errors.New("invalid user ID")
}
// Check if user is already a participant
isParticipant, err := _i.Repo.CheckUserInChatSession(participantUserID, chatSessionID)
if err != nil {
return err
}
if isParticipant {
return errors.New("user is already a participant in this chat")
}
participant := &request.ChatParticipantCreateRequest{
ChatSessionID: chatSessionID,
UserID: participantUserID,
}
_, err = _i.Repo.CreateChatParticipant(participant.ToEntity())
return err
}
func (_i *chatService) RemoveParticipantFromChat(authToken string, chatSessionID uint, participantUserID uint) error {
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if userInfo == nil {
return errors.New("user not found")
}
_i.Log.Info().Uint("userId", userInfo.ID).Uint("chatSessionID", chatSessionID).Uint("participantUserID", participantUserID).Msg("Removing participant from chat")
// Check if user has access to chat session
existing, err := _i.Repo.FindChatSessionByUserAndID(userInfo.ID, chatSessionID)
if err != nil {
return err
}
if existing == nil {
return errors.New("chat session not found or access denied")
}
// Only creator can remove participants (or user can remove themselves)
if existing.CreatedBy != userInfo.ID && userInfo.ID != participantUserID {
return errors.New("only chat creator can remove participants or user can remove themselves")
}
// Find participant
participants, err := _i.Repo.FindChatParticipantsBySessionID(chatSessionID)
if err != nil {
return err
}
var participantToRemove *entity.ChatParticipants
for _, participant := range participants {
if participant.UserID == participantUserID {
participantToRemove = participant
break
}
}
if participantToRemove == nil {
return errors.New("participant not found")
}
// Soft delete by setting is_active to false
now := time.Now()
participantToRemove.IsActive = false
participantToRemove.LeftAt = &now
return _i.Repo.UpdateChatParticipant(participantToRemove.ID, participantToRemove)
}