def delete(self, myId, convId, conv): log.debug("plugin:delete", convId) user_tuids = {} # Get the list of every user who responded to this event res = yield db.get_slice(convId, "eventResponses") attendees = [x.column.name.split(":", 1)[1] for x in res] # Add all the invited people of the item res = yield db.get_slice(convId, "items", ['invitees']) res = utils.supercolumnsToDict(res) attendees.extend(res["invitees"].keys()) invitedPeople = res["invitees"].keys() log.debug("Maps", ["%s:%s"%(uId, convId) for \ uId in attendees]) # Get the Org and GroupIds if any. convMeta = conv["meta"] groupIds = convMeta["target"].split(",") if "target" in convMeta else [] attendees.extend(groupIds+[convMeta["org"]]) log.debug("Attendees", attendees) # Get the timeuuids that were inserted for this user res = yield db.multiget_slice(["%s:%s"%(uId, convId) for \ uId in attendees], "userAgendaMap") res = utils.multiColumnsToDict(res) for k, v in res.iteritems(): uid = k.split(":", 1)[0] tuids = v.keys() if tuids: user_tuids[uid] = tuids log.debug("userAgenda Removal", user_tuids) # Delete their entries in the user's list of event entries for attendee in user_tuids: yield db.batch_remove({'userAgenda': [attendee]}, names=user_tuids[attendee]) # Delete invitation entries for invited people invited_tuids = dict([[x, user_tuids[x]] for x in invitedPeople]) log.debug("userAgenda Invitation Removal", invited_tuids) for attendee in invited_tuids: yield db.batch_remove({'userAgenda': ['%s:%s' %(attendee, 'I')]}, names=invited_tuids[attendee]) log.debug("eventResponses Removal", convId) # Delete the event's entry in eventResponses yield db.remove(convId, "eventResponses") log.debug("userAgendaMap Removal", user_tuids) # Delete their entries in userAgendaMap for attendee in user_tuids: yield db.batch_remove({'userAgendaMap': ["%s:%s"%(attendee, convId)]}, names=user_tuids[attendee])
def removeConvFromFeed(x): timestamps = [col.column.name for col in x] if timestamps: yield db.batch_remove({'feed': [feedId]}, names=timestamps) else: yield db.remove(feedId, 'feed', conv['meta']['uuid']) yield db.remove(feedId, 'feedItems', super_column=convId)
def _cleanupMissingComments(convId, missingIds, itemResponses): missingKeys = [] for response in itemResponses: userKey, responseKey = response.column.value.split(':') if responseKey in missingIds: missingKeys.append(response.column.name) d1 = db.batch_remove({'itemResponses': [convId]}, names=missingKeys) d1.addCallback(lambda x: db.get_count(convId, "itemResponses")) d1.addCallback(lambda x: db.insert(convId, 'items', \ str(x), 'responseCount', 'meta')) return d1
def _verifyProfile(self, request): email = utils.getRequestArg(request, 'email') token = utils.getRequestArg(request, 'token') if not (email and token): raise MissingParams(['Email', 'Account Verification Token']) cols = yield db.get_slice(email, "userAuth", ["reactivateToken", "isFlagged"]) cols = utils.columnsToDict(cols) if "isFlagged" in cols: storedToken = cols.get("reactivateToken", None) if storedToken == token: yield db.batch_remove({"userAuth": [email]}, names=["reactivateToken", "isFlagged"]) request.redirect('/signin')
def _editWorkInfo(self, request): # Contact information at work. myId = request.getSession(IAuthInfo).username orgId = request.getSession(IAuthInfo).organization me = base.Entity(myId) yield me.fetchData([]) data = {} to_remove = [] for field in ["phone", "mobile"]: val = utils.getRequestArg(request, field) if val: data[field] = val else: to_remove.append(field) if 'phone' in data and not re.match('^\+?[0-9x\- ]{5,20}$', data['phone']): raise errors.InvalidRequest(_('Phone numbers can only have numerals, hyphens, spaces and a plus sign')) if 'mobile' in data and not re.match('^\+?[0-9x\- ]{5,20}$', data['mobile']): raise errors.InvalidRequest(_('Phone numbers can only have numerals, hyphens, spaces and a plus sign')) if data: yield db.batch_insert(myId, "entities", {"contact": data}) if to_remove: yield db.batch_remove({"entities":[myId]}, names=to_remove, supercolumn='contact') contactInfo = me.get('contact', {}) if any([contactInfo.get(x, None) != data.get(x, None) for x in ["phone", "mobile"]]): request.write('$$.alerts.info("%s");' % _('Profile updated')) args = {"detail": "", "me": me} suggestedSections = yield self._checkProfileCompleteness(request, myId, args) tmp_suggested_sections = {} for section, items in suggestedSections.iteritems(): if len(suggestedSections[section]) > 0: tmp_suggested_sections[section] = items args.update({'suggested_sections':tmp_suggested_sections}) t.renderScriptBlock(request, "settings.mako", "right", False, ".right-contents", "set", **args) me.update({'contact':data}) yield search.solr.updatePeopleIndex(myId, me, orgId)
def resetPassword(self, request): email = utils.getRequestArg(request, 'email') token = utils.getRequestArg(request, 'token') passwd = utils.getRequestArg(request, 'password', False) pwdrepeat = utils.getRequestArg(request, 'pwdrepeat', False) if not (email and token and passwd and pwdrepeat): raise MissingParams(['Email', 'Password Reset Token']) if (passwd != pwdrepeat): raise errors.PasswordsNoMatch() validEmail, tokens, deleteTokens, leastTimestamp = yield _getResetPasswordTokens(email) if validEmail: if token not in tokens: raise PermissionDenied("Invalid token. <a href='/password/resend?email=%s'>Click here</a> to reset password" % (email)) yield db.insert(email, "userAuth", utils.hashpass(passwd), 'passwordHash') yield db.batch_remove({"userAuth": [email]}, names=deleteTokens) request.redirect('/signin')
def cleaner(convIds): deleteKeys = [] for key, value in itemsFromFeed.items(): if value in deleted: deleteKeys.append(key) yield db.batch_remove({'tagItems': [tagId]}, names=deleteKeys)
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 get(auth, feedId=None, feedItemsId=None, convIds=None, getFn=None, cleanFn=None, start='', count=10, getReasons=True, forceCount=False, itemType=None): """Fetch data from feed represented by feedId. Returns a dictionary that has the items from feed, start of the next page and responses and likes that I know of. Keyword params: @auth - An instance of AuthInfo representing the authenticated user @feedId - Id of the feed from which the data is to be fetched @feedItemsId - Id of the feed from with feed items must be fetched @convIds - List of conversation id's to be used as feed @getFn - Function that must be used to fetch items @cleanFn - Function that must be used to clean items that don't exist @start - Id of the item where the fetching must start @count - Number of items to fetch @getReasons - Add reason strings to the returned dictionary @forceCount - Try hard to get atleast count items from the feed """ toFetchItems = set() # Items and entities that need to be fetched toFetchEntities = set() # toFetchTags = set() # items = {} # Fetched items, entities and tags entities = base.EntitySet([])# tags = {} # deleted = [] # List of items that were deleted deleteKeys = [] # List of keys that need to be deleted responses = {} # Cached data of item responses and likes likes = {} # myLikes = {} myId = auth.username orgId = auth.organization feedSource = "feed_%s" % itemType\ if itemType and itemType in plugins\ and plugins[itemType].hasIndex\ else "feed" feedItemsId = feedItemsId or myId feedItems_d = [] # List of deferred used to fetch feedItems # Used for checking ACL relation = Relation(myId, []) yield relation.initGroupsList() # The updates that will be used to build reason strings convReasonUpdates = {} # Data that is sent to various plugins and returned by this function # XXX: myKey is depricated - use myId data = {"myId": myId, "orgId": orgId, "responses": responses, "likes": likes, "myLikes": myLikes, "items": items, "entities": entities, "tags": tags, "myKey": myId, "relations": relation} @defer.inlineCallbacks def fetchFeedItems(ids): rawFeedItems = yield db.get_slice(feedItemsId, "feedItems", ids) \ if ids else defer.succeed([]) for conv in rawFeedItems: convId = conv.super_column.name convUpdates = conv.super_column.columns responses[convId] = [] likes[convId] = [] latest = None updatesByType = {} for update in convUpdates: parts = update.value.split(':') updatesByType.setdefault(parts[0], []).append(parts) if parts[1] != myId: # Ignore my own updates latest = parts # when displaying latest actors # Parse all notification to make sure we fetch any required # items, entities. and cache generic stuff that we display for tipe in updatesByType.keys(): updates = updatesByType[tipe] if tipe in _feedUpdatePlugins: (i,e) = _feedUpdatePlugins[tipe].parse(convId, updates) toFetchItems.update(i) toFetchEntities.update(e) if tipe == "L": for update in updates: if update[2] == convId: likes[convId].append(update[1]) # XXX: Adding this item may break the sorting # of responses on this conversation # Bug #493 #else: # responses[convId].append(update[2]) elif tipe in ["C", "Q"]: for update in updates: responses[convId].append(update[2]) # Store any information that can be used to render # the reason strings when we have the required data if getReasons and latest: convReasonUpdates[convId] = updatesByType[latest[0]] # Fetch the feed if required and at the same time make sure # we delete unwanted items from the feed (cleanup). # NOTE: We assume that there will be very few deletes. nextPageStart = None if not convIds: feedId = feedId or myId allFetchedConvIds = set() # Complete set of convIds fetched itemsFromFeed = {} # All the key-values retrieved from feed keysFromFeed = [] # Sorted list of keys (used for paging) convIds = [] # List of convIds that will be displayed fetchStart = utils.decodeKey(start) fetchCount = count + 1 while len(convIds) < count: fetchedConvIds = [] # Use the getFn function if given. # NOTE: Part of this code is duplicated just below this. if getFn: results = yield getFn(start=fetchStart, count=fetchCount) for name, value in results.items(): keysFromFeed.append(name) if value not in allFetchedConvIds: fetchedConvIds.append(value) allFetchedConvIds.add(value) itemsFromFeed[name] = value else: deleteKeys.append(name) # Fetch user's feed when getFn isn't given. # NOTE: Part of this code is from above else: results = yield db.get_slice(feedId, feedSource, count=fetchCount, start=fetchStart, reverse=True) for col in results: value = col.column.value keysFromFeed.append(col.column.name) if value not in allFetchedConvIds: fetchedConvIds.append(value) allFetchedConvIds.add(value) itemsFromFeed[col.column.name] = value else: deleteKeys.append(col.column.name) # Initiate fetching feed items for all the conversation Ids. # Meanwhile we check if the authenticated user has access to # all the fetched conversation ids. # NOTE: Conversations would rarely be filtered out. So, we # just go ahead with fetching data for all convs. feedItems_d.append(fetchFeedItems(fetchedConvIds)) (filteredConvIds, deletedIds) = yield utils.fetchAndFilterConvs\ (fetchedConvIds, relation, items, myId, orgId) convIds.extend(filteredConvIds) deleted.extend(deletedIds) # Unless we are forced to fetch count number of items, we only # iterate till we fetch atleast half of them if (not forceCount and len(convIds) > (count/2)) or\ len(results) < fetchCount: break # If we need more items, we start fetching from where we # left in the previous iteration. fetchStart = keysFromFeed[-1] # If DB fetch got as many items as I requested # there may be additional items present in the feed # So, we cut one item from what we return and start the # next page from there. if len(results) == fetchCount: lastConvId = convIds[-1] for key in reversed(keysFromFeed): if key in itemsFromFeed and itemsFromFeed[key] == lastConvId: nextPageStart = utils.encodeKey(key) convIds = convIds[:-1] else: (convIds, deletedIds) = yield utils.fetchAndFilterConvs(convIds, relation, items, myId, orgId) # NOTE: Unlike the above case where we fetch convIds from # database (where we set the nextPageStart to a key), # here we set nextPageStart to the convId. if len(convIds) > count: nextPageStart = utils.encodeKey(convIds[count]) convIds = convIds[0:count] # Since convIds were directly passed to us, we would also # return the list of convIds deleted back to the caller. if deletedIds: data["deleted"] = deletedIds # We don't have any conversations to display! if not convIds: defer.returnValue({"conversations": []}) # Delete any convs that were deleted from the feeds and # any duplicates that were marked for deletion cleanup_d = [] if deleted: for key, value in itemsFromFeed.items(): if value in deleted: deleteKeys.append(key) if cleanFn: d1 = cleanFn(list(deleteKeys)) else: d1 = db.batch_remove({feedSource: [feedId]}, names=deleteKeys) d2 = db.batch_remove({'feedItems': [feedId]}, names=list(deleted)) cleanup_d = [d1, d2] # We now have a filtered list of conversations that can be displayed # Let's wait till all the feed items have been fetched and processed yield defer.DeferredList(feedItems_d) # Fetch the remaining items (comments on the actual conversations) items_d = db.multiget_slice(toFetchItems, "items", ["meta" ,"attachments"]) # Fetch tags on all the conversations that will be displayed for convId in convIds: conv = items[convId] toFetchEntities.add(conv["meta"]["owner"]) if "target" in conv["meta"]: toFetchEntities.update(conv["meta"]["target"].split(',')) toFetchTags.update(conv.get("tags",{}).keys()) tags_d = db.get_slice(orgId, "orgTags", toFetchTags) \ if toFetchTags else defer.succeed([]) # Fetch the list of my likes. # XXX: Latency can be pretty high here becuase many nodes will have to # be contacted for the information. Alternative could be to cache # all likes by a user somewhere. myLikes_d = db.multiget(toFetchItems.union(convIds), "itemLikes", myId) # Fetch extra data that is required to render special items # We already fetched the conversation items, plugins merely # add more data to the already fetched items for convId in convIds[:]: itemType = items[convId]["meta"]["type"] if itemType in plugins: try: entityIds = yield plugins[itemType].fetchData(data, convId) toFetchEntities.update(entityIds) except Exception, e: log.err(e) convIds.remove(convId)
def _getKeywordMatches(self, request, keyword, start='', count=10): args = {} authinfo = request.getSession(IAuthInfo) myId = authinfo.username orgId = authinfo.organization items = {} itemIds = [] itemIdKeyMap = {} allFetchedItems = set() deleted = set() fetchStart = utils.decodeKey(start) fetchCount = count + 2 while len(itemIds) < count: fetchedItemIds = [] toFetchItems = set() results = yield db.get_slice(orgId + ":" + keyword, "keywordItems", count=fetchCount, start=fetchStart, reverse=True) for col in results: fetchStart = col.column.name itemAndParentIds = col.column.value.split(':') itemIdKeyMap[itemAndParentIds[0]] = fetchStart fetchedItemIds.append(itemAndParentIds[0]) for itemId in itemAndParentIds: if itemId not in allFetchedItems: toFetchItems.add(itemId) allFetchedItems.add(itemId) if toFetchItems: fetchedItems = yield db.multiget_slice(toFetchItems, "items", ["meta", "attachments"]) fetchedItems = utils.multiSuperColumnsToDict(fetchedItems) items.update(fetchedItems) for itemId in fetchedItemIds: item = items[itemId] if not 'meta' in item: continue state = item['meta'].get('state', 'published') if state == 'deleted': deleted.add(itemIdKeyMap[itemId]) elif utils.checkAcl(myId, orgId, True, None, item['meta']): itemIds.append(itemId) if len(results) < fetchCount: break if len(itemIds) > count: nextPageStart = utils.encodeKey(itemIdKeyMap[itemIds[-1]]) itemIds = itemIds[:-1] else: nextPageStart = None dd = db.batch_remove({'keywordItems': [orgId + ':' + keyword]}, names=deleted) if deleted else defer.succeed([]) args.update({'items': items, 'myId': myId}) toFetchEntities = set() extraDataDeferreds = [] for itemId in itemIds: item = items[itemId] itemMeta = item['meta'] toFetchEntities.add(itemMeta['owner']) if 'target' in itemMeta: toFetchEntities.update(itemMeta['target'].split(',')) if 'parent' in itemMeta: parentId = itemMeta['parent'] if parentId in items: toFetchEntities.add(items[parentId]['meta']['owner']) itemType = itemMeta.get('type', 'status') if itemType in plugins: d = plugins[itemType].fetchData(args, itemId) extraDataDeferreds.append(d) result = yield defer.DeferredList(extraDataDeferreds) for success, ret in result: if success: toFetchEntities.update(ret) fetchedEntities = {} if toFetchEntities: fetchedEntities = base.EntitySet(toFetchEntities) yield fetchedEntities.fetchData() yield dd args.update({'entities': fetchedEntities, 'matches': itemIds, 'nextPageStart': nextPageStart}) defer.returnValue(args)
def _submitReport(self, request, action): (appchange, script, args, myId) = yield self._getBasicArgs(request) landing = not self._ajax snippet, comment = utils.getTextWithSnippet(request, "comment", constants.COMMENT_PREVIEW_LENGTH) orgId = args['orgId'] isNewReport = False timeUUID = uuid.uuid1().bytes convId, conv = yield utils.getValidItemId(request, "id") convMeta = conv["meta"] convOwnerId = convMeta["owner"] convType = convMeta["type"] convACL = convMeta["acl"] toFetchEntities = set([myId, convOwnerId]) if "reportId" in convMeta: reportId = convMeta["reportId"] isNewReport = False toFetchEntities.add(convMeta['reportedBy']) else: isNewReport = True reportId = utils.getUniqueKey() if isNewReport and convOwnerId == myId: raise errors.InvalidRequest(_("You cannot report your own Item. \ Delete the item instead")) toFetchEntities.remove(myId) entities = base.EntitySet(toFetchEntities) yield entities.fetchData() entities.update({myId: args["me"]}) if myId == convOwnerId: if action not in ["accept", "repost"]: raise errors.InvalidRequest(_('Invalid action was performed on the report')) convReport = {"reportStatus": action} yield db.batch_insert(convId, "items", {"meta": convReport}) if action == "accept": # Owner removed the comment. Delete the item from his feed yield Item.deleteItem(convId, myId, orgId) request.write("$$.fetchUri('/feed/');") request.write("$$.alerts.info('%s')" % _("Your item has been deleted")) request.finish() else: # Owner posted a reply, so notify reporter of the same yield Item._notify("RFC", convId, timeUUID, convType=convType, convOwnerId=convOwnerId, myId=myId, entities=entities, me=args["me"], reportedBy=convMeta["reportedBy"]) else: if action not in ["report", "repost", "reject"]: raise errors.InvalidRequest(_('Invalid action was performed on the report')) if isNewReport: # Update Existing Item Information with Report Meta newACL = pickle.dumps({"accept": {"users": [convOwnerId, myId]}}) convReport = {"reportedBy": myId, "reportId": reportId, "reportStatus": "pending", "state": "flagged"} convMeta.update(convReport) yield db.batch_insert(convId, "items", {"meta": convReport}) reportLink = """·<a class="button-link" title="View Report" href="/item/report?id=%s"> View Report</a>""" % convId request.write("""$("#item-footer-%s").append('%s');""" % (convId, reportLink)) yield Item._notify("FC", convId, timeUUID, convType=convType, entities=entities, convOwnerId=convOwnerId, myId=myId, me=args["me"]) else: if action == "repost": # Remove the reportId key, so owner cannot post any comment yield db.batch_remove({'items': [convId]}, names=["reportId", "reportStatus", "reportedBy", "state"], supercolumn='meta') oldReportMeta = {"reportedBy": convMeta["reportedBy"], "reportId": reportId} # Save the now resolved report in items and remove its # reference in the item meta so new reporters wont't see # old reports timestamp = str(int(time.time())) yield db.insert(convId, "items", reportId, timestamp, "reports") yield db.batch_insert(reportId, "items", {"meta": oldReportMeta}) # Notify the owner that the report has been withdrawn yield Item._notify("UFC", convId, timeUUID, convType=convType, convOwnerId=convOwnerId, myId=myId, entities=entities, me=args["me"]) elif action in ["reject", "report"]: # Reporter rejects the comment by the owner or reports the # same item again. convReport = {"reportStatus": "pending"} yield Item._notify("RFC", convId, timeUUID, convType=convType, convOwnerId=convOwnerId, myId=myId, entities=entities, me=args["me"], reportedBy=convMeta["reportedBy"]) yield db.batch_insert(convId, "items", {"meta": convReport}) args.update({"entities": entities, "ownerId": convOwnerId, "convId": convId}) # Update New Report comment Details commentId = utils.getUniqueKey() timeUUID = uuid.uuid1().bytes meta = {"owner": myId, "parent": reportId, "comment": comment, "timestamp": str(int(time.time())), "uuid": timeUUID, "richText": str(False)} if snippet: meta['snippet'] = snippet yield db.batch_insert(commentId, "items", {'meta': meta}) # Update list of comments for this report yield db.insert(reportId, "itemResponses", "%s:%s:%s" % (myId, commentId, action), timeUUID) yield self._renderReportResponses(request, convId, convMeta, args) request.write("$('#report-comment').attr('value', '')")
def updateFeedResponses(userKey, parentKey, itemKey, timeuuid, itemType, responseType, convOwner, commentOwner, tagId, entities, promote): if not entities: entities = [commentOwner] else: entities.extend([commentOwner]) entities = ",".join(entities) feedItemValue = ":".join([responseType, commentOwner, itemKey, entities, tagId]) tmp, oldest, latest = {}, None, None cols = yield db.get_slice(userKey, "feedItems", super_column=parentKey, reverse=True) cols = utils.columnsToDict(cols, ordered=True) feedKeys = [] userFeedItems = [] userFeedItemsByType = {} for tuuid, val in cols.items(): # Bailout if we already know about this update. if tuuid == timeuuid: defer.returnValue(None) rtype = val.split(':')[0] if rtype not in ('!', 'I'): tmp.setdefault(rtype, []).append(tuuid) if val.split(':')[1] == userKey: userFeedItems.append(tuuid) userFeedItemsByType.setdefault(rtype, []).append(tuuid) oldest = tuuid feedKeys.append(tuuid) # Remove older entries of this conversation from the feed # only if a new one was added before this function was called. if promote and feedKeys: yield db.batch_remove({'feed': [userKey]}, names=feedKeys) totalItems = len(cols) noOfItems = len(tmp.get(responseType, [])) if noOfItems == MAXFEEDITEMSBYTYPE: if (len(userFeedItemsByType.get(responseType, {})) == MAXFEEDITEMSBYTYPE and not promote)\ or (tmp[responseType][noOfItems-1] not in userFeedItemsByType.get(responseType, {}) \ and len(userFeedItemsByType.get(responseType, {})) == MAXFEEDITEMSBYTYPE-1 and not promote): oldest = userFeedItemsByType[responseType][noOfItems-2] else: oldest = tmp[responseType][noOfItems-1] if ((len(userFeedItems)== MAXFEEDITEMS-1 and not promote) or \ (oldest not in userFeedItems and len(userFeedItems) == MAXFEEDITEMS-2 and not promote)): oldest = userFeedItems[-2] if noOfItems == MAXFEEDITEMSBYTYPE or totalItems == MAXFEEDITEMS: yield db.remove(userKey, "feedItems", oldest, parentKey) if plugins.has_key(itemType) and plugins[itemType].hasIndex: yield db.remove(userKey, "feed_"+itemType, oldest) if totalItems == 0 and responseType != 'I': value = ":".join(["!", convOwner, parentKey, ""]) tuuid = uuid.uuid1().bytes yield db.batch_insert(userKey, "feedItems", {parentKey:{tuuid:value}}) yield db.batch_insert(userKey, "feedItems", {parentKey:{timeuuid: feedItemValue}})
def migrateFriendsToFollowers(): # Migrate all friends to followers/subscriptions. connectionRows = yield db.get_range_slice('connections', count=10000) for connectionRow in connectionRows: userId = connectionRow.key friends = [x.super_column.name for x in connectionRow.columns] yield db.batch_insert(userId, "followers", dict([(x, '') for x in friends])) yield db.batch_mutate(dict([(x, {'subscriptions': {userId: ''}}) for x in friends])) log.msg('>>>>>>>> Converted all connections to following.') # Remove name indices of friends entityRows = yield db.get_range_slice('entities', count=10000, names=['basic']) entities = dict([(x.key, utils.supercolumnsToDict(x.columns)) for x in entityRows]) userIds = [x for x in entities.keys() if entities[x]['basic']['type'] == 'user'] for userId in userIds: yield db.remove(userId, 'displayNameIndex') yield db.remove(userId, 'nameIndex') log.msg('>>>>>>>> Removed name indices for friends.') # Convert all "connection" activity to "follow". # We already have two separate items, so subtype conversion should be good. itemRows = yield db.get_range_slice('items', count=10000, names=['meta']) items = dict([(x.key, utils.supercolumnsToDict(x.columns)) for x in itemRows]) connectionItems = [x for x in items.keys()\ if items[x]['meta'].get('type', '') == 'activity'\ and items[x]['meta']['subType'] == 'connection'] yield db.batch_mutate(dict([(x, {'items':{'meta':{'subType':'following'}}}) for x in connectionItems])) log.msg('>>>>>>>> All connection items converted to following.') # Remove all friend requests from pendingConnections pendingRows = yield db.get_range_slice('pendingConnections', count=10000) for pendingRow in pendingRows: userId = pendingRow.key pendingFriendRequestIds = [x.column.name for x in pendingRow.columns \ if not x.column.name.startswith('G')] if pendingFriendRequestIds: yield db.batch_remove({'pendingConnections': [userId]}, names=pendingFriendRequestIds) log.msg('>>>>>>>> Removed pending friend requests.') # Remove all friend requests from latest yield db.batch_remove({'latest': userIds}, names='people') log.msg('>>>>>>>> Removed friend requests from latest.') # Remove all friend-request-accepted notifications notifyMutations = {} for userId in userIds: items = yield db.get_slice(userId, "notificationItems", super_column=':FA') if items: names = [col.column.name for col in items] colmap = dict([(x, None) for x in names]) deletion = Deletion(time.time() * 1000000, 'notifications', SlicePredicate(column_names=names)) notifyMutations[userId] = {'notifications': colmap, 'latest': [deletion]} yield db.remove(userId, 'notificationItems', super_column=':FA') if notifyMutations: yield db.batch_mutate(notifyMutations) log.msg('>>>>>>>> Removed friend notifications from notifications and latest.') # Finally, remove the connections column family. yield db.system_drop_column_family('connections') yield db.system_drop_column_family('connectionsByTag') log.msg('>>>>>>>> Removed the connections column family.')
def _editBasicInfo(self, request): authInfo = request.getSession(IAuthInfo) myId = authInfo.username orgId = authInfo.organization userInfo = {"basic":{}} to_remove = [] basicUpdatedInfo, basicUpdated = {}, False me = base.Entity(myId) yield me.fetchData([]) # Check if any basic information is being updated. for cn in ("jobTitle", "name", "firstname", "lastname", "timezone"): val = utils.getRequestArg(request, cn) if not val and cn in ['name', 'jobTitle', 'timezone']: request.write("<script>parent.$$.alerts.error('One or more required parameters are missing')</script>") raise errors.MissingParams(_([cn])) if val: userInfo["basic"][cn] = val basicUpdatedInfo[cn] = val elif cn in ["firstname", "lastname"]: to_remove.append(cn) basicUpdatedInfo[cn] = "" if me.basic.get(cn, None) != userInfo['basic'].get(cn, None): basicUpdated = True # Update name indicies of organization. nameIndexKeys = [orgId] nameIndicesDeferreds = [] oldNameParts = [] newNameParts = [] for field in ["name", "lastname", "firstname"]: if field in basicUpdatedInfo: newNameParts.extend(basicUpdatedInfo[field].split()) oldNameParts.extend(me.basic.get(field, '').split()) if field == 'name': d1 = utils.updateDisplayNameIndex(myId, nameIndexKeys, basicUpdatedInfo[field], me.basic.get(field, None)) nameIndicesDeferreds.append(d1) d = utils.updateNameIndex(myId, nameIndexKeys, " ".join(newNameParts), " ".join(oldNameParts)) nameIndicesDeferreds.append(d) # Avatar (display picture) dp = utils.getRequestArg(request, "dp", sanitize=False) if dp: avatar = yield saveAvatarItem(myId, orgId, dp) userInfo["basic"]["avatar"] = avatar me.basic["avatar"] = avatar avatarURI = utils.userAvatar(myId, me) basicUpdatedInfo["avatar"] = avatarURI basicUpdated = True if userInfo["basic"]: yield db.batch_insert(myId, "entities", userInfo) me.basic.update(userInfo['basic']) yield search.solr.updatePeopleIndex(myId, me, orgId) if to_remove: yield db.batch_remove({'entities':[myId]}, names=to_remove, supercolumn='basic') if basicUpdated: response = """ <script> var data = %s; if (data.avatar){ var imageUrl = data.avatar; parent.$('#avatar').css('background-image', 'url(' + imageUrl + ')'); } parent.$('#name').html(data.name + ', ' + data.jobTitle); parent.$$.alerts.info("%s"); </script> """ % (json.dumps(basicUpdatedInfo), _("Profile updated")) request.write(response) # Wait for name indices to be updated. if nameIndicesDeferreds: yield defer.DeferredList(nameIndicesDeferreds) args = {"detail": "", "me": me} suggestedSections = yield self._checkProfileCompleteness(request, myId, args) tmp_suggested_sections = {} for section, items in suggestedSections.iteritems(): if len(suggestedSections[section]) > 0: tmp_suggested_sections[section] = items args.update({'suggested_sections':tmp_suggested_sections}) t.renderScriptBlock(request, "settings.mako", "right", False, ".right-contents", "set", **args)
def _deliverMessage(self, convId, recipients, timeUUID, owner): """To each participant in a conversation, add the conversation to the list of unread conversations. CF Changes: mConversations mConvFolders messages mAllConversations mDeletedConversations mArchivedConversations latest """ convFolderMap = {} userFolderMap = {} toNotify = {} toRemove = {'latest': []} conv = yield db.get_slice(convId, "mConversations", ['meta']) conv = utils.supercolumnsToDict(conv) oldTimeUUID = conv['meta']['uuid'] unread = "u:%s" % (convId) read = "r:%s" % (convId) cols = yield db.get_slice(convId, 'mConvFolders', recipients) cols = utils.supercolumnsToDict(cols) for recipient in recipients: deliverToInbox = True # for a new message, mConvFolders will be empty # so recipient may not necessarily be present in cols if recipient != owner: toNotify[recipient] = {'latest': {'messages': {timeUUID: convId}}} toRemove['latest'].append(recipient) for folder in cols.get(recipient, []): cf = self._folders[folder] if folder in self._folders else folder yield db.remove(recipient, cf, oldTimeUUID) if cf == 'mDeletedConversations': #don't add to recipient's inbox if the conv is deleted. deliverToInbox = False else: yield db.remove(convId, 'mConvFolders', folder, recipient) if deliverToInbox: val = unread if recipient != owner else read convFolderMap[recipient] = {'mAllConversations': {timeUUID: val}} userFolderMap[recipient] = {'mAllConversations': ''} if recipient != owner: convFolderMap[recipient]['mUnreadConversations'] = {timeUUID: unread} userFolderMap[recipient]['mUnreadConversations'] = '' else: val = unread if recipient != owner else read convFolderMap[recipient] = {'mDeletedConversations': {timeUUID: val}} yield db.batch_mutate(convFolderMap) yield db.batch_insert(convId, "mConvFolders", userFolderMap) if toRemove['latest'] and oldTimeUUID != timeUUID: yield db.batch_remove(toRemove, names=[oldTimeUUID], supercolumn="messages") if toNotify: yield db.batch_mutate(toNotify)
def _rsvp(self, request): (appchange, script, args, myId) = yield self._getBasicArgs(request) landing = not self._ajax orgId = args['orgId'] response = utils.getRequestArg(request, 'response') deferreds = [] prevResponse = "" if not response or response not in ('yes', 'maybe', 'no'): raise errors.InvalidRequest() convId, conv = yield utils.getValidItemId(request, "id", columns=["invitees"]) if not conv: raise errors.MissingParams([_("Event ID")]) if ("type" in conv["meta"] and conv["meta"]["type"] != "event"): raise errors.InvalidRequest("Not a valid event") #Find out if already stored response is the same as this one. Saves # quite a few queries rsvp_names = ["%s:%s" %(x, myId) for x in ['yes', 'no', 'maybe']] cols = yield db.get_slice(convId, "eventResponses", names=rsvp_names) if cols: prevResponse = cols[0].column.name.split(":", 1)[0] if prevResponse == response: defer.returnValue(0) starttime = int(conv["meta"]["event_startTime"]) endtime = int(conv["meta"]["event_endTime"]) starttimeUUID = utils.uuid1(timestamp=starttime) starttimeUUID = starttimeUUID.bytes endtimeUUID = utils.uuid1(timestamp=endtime) endtimeUUID = endtimeUUID.bytes #Now insert the event in the user's agenda list if the user has # never responded to this event or the user is not in the invited list. #In the second case the agenda was already updated when creating the # event if prevResponse == "" and myId not in conv["invitees"].keys(): d1 = db.insert(myId, "userAgenda", convId, starttimeUUID) d2 = db.insert(myId, "userAgenda", convId, endtimeUUID) d3 = db.insert("%s:%s" %(myId, convId), "userAgendaMap", "", starttimeUUID) d4 = db.insert("%s:%s" %(myId, convId), "userAgendaMap", "", endtimeUUID) deferreds.extend([d1, d3, d2, d4]) #Remove any old responses to this event by this user. yield db.batch_remove({'eventResponses': [convId]}, names=rsvp_names) #Now insert the user's new response. d = db.insert(convId, "eventResponses", "", "%s:%s" %(response, myId)) deferreds.append(d) if script: #Update the inline status of the rsvp status if response == "yes": rsp = _("You are attending") elif response == "no": rsp = _("You are not attending") elif response == "maybe": rsp = _("You may attend") request.write("$('#event-rsvp-status-%s').text('%s');" %(convId, rsp)) request.write("$('#conv-%s .event-join-decline').text('%s');" %(convId, rsp)) if deferreds: res = yield defer.DeferredList(deferreds) if script: args.update({"items":{convId:conv}, "convId":convId}) entityIds = yield event.fetchData(args, convId) entities = base.EntitySet(entityIds) yield entities.fetchData() args["entities"] = entities t.renderScriptBlock(request, "event.mako", "event_meta", landing, "#item-meta", "set", **args) # Push Feed Updates responseType = "E" convMeta = conv["meta"] convType = convMeta["type"] convOwnerId = convMeta["owner"] commentSnippet = convMeta["event_title"] itemId = convId convACL = convMeta["acl"] extraEntities = [convMeta["owner"]] # Importing social.feed at the beginning of the module leads to # a cyclic dependency as feed in turn imports plugins. from social.core import Feed if response == "yes": timeUUID = uuid.uuid1().bytes # Add user to the followers list of parent item yield db.insert(convId, "items", "", myId, "followers") # Update user's feed, feedItems, feed_* userItemValue = ":".join([responseType, itemId, convId, convType, convOwnerId, commentSnippet]) yield db.insert(myId, "userItems", userItemValue, timeUUID) yield db.insert(myId, "userItems_event", userItemValue, timeUUID) # Push to feed feedItemVal = "%s:%s:%s:%s" % (responseType, myId, itemId, ','.join(extraEntities)) yield Feed.push(myId, orgId, convId, conv, timeUUID, feedItemVal) elif prevResponse != "": rsvpTimeUUID = None cols = yield db.get_slice(myId, "userItems") cols = utils.columnsToDict(cols) for k, v in cols.iteritems(): if v.startswith("E"): rsvpTimeUUID = k if rsvpTimeUUID: # Remove update if user changes RSVP to no/maybe from yes. # Do not update if user had RSVPed to this event. feedUpdateVal = "%s:%s:%s:%s" % (responseType, myId, itemId, convOwnerId) yield Feed.unpush(myId, orgId, convId, conv, feedUpdateVal) # FIX: if user updates more than one item at exactly same time, # one of the updates will overwrite the other. Fix it. yield db.remove(myId, "userItems", rsvpTimeUUID) yield db.remove(myId, "userItems_event", rsvpTimeUUID) if myId != convOwnerId and response == "yes": timeUUID = uuid.uuid1().bytes yield _notify("EA", convId, timeUUID, convType=convType, convOwnerId=convOwnerId, myId=myId, me=args["me"])
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 _editPersonalInfo(self, request): # Personal information about the user myId = request.getSession(IAuthInfo).username orgId = request.getSession(IAuthInfo).organization data = {} to_remove = [] me = yield db.get_slice(myId, 'entities') me = base.Entity(myId) yield me.fetchData([]) dob_day = utils.getRequestArg(request, "dob_day") or None dob_mon = utils.getRequestArg(request, "dob_mon") or None dob_year = utils.getRequestArg(request, "dob_year") or None if dob_day and dob_mon and dob_year: try: dateStr = "%s/%s/%s" % (dob_day, dob_mon, dob_year) date = time.strptime(dateStr, "%d/%m/%Y") if date.tm_year < time.localtime().tm_year: dob_day = "%02d" % date.tm_mday dob_mon = "%02d" % date.tm_mon data["birthday"] = "%s%s%s" % (dob_year, dob_mon, dob_day) except ValueError: raise errors.InvalidRequest(_('Please select a valid Date of Birth')) else: to_remove.append('birthday') columnNames = ['email', 'phone', 'mobile', 'hometown', 'currentCity'] for name in columnNames: val = utils.getRequestArg(request, name) if val: data[name] = val else: to_remove.append(name) if data: yield db.batch_insert(myId, "entities", {"personal": data}) if to_remove: yield db.batch_remove({'entities':[myId]}, names=to_remove, supercolumn='personal') if 'phone' in data and not re.match('^\+?[0-9x\- ]{5,20}$', data['phone']): raise errors.InvalidRequest(_('Phone numbers can only have numerals, hyphens, spaces and a plus sign')) if 'mobile' in data and not re.match('^\+?[0-9x\- ]{5,20}$', data['mobile']): raise errors.InvalidRequest(_('Phone numbers can only have numerals, hyphens, spaces and a plus sign')) columnNames.append('birthday') personalInfo = me.get('personal', {}) if any([personalInfo.get(x, None) != data.get(x, None) for x in columnNames]): request.write('$$.alerts.info("%s");' % _('Profile updated')) args = {"detail": "", "me": me} suggestedSections = yield self._checkProfileCompleteness(request, myId, args) tmp_suggested_sections = {} for section, items in suggestedSections.iteritems(): if len(suggestedSections[section]) > 0: tmp_suggested_sections[section] = items args.update({'suggested_sections':tmp_suggested_sections}) t.renderScriptBlock(request, "settings.mako", "right", False, ".right-contents", "set", **args) me.update({'personal':data}) yield search.solr.updatePeopleIndex(myId, me, orgId)