def _addMembers(self, request): """This method add a new user to this conversation. Keyword Arguments: newMembers: A list of members who will be added to this conversation. convId: The id of the conversation to which these new members will be added as participants. CF Changes: mConversations """ myId = request.getSession(IAuthInfo).username orgId = request.getSession(IAuthInfo).organization newMembers, body, subject, convId = self._parseComposerArgs(request) if not (convId and newMembers): raise errors.MissingParams(['Recipient']) conv = yield db.get_slice(convId, "mConversations") conv = utils.supercolumnsToDict(conv) subject = conv['meta'].get('subject', None) participants = set(conv.get('participants', {}).keys()) if not conv: raise errors.InvalidMessage(convId) if myId not in participants: raise errors.MessageAccessDenied(convId) #cols = yield db.multiget_slice(newMembers, "entities", ['basic']) #people = utils.multiSuperColumnsToDict(cols) people = base.EntitySet(newMembers) yield people.fetchData() newMembers = set([userId for userId in people.keys() \ if people[userId].basic and \ people[userId].basic["org"] == orgId]) newMembers = newMembers - participants mailNotificants = participants - set([myId]) toFetchEntities = mailNotificants.union([myId, orgId]).union(newMembers) entities = base.EntitySet(toFetchEntities) yield entities.fetchData() data = {"entities": entities} data["orgId"] = orgId data["convId"] = convId data["subject"] = subject data["_fromName"] = entities[myId].basic['name'] if newMembers: data["message"] = conv["meta"]["snippet"] newMembers = dict([(userId, '') for userId in newMembers]) yield db.batch_insert(convId, "mConversations", {'participants': newMembers}) yield self._deliverMessage(convId, newMembers, conv['meta']['uuid'], conv['meta']['owner']) yield notifications.notify(newMembers, ":NM", myId, **data) if mailNotificants and newMembers: data["addedMembers"] = newMembers yield notifications.notify(mailNotificants, ":MA", myId, **data)
def invite(group, me, user): """Invite an user to join a group. Only group-member can invite others to the group. Ignore if invited user is already a member of the group. Keyword params: @me: @user: user object @group: group object """ try: yield db.get(group.id, "groupMembers", me.id) except ttypes.NotFoundException as e: raise e try: yield db.get(group.id, "groupMembers", user.id) except ttypes.NotFoundException: try: yield db.get(user.id, "pendingConnections", "GO:%s" % (group.id)) except ttypes.NotFoundException: cols = yield db.get_slice(user.id, "pendingConnections", ["GI:%s" % (group.id)]) invited_by = set() if cols: invited_by.update(cols[0].column.value.split(',')) invited_by.add(me.id) yield db.insert(user.id, "pendingConnections", ",".join(invited_by), "GI:%s" % (group.id)) data = {"entities": {group.id: group, user.id: user, me.id: me}, "groupName": group.basic["name"]} yield notifications.notify([user.id], ":GI:%s" % (group.id), me.id, **data)
def approveRequest(request, group, user, me): """accept a group-join request. Add the user to group-members. Only group-admin can perform this action. Keyword params: @me: @user: user object @group: group object @request: """ if me.id not in group.admins: raise errors.PermissionDenied('Access Denied') try: yield db.get(group.id, "pendingConnections", "GI:%s" % (user.id)) d1 = _removeFromPending(group, user) d2 = _addMember(request, group, user) data = {"entities": {group.id: group, user.id: user, me.id: me}} d3 = notifications.notify([user.id], ":GA", group.id, **data) yield defer.DeferredList([d1, d2, d3]) defer.returnValue(True) except ttypes.NotFoundException: pass defer.returnValue(False)
def subscribe(request, group, user, org): """Open group: add user to the group. Closed group: add a pending request, send a notification to group-admins. Raise an exception if user is blocked from joining the group. Keyword params: @org: org object @user: user object @group: group object @request: """ cols = yield db.get_slice(group.id, "blockedUsers", [user.id]) if cols: raise errors.PermissionDenied(_("You are banned from joining the group.")) isNewMember = False pendingRequests = {} try: cols = yield db.get(group.id, "groupMembers", user.id) except ttypes.NotFoundException: if group.basic['access'] == "open": yield _addMember(request, group, user) yield _removeFromPending(group, user) isNewMember = True else: # Add to pending connections yield db.insert(user.id, "pendingConnections", '', "GO:%s" % (group.id)) yield db.insert(group.id, "pendingConnections", '', "GI:%s" % (user.id)) yield _notify(group, user) pendingRequests["GO:%s" % (group.id)] = user.id entities = base.EntitySet(group.admins.keys()) yield entities.fetchData() entities.update({group.id: group, org.id: org, user.id: user}) data = {"entities": entities, "groupName": group.basic['name']} yield notifications.notify(group.admins, ":GR", user.id, **data) defer.returnValue((isNewMember, pendingRequests))
def _removeMembers(self, request): """This method allows the current user to remove another participant to this conversation. Keyword Arguments: newMembers: A list of members who will be added to this conversation. convId: The id of the conversation to which these new members will be added as participants. CF Changes: mConversations latest """ myId = request.getSession(IAuthInfo).username orgId = request.getSession(IAuthInfo).organization members, body, subject, convId = self._parseComposerArgs(request) if not (convId and members): raise errors.MissingParams([]) conv = yield db.get_slice(convId, "mConversations") conv = utils.supercolumnsToDict(conv) subject = conv['meta'].get('subject', None) participants = conv.get('participants', {}).keys() if not conv: raise errors.InvalidMessage(convId) if myId not in participants: raise errors.MessageAccessDenied(convId) cols = yield db.multiget_slice(members, "entities", ['basic']) people = utils.multiSuperColumnsToDict(cols) members = set([userId for userId in people if people[userId] and \ people[userId]["basic"]["org"] == orgId]) members = members.intersection(participants) if len(members) == len(participants): members.remove(conv['meta']['owner']) deferreds = [] if members: d = db.batch_remove({"mConversations": [convId]}, names=members, supercolumn='participants') deferreds.append(d) cols = yield db.get_slice(convId, 'mConvFolders', members) cols = utils.supercolumnsToDict(cols) for recipient in cols: for folder in cols[recipient]: cf = self._folders[folder] if folder in self._folders else folder d = db.remove(recipient, cf, conv['meta']['uuid']) deferreds.append(d) #update latest- messages-count deferreds.append(db.batch_remove({"latest": members}, names=[conv['meta']['uuid']], supercolumn='messages')) if deferreds: yield deferreds mailNotificants = set(participants) - members - set([myId]) if mailNotificants and members: toFetchEntities = mailNotificants.union([myId, orgId]).union(members) entities = base.EntitySet(toFetchEntities) yield entities.fetchData() data = {"entities": entities} data["orgId"] = orgId data["convId"] = convId data["removedMembers"] = members data["subject"] = subject data["_fromName"] = entities[myId].basic['name'] yield notifications.notify(mailNotificants, ":MA", myId, **data)
def _createConversation(self, request): """Create a new conversation including committing meta info about the conversation and the message. Keyword Arguments: recipients: A list of user ids who also receive this conversation. body: Text of the first message in this conversation. subject: Subject of the conversation. CF Changes: mConvMessages attachmentVersions """ (appchange, script, args, myId) = yield self._getBasicArgs(request) landing = not self._ajax myOrgId = args['orgId'] epoch = int(time.time()) recipients, body, subject, parent = self._parseComposerArgs(request) filterType = utils.getRequestArg(request, "filterType") or None if not parent and not recipients: raise errors.MissingParams(['Recipients']) if not subject and not body: raise errors.MissingParams(['Both subject and message']) recipients = base.EntitySet(recipients) yield recipients.fetchData() recipients = set([userId for userId in recipients.keys() if not recipients[userId].isEmpty()]) if not recipients: raise errors.MissingParams(['Recipients']) recipients.add(myId) participants = list(recipients) timeUUID = uuid.uuid1().bytes snippet = self._fetchSnippet(body) meta = {'uuid': timeUUID, 'date_epoch': str(epoch), "snippet": snippet, 'subject': subject, "owner": myId, "timestamp": str(int(time.time()))} attachments = yield self._handleAttachments(request) convId = yield self._newConversation(myId, participants, meta, attachments) messageId = yield self._newMessage(myId, timeUUID, body, epoch) yield self._deliverMessage(convId, participants, timeUUID, myId) yield db.insert(convId, "mConvMessages", messageId, timeUUID) self._indexMessage(convId, messageId, myOrgId, meta, attachments, body) #XXX: Is this a duplicate batch insert? #yield db.batch_insert(convId, "mConversations", {'meta':meta}) people = base.EntitySet(participants) yield people.fetchData() value = myId data = {"entities": people} data["entities"].update({args['orgId']: args["org"]}) data["orgId"] = args["orgId"] data["convId"] = convId data["message"] = body data["subject"] = subject data["_fromName"] = people[value].basic['name'] users = set(participants) - set([myId]) if users: yield notifications.notify(users, ":NM", myId, timeUUID, **data) if script: request.write("$$.dialog.close('msgcompose-dlg', true);$$.fetchUri('/messages');")
def _reply(self, request): """Commit a new message in reply to an existing conversation. Creates a new message, uploads any attachments, updates the conversation meta info and finally renders the message for the user. Keyword Arguments: convId: conversation id to which this user is replying to. body: The content of the reply. CF Changes: mConversations mConvMessages attachmentVersions """ (appchange, script, args, myId) = yield self._getBasicArgs(request) landing = not self._ajax myOrgId = args['orgId'] convId = utils.getRequestArg(request, 'id') recipients, body, subject, convId = self._parseComposerArgs(request) epoch = int(time.time()) if not convId: raise errors.MissingParams([]) cols = yield db.get_slice(convId, "mConversations", ['meta', 'participants']) cols = utils.supercolumnsToDict(cols) subject = cols['meta'].get('subject', None) participants = cols.get('participants', {}).keys() if not cols: raise errors.InvalidMessage(convId) if myId not in participants: raise errors.MessageAccessDenied(convId) timeUUID = uuid.uuid1().bytes snippet = self._fetchSnippet(body) meta = {'uuid': timeUUID, 'date_epoch': str(epoch), "snippet": snippet} attachments = yield self._handleAttachments(request) attach_meta = self._formatAttachMeta(attachments) messageId = yield self._newMessage(myId, timeUUID, body, epoch) yield self._deliverMessage(convId, participants, timeUUID, myId) yield db.insert(convId, "mConvMessages", messageId, timeUUID) yield db.batch_insert(convId, "mConversations", {'meta': meta, 'attachments': attach_meta}) # Currently, we don't support searching for private messages # self._indexMessage(convId, messageId, myOrgId, meta, attachments, body) #XXX:We currently only fetch the message we inserted. Later we may fetch # all messages delivered since we last rendered the conversation cols = yield db.get_slice(convId, "mConversations") conv = utils.supercolumnsToDict(cols) participants = set(conv['participants']) mids = [messageId] messages = yield db.multiget_slice(mids, "messages", ["meta"]) messages = utils.multiSuperColumnsToDict(messages) participants.update([messages[mid]['meta']['owner'] for mid in messages]) people = base.EntitySet(participants) yield people.fetchData() value = myId data = {"entities": people} data["entities"].update({args['orgId']: args["org"]}) data["orgId"] = args["orgId"] data["convId"] = convId data["message"] = body data["subject"] = subject data["_fromName"] = people[value].basic['name'] users = participants - set([myId]) if users: yield notifications.notify(users, ":MR", value, timeUUID, **data) args.update({"people": people}) args.update({"messageIds": mids}) args.update({'messages': messages}) if script: onload = """ $('.conversation-reply').attr('value', ''); $('#msgreply-attach-uploaded').empty(); """ t.renderScriptBlock(request, "message.mako", "render_conversation_messages", landing, ".conversation-messages-wrapper", "append", True, handlers={"onload": onload}, **args) #Update the right side bar with any attachments the user uploaded args.update({"conv": conv}) people = base.EntitySet(set(conv['participants'])) yield people.fetchData() args.update({"people": people}) args.update({"conv": conv}) args.update({"id": convId}) args.update({"view": "message"}) if script: onload = """ $('#conversation_add_member').autocomplete({ source: '/auto/users', minLength: 2, select: function( event, ui ) { $('#conversation_recipients').attr('value', ui.item.uid) } }); """ t.renderScriptBlock(request, "message.mako", "right", landing, ".right-contents", "set", True, handlers={"onload": onload}, **args) else: request.redirect('/messages') request.finish()
def notifyFollow(res): data = {'entities': entities} return notifications.notify([targetId], ":NF", myId, **data)
def _addUser(self, request): emailId = utils.getRequestArg(request, 'email') existingUser = db.get_count(emailId, "userAuth") localpart, domain = emailId.split("@") displayName = utils.getRequestArg(request, 'name') jobTitle = utils.getRequestArg(request, 'jobTitle') timezone = utils.getRequestArg(request, 'timezone') passwd = utils.getRequestArg(request, 'password', sanitize=False) pwdrepeat = utils.getRequestArg(request, 'pwdrepeat', sanitize=False) if not displayName or not jobTitle or not timezone or not passwd: raise errors.MissingParams([_("All fields are required to create the user")]) if passwd != pwdrepeat: raise PasswordsNoMatch() args = {'emailId': emailId, 'view': 'invite'} existingUser = yield existingUser if not existingUser: authinfo = yield defer.maybeDeferred(request.getSession, IAuthInfo) orgId = yield getOrgId(domain) if not orgId: orgId = utils.getUniqueKey() domains = {domain: ''} basic = {"name": domain, "type": "org"} yield db.batch_insert(orgId, "entities", {"basic": basic, "domains": domains}) yield db.insert(domain, "domainOrgMap", '', orgId) userId = yield utils.addUser(emailId, displayName, passwd, orgId, jobTitle, timezone) authinfo.username = userId authinfo.organization = orgId authinfo.isAdmin = False yield request._saveSessionToDB() cols = yield db.get_slice(domain, "invitations", [emailId]) cols = utils.supercolumnsToDict(cols) userIds = cols.get(emailId, {}).values() if userIds: db.batch_remove({'invitationsSent': userIds}, names=[emailId]) yield db.remove(domain, "invitations", super_column=emailId) t.render(request, "signup.mako", **args) # Notify all invitees about this user. token = utils.getRequestArg(request, "token") acceptedInvitationSender = cols.get(emailId, {}).get(token) otherInvitees = [x for x in userIds if x not in (acceptedInvitationSender, emailId)] entities = base.EntitySet(userIds + [orgId, userId]) yield entities.fetchData() data = {"entities": entities, 'orgId': orgId} yield notifications.notify([acceptedInvitationSender], ":IA", userId, **data) yield notifications.notify(otherInvitees, ":NU", userId, **data) else: raise InvalidRegistration("A user with this e-mail already exists! Already registered?")
def _sendNotifications(ignored): return notifications.notify(recipients, notifyId, myId, timeUUID, **kwargs)
def new(request, authInfo, convType, richText=False): if convType not in plugins: raise errors.BaseError('Unsupported item type', 400) myId = authInfo.username orgId = authInfo.organization entities = base.EntitySet([myId, orgId]) yield entities.fetchData(['basic', 'admins']) plugin = plugins[convType] convId = utils.getUniqueKey() conv = yield plugin.create(request, entities[myId], convId, richText) orgAdminIds = entities[orgId].admins.keys() if orgAdminIds: text = '' monitoredFields = getattr(plugin, 'monitoredFields', {}) for superColumnName in monitoredFields: for columnName in monitoredFields[superColumnName]: if columnName in conv[superColumnName]: text = " ".join([text, conv[superColumnName][columnName]]) matchedKeywords = yield utils.watchForKeywords(orgId, text) if matchedKeywords: reviewOK = utils.getRequestArg(request, "_review") == "1" if reviewOK: # Add conv to list of items that matched this keyword # and notify the administrators about it. orgAdmins = base.EntitySet(orgAdminIds) yield orgAdmins.fetchData() entities.update(orgAdmins) for keyword in matchedKeywords: yield db.insert(orgId + ":" + keyword, "keywordItems", convId, conv['meta']['uuid']) yield notifications.notify(orgAdminIds, ':KW:' + keyword, myId, entities=entities) else: # This item contains a few keywords that are being monitored # by the admin and cannot be posted unless reviewOK is set. defer.returnValue((None, None, matchedKeywords)) # # Save the new item to database and index it. # yield db.batch_insert(convId, "items", conv) yield files.pushfileinfo(myId, orgId, convId, conv) search.solr.updateItemIndex(convId, conv, orgId) # # Push item to feeds and userItems # timeUUID = conv["meta"]["uuid"] convACL = conv["meta"]["acl"] deferreds = [] responseType = 'I' # Push to feeds feedUpdateVal = "I:%s:%s" % (myId, convId) d = Feed.push(myId, orgId, convId, conv, timeUUID, feedUpdateVal, promoteActor=True) deferreds.append(d) # Save in user items. userItemValue = ":".join([responseType, convId, convId, convType, myId, '']) d = db.insert(myId, "userItems", userItemValue, timeUUID) deferreds.append(d) if plugins[convType].hasIndex: d = db.insert(myId, "userItems_%s" % (convType), userItemValue, timeUUID) deferreds.append(d) yield defer.DeferredList(deferreds) defer.returnValue((convId, conv, None))
def _comment(convId, conv, comment, snippet, myId, orgId, richText, reviewed, fids=None): convType = conv["meta"].get("type", "status") # 1. Create the new item timeUUID = uuid.uuid1().bytes meta = {"owner": myId, "parent": convId, "comment": comment, "timestamp": str(int(time.time())), "org": orgId, "uuid": timeUUID, "richText": str(richText)} item = {'meta':meta} followers = {myId: ''} itemId = utils.getUniqueKey() if snippet: meta['snippet'] = snippet # 1.5. Check if the comment matches any of the keywords entities = base.EntitySet([myId, orgId]) yield entities.fetchData(['basic', 'admins']) orgAdmins = entities[orgId].admins.keys() if orgAdmins: matchedKeywords = yield utils.watchForKeywords(orgId, comment) if matchedKeywords: if reviewed: # Add item to list of items that matched this keyword # and notify the administrators about it. for keyword in matchedKeywords: yield db.insert(orgId + ":" + keyword, "keywordItems", itemId + ":" + convId, timeUUID) yield notifications.notify(orgAdmins, ':KW:' + keyword, myId, entities=entities) else: # This item contains a few keywords that are being monitored # by the admin and cannot be posted unless reviewOK is set. defer.returnValue((None, convId, {convId: conv}, matchedKeywords)) # 1.9. Actually store the item if fids: attachments = yield utils._upload_files(myId, fids) if attachments: item['attachments'] = {} for attachmentId in attachments: fileId, name, size, ftype = attachments[attachmentId] item["attachments"][attachmentId] = "%s:%s:%s" % (name, size, ftype) yield db.batch_insert(itemId, "items", item) yield files.pushfileinfo(myId, orgId, itemId, item, conv) # 2. Update response count and add myself to the followers of conv convOwnerId = conv["meta"]["owner"] convType = conv["meta"]["type"] responseCount = int(conv["meta"].get("responseCount", "0")) if responseCount % 5 == 3: responseCount = yield db.get_count(convId, "itemResponses") responseCount += 1 conv['meta']['responseCount'] = responseCount convUpdates = {"responseCount": str(responseCount)} yield db.batch_insert(convId, "items", {"meta": convUpdates, "followers": followers}) # 3. Add item as response to parent yield db.insert(convId, "itemResponses", "%s:%s" % (myId, itemId), timeUUID) # 4. Update userItems and userItems_* responseType = "Q" if convType == "question" else 'C' commentSnippet = utils.toSnippet(comment, 35, richText) userItemValue = ":".join([responseType, itemId, convId, convType, convOwnerId, commentSnippet]) yield db.insert(myId, "userItems", userItemValue, timeUUID) if convType in plugins and plugins[convType].hasIndex: yield db.insert(myId, "userItems_" + convType, userItemValue, timeUUID) # 5. Update my feed. feedItemVal = "%s:%s:%s" % (responseType, myId, itemId) yield Feed.push(myId, orgId, convId, conv, timeUUID, feedItemVal) yield _notify("C", convId, timeUUID, convType=convType, convOwnerId=convOwnerId, myId=myId, me=entities[myId], comment=comment, richText=richText) search.solr.updateItemIndex(itemId, {'meta': meta}, orgId, conv=conv) items = {itemId: item, convId: conv} defer.returnValue((itemId, convId, items, None))