コード例 #1
0
func createChannelMessageList(channelId, messageId int64) *models.ChannelMessageList {
	cml := models.NewChannelMessageList()

	cml.ChannelId = channelId
	cml.MessageId = messageId

	So(cml.Create(), ShouldBeNil)

	return cml
}
コード例 #2
0
// 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)
}
コード例 #3
0
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
}
コード例 #4
0
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),
	)
}
コード例 #5
0
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
}
コード例 #6
0
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)
}
コード例 #7
0
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
}
コード例 #8
0
// 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)
}
コード例 #9
0
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)
					})
				})
			})
		})
	})
}
コード例 #10
0
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)
}