func createChannelMessageList(channelId, messageId int64) *models.ChannelMessageList { cml := models.NewChannelMessageList() cml.ChannelId = channelId cml.MessageId = messageId So(cml.Create(), ShouldBeNil) return cml }
// calculateUnreadItemCount calculates the unread count for given participant in // given channel func (cue *channelUpdatedEvent) calculateUnreadItemCount() (int, error) { if cue.Channel == nil { return 0, models.ErrChannelIsNotSet } // channel type can only be // // PinnedActivity // PrivateMessage // Topic if !cue.Channel.ShowUnreadCount() { return 0, fmt.Errorf("not supported channel type for unread count calculation %+v", cue.Channel.TypeConstant) } // we need channel participant for their latest appearence in regarding channel if cue.ChannelParticipant == nil { return 0, models.ErrChannelParticipantIsNotSet } // Topic channels and private messages have the normal structure, one // channel, many messages, many participants. For them unread count will be // calculated from unread post count whithin a channel, base timestamp here // is persisted in ChannelParticipant table as LastSeenAt timestamp. // // If one message is edited by another user with a new tag, this message // will not be marked as read, because we are not looking to createdAt of // the channel message list, we are taking AddedAt into consideration here if cue.Channel.TypeConstant != models.Channel_TYPE_PINNED_ACTIVITY { return models.NewChannelMessageList().UnreadCount(cue.ChannelParticipant) } // from this point we need parent message if cue.ParentChannelMessage == nil { return 0, models.ErrParentMessageIsNotSet } if cue.ParentChannelMessage.Id == 0 { return 0, models.ErrParentMessageIdIsNotSet } cml, err := cue.Channel.FetchMessageList(cue.ParentChannelMessage.Id) if err != nil { return 0, err } // check if the participant is troll isRecieverTroll := cue.ChannelParticipant.MetaBits.Is(models.Troll) // for pinned posts we are calculating unread count from reviseddAt of the // regarding channel message list, since only participant for the channel is // the owner and we cant use channel_participant for unread counts on the // other hand messages should have their own unread count we are // specialcasing the pinned posts here return models.NewMessageReply().UnreadCount(cml.MessageId, cml.RevisedAt, isRecieverTroll) }
func (f *Controller) sendReplyEventAsChannelUpdatedEvent(mr *models.MessageReply, eventType channelUpdatedEventType) error { parent, err := models.Cache.Message.ById(mr.MessageId) if err != nil { return err } // if reply is created now, it wont be in the cache // but fetch it from db and add to cache, we may use it later reply, err := models.Cache.Message.ById(mr.ReplyId) if err != nil { return err } cml := models.NewChannelMessageList() channels, err := cml.FetchMessageChannels(parent.Id) if err != nil { return err } if len(channels) == 0 { f.log.Error( "Message:(%d) is not in any channel, bu somehow we addd a reply??", parent.Id, ) return nil } cue := &channelUpdatedEvent{ // channel will be set in range loop Controller: f, Channel: nil, ParentChannelMessage: parent, ReplyChannelMessage: reply, EventType: eventType, } // send this event to all channels // that have this message for _, channel := range channels { cue.Channel = &channel // send this event to all channels // that have this message err := cue.notifyAllParticipants() if err != nil { f.log.Error("err %s", err.Error()) } } return nil }
func List(u *url.URL, h http.Header, _ interface{}, context *models.Context) (int, http.Header, interface{}, error) { channelId, err := request.GetURIInt64(u, "id") if err != nil { return response.NewBadRequest(err) } query := request.GetQuery(u) query = context.OverrideQuery(query) c, err := models.Cache.Channel.ById(channelId) if err != nil { return response.NewBadRequest(err) } if !query.ShowExempt { query.ShowExempt = context.Client.Account.IsTroll } // if channel is exempt and user should see the // content, return not found err if c.MetaBits.Is(models.Troll) && !query.ShowExempt { return response.NewNotFound() } canOpen, err := c.CanOpen(query.AccountId) if err != nil { return response.NewBadRequest(err) } if !canOpen { return response.NewAccessDenied( fmt.Errorf( "account (%d) tried to retrieve the unattended private channel (%d)", query.AccountId, c.Id, )) } cml := models.NewChannelMessageList() cml.ChannelId = c.Id return response.HandleResultAndError( cml.List(query, false), ) }
func CountHistory(channelId int64) (*models.CountResponse, error) { c := models.NewChannelMessageList() c.ChannelId = channelId url := fmt.Sprintf("/channel/%d/history/count", channelId) res, err := marshallAndSendRequest("GET", url, c) if err != nil { return nil, err } var count models.CountResponse err = json.Unmarshal(res, &count) if err != nil { return nil, err } return &count, nil }
func Count(u *url.URL, h http.Header, _ interface{}) (int, http.Header, interface{}, error) { channelId, err := request.GetURIInt64(u, "id") if err != nil { return response.NewBadRequest(err) } if channelId == 0 { return response.NewBadRequest(errors.New("channel id is not set")) } count, err := models.NewChannelMessageList().Count(channelId) if err != nil { return response.NewBadRequest(err) } res := new(models.CountResponse) res.TotalCount = count return response.NewOK(res) }
func FetchMessageIdsByChannelId(channelId int64, q *request.Query) ([]int64, error) { query := &bongo.Query{ Selector: map[string]interface{}{ "channel_id": channelId, }, Pluck: "message_id", Pagination: *bongo.NewPagination(q.Limit, q.Skip), Sort: map[string]string{ "added_at": "DESC", }, } var messageIds []int64 if err := models.NewChannelMessageList().Some(&messageIds, query); err != nil { return nil, err } if messageIds == nil { return make([]int64, 0), nil } return messageIds, nil }
// this is a TEMP function just for @usirin func TempList(u *url.URL, h http.Header, _ interface{}, context *models.Context) (int, http.Header, interface{}, error) { channelId, err := request.GetURIInt64(u, "id") if err != nil { return response.NewBadRequest(err) } query := request.GetQuery(u) query = context.OverrideQuery(query) c, err := models.Cache.Channel.ById(channelId) if err != nil { return response.NewBadRequest(err) } // if channel is exempt and user should see the // content, return not found err if !query.ShowExempt { query.ShowExempt = context.Client.Account.IsTroll } if c.MetaBits.Is(models.Troll) && !query.ShowExempt { return response.NewNotFound() } // check if channel is accessible by the requester canOpen, err := c.CanOpen(query.AccountId) if err != nil { return response.NewBadRequest(err) } if !canOpen { return response.NewAccessDenied( fmt.Errorf( "account (%d) tried to retrieve the unattended private channel (%d)", query.AccountId, c.Id, )) } bq := &bongo.Query{ Selector: map[string]interface{}{ "initial_channel_id": c.Id, }, Pagination: *bongo.NewPagination(query.Limit, query.Skip), Pluck: "id", } bq.AddScope(models.SortedByCreatedAt) bq.AddScope(models.RemoveTrollContent(c, query.ShowExempt)) bq.AddScope(models.ExcludeFields(query.Exclude)) bq.AddScope(models.TillTo(query.From)) bqq := bongo.B.BuildQuery(models.NewChannelMessage(), bq) var messages []int64 if err := bongo.CheckErr( bqq.Pluck(bq.Pluck, &messages), ); err != nil { return response.NewBadRequest(err) } // get the messages in regarding channel cmcs, err := models. NewChannelMessageList(). PopulateChannelMessages( messages, query, ) if err != nil { return response.NewBadRequest(err) } // reduce replies replyIds := make([]int64, 0) for i := range cmcs { cmc := cmcs[i] if cmc.Message.TypeConstant == models.ChannelMessage_TYPE_REPLY { replyIds = append(replyIds, cmc.Message.Id) } } if len(replyIds) == 0 { return response.NewOK(cmcs) } // select replies var mrs []models.MessageReply gerr := bongo.B. BuildQuery(models.NewMessageReply(), &bongo.Query{}). Where("reply_id in (?)", replyIds). Find(&mrs) if err := bongo.CheckErr(gerr); err != nil { return response.NewBadRequest(err) } // set their parent ids for j, mr := range mrs { for i := range cmcs { if mr.ReplyId == cmcs[i].Message.Id { cmcs[i].ParentID = mrs[j].MessageId } } } // send response return response.NewOK(cmcs) }
func TestChannelParticipantOperations(t *testing.T) { tests.WithRunner(t, func(r *runner.Runner) { Convey("while testing channel participants", t, func() { Convey("First Create Users and initiate conversation", func() { ownerAccount, groupChannel, groupName := models.CreateRandomGroupDataWithChecks() ownerSes, err := modelhelper.FetchOrCreateSession(ownerAccount.Nick, groupName) So(err, ShouldBeNil) So(ownerSes, ShouldNotBeNil) secondAccount, err := models.CreateAccountInBothDbs() tests.ResultedWithNoErrorCheck(secondAccount, err) _, err = groupChannel.AddParticipant(secondAccount.Id) So(err, ShouldBeNil) thirdAccount, err := models.CreateAccountInBothDbs() tests.ResultedWithNoErrorCheck(thirdAccount, err) _, err = groupChannel.AddParticipant(thirdAccount.Id) So(err, ShouldBeNil) forthAccount, err := models.CreateAccountInBothDbs() tests.ResultedWithNoErrorCheck(forthAccount, err) _, err = groupChannel.AddParticipant(forthAccount.Id) So(err, ShouldBeNil) devrim, err := models.CreateAccountInBothDbsWithNick("devrim") tests.ResultedWithNoErrorCheck(devrim, err) _, err = groupChannel.AddParticipant(devrim.Id) So(err, ShouldBeNil) ses, err := modelhelper.FetchOrCreateSession(ownerAccount.Nick, groupName) tests.ResultedWithNoErrorCheck(ses, err) secondSes, err := modelhelper.FetchOrCreateSession(secondAccount.Nick, groupName) tests.ResultedWithNoErrorCheck(secondSes, err) pmr := models.ChannelRequest{} pmr.AccountId = ownerAccount.Id pmr.Body = "new conversation" pmr.GroupName = groupName pmr.Recipients = []string{"devrim"} channelContainer, err := rest.SendPrivateChannelRequest(pmr, ownerSes.ClientId) So(err, ShouldBeNil) So(channelContainer, ShouldNotBeNil) Convey("First user should be able to add second and third users to conversation", func() { _, err = rest.AddChannelParticipant(channelContainer.Channel.Id, ownerSes.ClientId, secondAccount.Id, thirdAccount.Id) So(err, ShouldBeNil) participants, err := rest.ListChannelParticipants(channelContainer.Channel.Id, ownerSes.ClientId) So(err, ShouldBeNil) So(participants, ShouldNotBeNil) // it is four because first user is "devrim" here So(len(participants), ShouldEqual, 4) Convey("First user should not be able to re-add second participant", func() { _, err = rest.AddChannelParticipant(channelContainer.Channel.Id, ownerSes.ClientId, secondAccount.Id) So(err, ShouldBeNil) participants, err := rest.ListChannelParticipants(channelContainer.Channel.Id, ownerSes.ClientId) So(err, ShouldBeNil) So(participants, ShouldNotBeNil) So(len(participants), ShouldEqual, 4) }) Convey("Second user should be able to leave conversation", func() { // token of account -> secondAccount _, err = rest.DeleteChannelParticipant(channelContainer.Channel.Id, secondSes.ClientId, secondAccount.Id) So(err, ShouldBeNil) participants, err := rest.ListChannelParticipants(channelContainer.Channel.Id, ownerSes.ClientId) So(err, ShouldBeNil) So(participants, ShouldNotBeNil) So(len(participants), ShouldEqual, 3) Convey("A user who is not participant of a conversation should not be able to add another user to the conversation", func() { _, err = rest.AddChannelParticipant(channelContainer.Channel.Id, secondSes.ClientId, forthAccount.Id) So(err, ShouldNotBeNil) participants, err := rest.ListChannelParticipants(channelContainer.Channel.Id, ownerSes.ClientId) So(err, ShouldBeNil) So(participants, ShouldNotBeNil) So(len(participants), ShouldEqual, 3) }) }) Convey("Channel owner should be able to kick another conversation participant", func() { _, err = rest.DeleteChannelParticipant(channelContainer.Channel.Id, ownerSes.ClientId, secondAccount.Id) So(err, ShouldBeNil) participants, err := rest.ListChannelParticipants(channelContainer.Channel.Id, ownerSes.ClientId) So(err, ShouldBeNil) So(participants, ShouldNotBeNil) So(len(participants), ShouldEqual, 3) }) Convey("when a user is blocked", func() { _, err = rest.BlockChannelParticipant(channelContainer.Channel.Id, ownerSes.ClientId, secondAccount.Id) So(err, ShouldBeNil) Convey("it should not be in channel participant list", func() { participants, err := rest.ListChannelParticipants(channelContainer.Channel.Id, ownerSes.ClientId) So(err, ShouldBeNil) So(participants, ShouldNotBeNil) So(len(participants), ShouldEqual, 3) }) Convey("should not be able to add it back", func() { _, err = rest.AddChannelParticipant(channelContainer.Channel.Id, ownerSes.ClientId, secondAccount.Id) So(err, ShouldNotBeNil) }) Convey("should be able to unblock", func() { _, err = rest.UnblockChannelParticipant(channelContainer.Channel.Id, ownerSes.ClientId, secondAccount.Id) So(err, ShouldBeNil) Convey("it should not be in channel participant list still", func() { participants, err := rest.ListChannelParticipants(channelContainer.Channel.Id, ownerSes.ClientId) So(err, ShouldBeNil) So(participants, ShouldNotBeNil) So(len(participants), ShouldEqual, 3) }) Convey("when we add the same user as participant", func() { _, err = rest.AddChannelParticipant(channelContainer.Channel.Id, ownerSes.ClientId, secondAccount.Id, thirdAccount.Id) So(err, ShouldBeNil) Convey("it should be in channel participant list", func() { participants, err := rest.ListChannelParticipants(channelContainer.Channel.Id, ownerSes.ClientId) So(err, ShouldBeNil) So(participants, ShouldNotBeNil) So(len(participants), ShouldEqual, 4) }) }) }) }) Convey("Second user should not be able to kick another conversation participant", func() { _, err = rest.DeleteChannelParticipant(channelContainer.Channel.Id, secondSes.ClientId, thirdAccount.Id) So(err, ShouldNotBeNil) }) }) Convey("First user should be able to invite second user", func() { _, err = rest.InviteChannelParticipant(channelContainer.Channel.Id, ownerSes.ClientId, secondAccount.Id) So(err, ShouldBeNil) participants, err := rest.ListChannelParticipants(channelContainer.Channel.Id, ownerSes.ClientId) So(err, ShouldBeNil) So(participants, ShouldNotBeNil) // it is four because first user is "devrim" here So(len(participants), ShouldEqual, 2) Convey("Second user should be able to reject invitation", func() { ses, err := modelhelper.FetchOrCreateSession(secondAccount.Nick, groupName) So(err, ShouldBeNil) So(ses, ShouldNotBeNil) err = rest.RejectInvitation(channelContainer.Channel.Id, ses.ClientId) So(err, ShouldBeNil) participants, err := rest.ListChannelParticipants(channelContainer.Channel.Id, ownerSes.ClientId) So(err, ShouldBeNil) So(participants, ShouldNotBeNil) So(len(participants), ShouldEqual, 2) }) Convey("Second user should be able to accept invitation", func() { ses, err := modelhelper.FetchOrCreateSession(secondAccount.Nick, groupName) So(err, ShouldBeNil) So(ses, ShouldNotBeNil) err = rest.AcceptInvitation(channelContainer.Channel.Id, ses.ClientId) So(err, ShouldBeNil) participants, err := rest.ListChannelParticipants(channelContainer.Channel.Id, ownerSes.ClientId) So(err, ShouldBeNil) So(participants, ShouldNotBeNil) So(len(participants), ShouldEqual, 3) }) }) // TODO Until we find a better way for handling async stuff, this test is skipped. Instead of sleep, we should use some // timeouts for testing these kind of stuff. SkipConvey("All private messages must be deleted when all participant users leave the channel", func() { account := models.NewAccount() err = account.ByNick("devrim") So(err, ShouldBeNil) _, err = rest.DeleteChannelParticipant(channelContainer.Channel.Id, ses.ClientId, account.Id) So(err, ShouldBeNil) _, err = rest.DeleteChannelParticipant(channelContainer.Channel.Id, ownerSes.ClientId, ownerAccount.Id) So(err, ShouldBeNil) time.Sleep(1 * time.Second) testChannel := models.NewChannel() err := testChannel.ById(channelContainer.Channel.Id) So(err, ShouldEqual, bongo.RecordNotFound) testChannelList := models.NewChannelMessageList() err = bongo.B.Unscoped().Where("channel_id = ?", channelContainer.Channel.Id).Find(testChannelList).Error So(err, ShouldEqual, bongo.RecordNotFound) testMessage := models.NewChannelMessage() err = bongo.B.Unscoped().Where("initial_channel_id = ?", channelContainer.Channel.Id).Find(testMessage).Error So(err, ShouldEqual, bongo.RecordNotFound) }) Convey("Users should not be able to add/remove users to/from bot channels", func() { ownerAccount, _, groupName := models.CreateRandomGroupDataWithChecks() participant := models.NewAccount() participant.OldId = AccountOldId.Hex() participant, err = rest.CreateAccount(participant) So(err, ShouldBeNil) So(participant, ShouldNotBeNil) ses, err := modelhelper.FetchOrCreateSession(ownerAccount.Nick, groupName) So(err, ShouldBeNil) ch, err := rest.CreateChannelByGroupNameAndType(ownerAccount.Id, groupName, models.Channel_TYPE_BOT, ses.ClientId) So(err, ShouldBeNil) So(ch, ShouldNotBeNil) // account is -> ownerAccount.Id _, err = rest.AddChannelParticipant(ch.Id, ses.ClientId, participant.Id) So(strings.Contains(err.Error(), "can not add participants for bot channel"), ShouldBeTrue) }) Convey("Users should be able to add/remove users to/from topic channels", func() { ownerAccount, _, groupName := models.CreateRandomGroupDataWithChecks() participant := models.NewAccount() participant.OldId = AccountOldId.Hex() participant, err = rest.CreateAccount(participant) So(err, ShouldBeNil) So(participant, ShouldNotBeNil) ses, err := modelhelper.FetchOrCreateSession(ownerAccount.Nick, groupName) So(err, ShouldBeNil) ch, err := rest.CreateChannelByGroupNameAndType(ownerAccount.Id, groupName, models.Channel_TYPE_TOPIC, ses.ClientId) So(err, ShouldBeNil) So(ch, ShouldNotBeNil) // account is -> ownerAccount.Id _, err = rest.AddChannelParticipant(ch.Id, ses.ClientId, participant.Id) So(err, ShouldBeNil) Convey("adding same user again should success", func() { _, err = rest.AddChannelParticipant(ch.Id, ses.ClientId, participant.Id) So(err, ShouldBeNil) }) _, err = rest.DeleteChannelParticipant(ch.Id, ses.ClientId, participant.Id) So(err, ShouldBeNil) Convey("removing same user again should success", func() { _, err = rest.DeleteChannelParticipant(ch.Id, ses.ClientId, participant.Id) So(err, ShouldBeNil) }) }) Convey("while removing users from group channels", func() { ownerAccount, _, groupName := models.CreateRandomGroupDataWithChecks() participant := models.NewAccount() participant.OldId = AccountOldId.Hex() participant, err = rest.CreateAccount(participant) So(err, ShouldBeNil) So(participant, ShouldNotBeNil) participant2 := models.NewAccount() participant2.OldId = AccountOldId.Hex() participant2, err = rest.CreateAccount(participant2) So(err, ShouldBeNil) So(participant2, ShouldNotBeNil) ownerSes, err := modelhelper.FetchOrCreateSession(ownerAccount.Nick, groupName) So(err, ShouldBeNil) ses, err := modelhelper.FetchOrCreateSession(participant.Nick, groupName) So(err, ShouldBeNil) ch, err := rest.CreateChannelByGroupNameAndType(ownerAccount.Id, groupName, models.Channel_TYPE_GROUP, ownerSes.ClientId) So(err, ShouldBeNil) So(ch, ShouldNotBeNil) // ownerSes session is admin's session data _, err = rest.AddChannelParticipant(ch.Id, ownerSes.ClientId, participant.Id) So(err, ShouldBeNil) _, err = rest.AddChannelParticipant(ch.Id, ownerSes.ClientId, participant2.Id) So(err, ShouldBeNil) Convey("owner should be able to remove user from group channel", func() { // ownerSes session is admin's session data _, err = rest.DeleteChannelParticipant(ch.Id, ownerSes.ClientId, participant2.Id) So(err, ShouldBeNil) }) Convey("nonOwner should not be able to remove user from group channel", func() { // ses session is participant's session data _, err = rest.DeleteChannelParticipant(ch.Id, ses.ClientId, participant2.Id) So(err, ShouldNotBeNil) }) }) }) }) }) }
func Create(u *url.URL, h http.Header, req *models.ChannelMessage, c *models.Context) (int, http.Header, interface{}, error) { if !c.IsLoggedIn() { return response.NewBadRequest(models.ErrAccessDenied) } channelId, err := fetchInitialChannelId(u, c) if err != nil { return response.NewBadRequest(err) } ch := models.NewChannel() if err := ch.ById(channelId); err != nil { return response.NewBadRequest(models.ErrChannelNotFound) } canOpen, err := ch.CanOpen(c.Client.Account.Id) if err != nil { return response.NewBadRequest(err) } if !canOpen { return response.NewBadRequest(models.ErrCannotOpenChannel) } // override message type // all of the messages coming from client-side // should be marked as POST req.TypeConstant = models.ChannelMessage_TYPE_POST req.InitialChannelId = channelId req.AccountId = c.Client.Account.Id if req.Payload == nil { req.Payload = gorm.Hstore{} } if c.Client.Account.IsShareLocationEnabled() { // gets the IP of the Client // and adds it to the payload of the ChannelMessage location := parseLocation(c) req.Payload["location"] = location } if err := checkThrottle(channelId, req.AccountId); err != nil { return response.NewBadRequest(err) } if err := req.Create(); err != nil { // todo this should be internal server error return response.NewBadRequest(err) } cml := models.NewChannelMessageList() // override channel id cml.ChannelId = channelId cml.MessageId = req.Id cml.ClientRequestId = req.ClientRequestId if err := cml.Create(); err != nil && !models.IsUniqueConstraintError(err) { // todo this should be internal server error return response.NewBadRequest(err) } cmc := models.NewChannelMessageContainer() err = cmc.Fetch(req.Id, request.GetQuery(u)) if err != nil { return response.NewBadRequest(err) } // assign client request id back to message response because // client uses it for latency compansation cmc.Message.ClientRequestId = req.ClientRequestId return response.HandleResultAndError(cmc, err) }