func ParticipatedChannelCount(u *url.URL, h http.Header, _ interface{}, c *models.Context) (int, http.Header, interface{}, error) { accountId, err := request.GetURIInt64(u, "id") if err != nil { return response.NewBadRequest(err) } if !c.IsLoggedIn() { return response.NewBadRequest(models.ErrNotLoggedIn) } if accountId != c.Client.Account.Id { return response.NewBadRequest(models.ErrAccessDenied) } query := request.GetQuery(u) query = c.OverrideQuery(query) if query.Type == "" { query.Type = models.Channel_TYPE_TOPIC } cp := models.NewChannelParticipant() a := &models.Account{Id: query.AccountId} return response.HandleResultAndError(cp.ParticipatedChannelCount(a, query)) }
// DeletePrivateChannelMessages deletes all channel messages from a private message channel // when there are no more participants func DeleteDesertedChannelMessages(channelId int64) error { c := models.NewChannel() if err := c.ById(channelId); err != nil { return err } if c.TypeConstant != models.Channel_TYPE_PRIVATE_MESSAGE && c.TypeConstant != models.Channel_TYPE_COLLABORATION { return nil } cp := models.NewChannelParticipant() cp.ChannelId = c.Id count, err := cp.FetchParticipantCount() if err != nil { return err } if count != 0 { return nil } // no need to keep the channel any more return c.Delete() }
func updateStatus(participant *models.ChannelParticipant, query *request.Query, ctx *models.Context) (*models.ChannelParticipant, error) { if ok := ctx.IsLoggedIn(); !ok { return nil, models.ErrNotLoggedIn } query.AccountId = ctx.Client.Account.Id cp := models.NewChannelParticipant() cp.ChannelId = query.Id // check if the user is invited isInvited, err := cp.IsInvited(query.AccountId) if err != nil { return nil, err } if !isInvited { return nil, errors.New("uninvited user error") } cp.StatusConstant = participant.StatusConstant // update the status if err := cp.Update(); err != nil { return nil, err } return cp, nil }
// todo - refactor this part func (f *Controller) MessageListDeleted(cml *models.ChannelMessageList) error { c, err := models.Cache.Channel.ById(cml.ChannelId) if err != nil { return err } // Since we are making hard deletes, we no longer need to check the // message existency cm := models.NewChannelMessage() cm.Id = cml.MessageId cp := models.NewChannelParticipant() cp.AccountId = c.CreatorId cue := &channelUpdatedEvent{ Controller: f, Channel: c, ParentChannelMessage: cm, ChannelParticipant: cp, EventType: channelUpdatedEventMessageRemovedFromChannel, } if err := cue.notifyAllParticipants(); err != nil { return err } // f.sendNotification(cp.AccountId, ChannelUpdateEventName, cue) if err := f.sendChannelEvent(cml, cm, MessageRemovedEventName); err != nil { return err } return nil }
func RejectInvitation(channelId int64, token string) error { url := fmt.Sprintf("/channel/%d/invitation/reject", channelId) cp := models.NewChannelParticipant() _, err := marshallAndSendRequestWithAuth("POST", url, cp, token) return err }
func AddMulti(u *url.URL, h http.Header, participants []*models.ChannelParticipant, context *models.Context) (int, http.Header, interface{}, error) { query := context.OverrideQuery(request.GetQuery(u)) if err := checkChannelPrerequisites( query.Id, query.AccountId, participants, ); err != nil { return response.NewBadRequest(err) } ch := models.NewChannel() err := ch.ById(query.Id) if err != nil { return response.NewBadRequest(err) } if ch.TypeConstant == models.Channel_TYPE_BOT { return response.NewBadRequest(errors.New("can not add participants for bot channel")) } for i := range participants { participant := models.NewChannelParticipant() participant.ChannelId = query.Id // prevent duplicate participant addition isParticipant, err := participant.IsParticipant(participants[i].AccountId) if err != nil { return response.NewBadRequest(err) } if isParticipant { continue } participant.AccountId = participants[i].AccountId //We can add users with requestpending status if participants[i].StatusConstant != "" { participant.StatusConstant = participants[i].StatusConstant } if err := participant.Create(); err != nil { return response.NewBadRequest(err) } participants[i] = participant if err := addJoinActivity(query.Id, participant, query.AccountId); err != nil { return response.NewBadRequest(err) } } go notifyParticipants(ch, models.ChannelParticipant_Added_To_Channel_Event, participants) return response.NewOK(participants) }
// sendChannelUpdatedEvent sends channel updated events func (cue *channelUpdatedEvent) notifyAllParticipants() error { cue.Controller.log.Debug("notifying all participants with: %+v", cue) // if it is not a valid event, return silently if !cue.isValidNotifyAllParticipantsEvent() { cue.Controller.log.Debug("not a valid event (%s) for notifying all participants", cue.EventType) return nil } // fetch all participants of related channel if you ask why we are not // sending those messaages to the channel's channel instead of sending // events as notifications?, because we are also sending unread counts of // the related channel's messages by the notifiee p := models.NewChannelParticipant() p.ChannelId = cue.Channel.Id // TODO use proper caching here participants, err := p.List( &request.Query{ // make sure exempt users are getting reatime notifications ShowExempt: true, // lets say nodejs topic has 56K participants(yes it has), if we // dont limit we can take koding down. 100 is just a random number Limit: 100, // send the event to the recently active users Sort: map[string]string{"updated_at": "DESC"}, }, ) if err != nil { cue.Controller.log.Error("Error occurred while fetching participants %s", err.Error()) return err } if len(participants) == 0 { return nil } for i, cp := range participants { if !cue.isEligibleForBroadcastingToParticipant(cp.AccountId) { cue.Controller.log.Debug("not sending event (%s) to the participant %d", cue.EventType, cp.AccountId) continue } cue.ChannelParticipant = &participants[i] err := cue.sendForParticipant() if err != nil { cue.Controller.log.Error("Error while sending notification (%s)", err.Error()) } } return nil }
// ChannelMessageListUpdated event states that one of the channel_message_list // record is updated, it means that one the pinned post's owner glanced it. - At // least for now - If we decide on another giving another meaning on this event, // we can rename the event. PinnedPost's unread count is calculated from the // last glanced reply's point. This message is user specific. There is no // relation with channel participants or channel itself func (f *Controller) ChannelMessageListUpdated(cml *models.ChannelMessageList) error { // find the user's pinned post channel // we need it for finding the account id c, err := models.Cache.Channel.ById(cml.ChannelId) if err != nil { return err } if c.TypeConstant != models.Channel_TYPE_PINNED_ACTIVITY { f.log.Error("please investigate here, we have updated the channel message list for a non-pinned post item %+v", c) return nil } // get the glanced message cm, err := models.Cache.Message.ById(cml.MessageId) if err != nil { return err } // No need to fetch the participant from database we are gonna use only the // account id cp := models.NewChannelParticipant() cp.AccountId = c.CreatorId cp.ChannelId = c.Id if err := cp.FetchParticipant(); err != nil { return err } cue := &channelUpdatedEvent{ // inject controller for reaching to RMQ, log and other stuff Controller: f, // In which channel this event happened, we need groupName from the // channel because user can be in multiple groups, and all group-account // couples have separate channels Channel: c, // We need parentChannelMessage for calculating the unread count of it's replies ParentChannelMessage: cm, // ChannelParticipant is the reciever of this event ChannelParticipant: cp, // Assign event type EventType: channelUpdatedEventMessageUpdatedAtChannel, } if err := cue.sendForParticipant(); err != nil { return err } return nil }
// PinnedChannelListUpdated handles the events of pinned channel lists'. When a // user glance a pinned message or when someone posts reply to a message we are // updating the channel message lists func (f *Controller) PinnedChannelListUpdated(pclue *models.PinnedChannelListUpdatedEvent) error { // find the user's pinned post channel // we need it for finding the account id c := pclue.Channel if &c == nil { f.log.Error("channel was nil, discarding the message %+v", pclue) return nil } if c.TypeConstant != models.Channel_TYPE_PINNED_ACTIVITY { f.log.Error("please investigate here, we have updated the channel message list for a non-pinned post item %+v", c) return nil } // No need to fetch the participant from database we are gonna use only the // account id cp := models.NewChannelParticipant() cp.AccountId = c.CreatorId cp.ChannelId = c.Id if err := cp.FetchParticipant(); err != nil { return err } cue := &channelUpdatedEvent{ // inject controller for reaching to RMQ, log and other stuff Controller: f, // In which channel this event happened, we need groupName from the // channel because user can be in multiple groups, and all group-account // couples have separate channels Channel: &c, // We need parentChannelMessage for calculating the unread count of it's replies ParentChannelMessage: &pclue.Message, // We need to find out that if the reply is created by a troll ReplyChannelMessage: &pclue.Reply, // ChannelParticipant is the reciever of this event ChannelParticipant: cp, // Assign event type EventType: channelUpdatedEventReplyAdded, } if err := cue.sendForParticipant(); err != nil { return err } return nil }
func CheckParticipation(u *url.URL, h http.Header, _ interface{}, context *models.Context) (int, http.Header, interface{}, error) { q := context.OverrideQuery(request.GetQuery(u)) if context.Client != nil && context.Client.Account != nil { q.AccountId = context.Client.Account.Id } if q.Type == "" || q.AccountId == 0 { return response.NewBadRequest(errors.New("type or accountid is not set")) } channel, err := models.NewChannel().ByName(q) if err != nil { return response.NewBadRequest(err) } res := models.NewCheckParticipationResponse() res.Channel = &channel res.Account = context.Client.Account if context.Client.Account != nil { res.AccountToken = context.Client.Account.Token } canOpen, err := channel.CanOpen(q.AccountId) if err != nil { return response.NewBadRequest(err) } if !canOpen { cp := models.NewChannelParticipant() cp.ChannelId = channel.Id isInvited, err := cp.IsInvited(q.AccountId) if err != nil { return response.NewBadRequest(err) } if !isInvited { return response.NewAccessDenied( fmt.Errorf( "account (%d) tried to retrieve the unattended channel (%d)", q.AccountId, channel.Id, ), ) } } return response.NewOK(res) }
func (mwc *Controller) createChannelParticipants(s modelhelper.Selector, channelId int64) error { migrateRelationship := func(relationship interface{}) error { r := relationship.(*mongomodels.Relationship) if r.MigrationStatus == "Completed" { return nil } // fetch follower id, err := mwc.AccountIdByOldId(r.TargetId.Hex()) if err != nil { mwc.log.Error("Participant account %s cannot be fetched: %s", r.TargetId.Hex(), err) return nil } cp := models.NewChannelParticipant() cp.ChannelId = channelId cp.AccountId = id cp.StatusConstant = models.ChannelParticipant_STATUS_ACTIVE cp.LastSeenAt = r.TimeStamp cp.UpdatedAt = r.TimeStamp cp.CreatedAt = r.TimeStamp if err := cp.CreateRaw(); err != nil { mwc.log.Error("Participant cannot be created: %s", err) return nil } r.MigrationStatus = "Completed" if err := modelhelper.UpdateRelationship(r); err != nil { mwc.log.Error("Participant relationship cannot be flagged as migrated: %s", err) } return nil } iterOptions := helpers.NewIterOptions() iterOptions.CollectionName = "relationships" iterOptions.F = migrateRelationship iterOptions.Filter = s iterOptions.Result = &mongomodels.Relationship{} iterOptions.Limit = 1000000000 iterOptions.Skip = 0 return helpers.Iter(modelhelper.Mongo, iterOptions) }
func channelParticipantOp(url string, channelId int64, token string, accountIds ...int64) (*models.ChannelParticipant, error) { res := make([]*models.ChannelParticipant, 0) for _, accountId := range accountIds { c := models.NewChannelParticipant() c.AccountId = accountId res = append(res, c) } cps, err := sendModelWithAuth("POST", url, &res, token) if err != nil { return nil, err } a := *(cps.(*[]*models.ChannelParticipant)) return a[0], nil }
func handleChannelResponse(c models.Channel, q *request.Query) (int, http.Header, interface{}, error) { // add troll mode filter if c.MetaBits.Is(models.Troll) && !q.ShowExempt { return response.NewNotFound() } canOpen, err := c.CanOpen(q.AccountId) if err != nil { return response.NewBadRequest(err) } if !canOpen { cp := models.NewChannelParticipant() cp.ChannelId = c.Id isInvited, err := cp.IsInvited(q.AccountId) if err != nil { return response.NewBadRequest(err) } if !isInvited { return response.NewAccessDenied( fmt.Errorf( "account (%d) tried to retrieve the unattended channel (%d)", q.AccountId, c.Id, ), ) } } cc := models.NewChannelContainer() if err := cc.Fetch(c.GetId(), q); err != nil { return response.NewBadRequest(err) } cc.AddIsParticipant(q.AccountId) // TODO this should be in the channel cache by default cc.AddLastMessage(q.AccountId) cc.AddUnreadCount(q.AccountId) return response.HandleResultAndError(cc, cc.Err) }
func InviteChannelParticipant(channelId int64, token string, accountIds ...int64) (*models.ChannelParticipant, error) { url := fmt.Sprintf("/channel/%d/participants/add", channelId) res := make([]*models.ChannelParticipant, 0) for _, accountId := range accountIds { c := models.NewChannelParticipant() c.AccountId = accountId c.StatusConstant = models.ChannelParticipant_STATUS_REQUEST_PENDING res = append(res, c) } cps, err := sendModelWithAuth("POST", url, &res, token) if err != nil { return nil, err } a := *(cps.(*[]*models.ChannelParticipant)) return a[0], nil }
func (mwc *Controller) createPublicChannel() *models.Channel { c := models.NewChannel() c.Name = "public" c.GroupName = models.Channel_KODING_NAME c.TypeConstant = models.Channel_TYPE_GROUP c.PrivacyConstant = models.Channel_PRIVACY_PUBLIC c.CreatorId = 1 err := c.Create() if err != nil { mwc.log.Error("Could not create public channel: %s", err) } cp := models.NewChannelParticipant() cp.ChannelId = c.Id cp.AccountId = 1 if err := cp.Create(); err != nil { mwc.log.Error("Could not add devrim to public channel: %s", err) } return c }
func fetchChannelParticipants(query *request.Query) ([]models.ChannelParticipantContainer, error) { cp := models.NewChannelParticipant() cp.ChannelId = query.Id participants, err := cp.List(query) if err != nil { return nil, err } cps := make([]models.ChannelParticipantContainer, len(participants)) for i, participant := range participants { cpc, err := models.NewChannelParticipantContainer(participant) if err != nil { return cps, err } cps[i] = *cpc } return cps, nil }
func ListGroupChannels(u *url.URL, h http.Header, _ interface{}, c *models.Context) (int, http.Header, interface{}, error) { if !c.IsLoggedIn() { return response.NewBadRequest(models.ErrNotLoggedIn) } cp := models.NewChannelParticipant() cids, err := cp.FetchAllParticipatedChannelIdsInGroup(c.Client.Account.Id, c.GroupName) if err != nil { return response.NewBadRequest(err) } channels, err := models.NewChannel().FetchByIds(cids) if err != nil { return response.NewBadRequest(err) } cc := models.NewChannelContainers() cc.PopulateWith(channels, c.Client.Account.Id) return response.HandleResultAndError(cc, cc.Err()) }
// handleParticipantRemove removes a user from all channel participated channels // in given group func (c *Controller) handleParticipantRemove(cp *models.ChannelParticipant) error { channel, err := models.Cache.Channel.ById(cp.ChannelId) if err != nil { c.log.Error("Channel: %d is not found", cp.ChannelId) return nil } if channel.TypeConstant != models.Channel_TYPE_GROUP { return nil } if !models.IsIn(cp.StatusConstant, models.ChannelParticipant_STATUS_BLOCKED, models.ChannelParticipant_STATUS_LEFT, ) { return nil } cpp := models.NewChannelParticipant() ids, err := cpp.FetchAllParticipatedChannelIdsInGroup(cp.AccountId, channel.GroupName) if err != nil && err != bongo.RecordNotFound { return err } if err == bongo.RecordNotFound { return nil } for _, id := range ids { ch := models.NewChannel() ch.Id = id if err := ch.RemoveParticipant(cp.AccountId); err != nil { return err } } return nil }
func checkChannelPrerequisites(channelId, requesterId int64, participants []*models.ChannelParticipant) error { if channelId == 0 || requesterId == 0 { return fmt.Errorf("values are not set. channelId: %d, requesterId: %d", channelId, requesterId) } if len(participants) == 0 { return errors.New("0 participant is given for participant operation") } c, err := models.Cache.Channel.ById(channelId) if err != nil { return err } canOpen, err := c.CanOpen(requesterId) if err != nil { return err } if !canOpen { return errors.New("can not open channel") } // admins can add users into group channels // if c.TypeConstant == models.Channel_TYPE_GROUP { // return errors.New("can not add/remove participants for group channel") // } if c.TypeConstant == models.Channel_TYPE_PINNED_ACTIVITY { return errors.New("can not add/remove participants for pinned activity channel") } if c.TypeConstant == models.Channel_TYPE_TOPIC { if len(participants) != 1 { return errors.New("you can not add only one participant into topic channel") } // with the new UI structure, now other users can add others into topic channels // // if participants[0].AccountId != requesterId { // return errors.New("you can not add others into topic channel") // } } // return early for non private message channels // no need to continue from here for other channels if c.TypeConstant != models.Channel_TYPE_PRIVATE_MESSAGE && c.TypeConstant != models.Channel_TYPE_COLLABORATION && c.TypeConstant != models.Channel_TYPE_GROUP && c.TypeConstant != models.Channel_TYPE_BOT { return nil } // check if requester is a participant of the private message channel cp := models.NewChannelParticipant() cp.ChannelId = channelId isParticipant, err := cp.IsParticipant(requesterId) if err != nil { return err } if !isParticipant { return fmt.Errorf("%d is not a participant of the channel %d, only participants can add/remove", requesterId, channelId) } return nil }