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