419 lines
13 KiB
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)
|
|
}
|