/// <summary> /// Update a relationship between a user and a topic /// </summary> /// <param name="processType">Process type</param> /// <param name="relationshipOperation">Relationship operation</param> /// <param name="relationshipHandle">Relationship handle</param> /// <param name="followerUserHandle">Follower user handle</param> /// <param name="followingTopicHandle">Following topic handle</param> /// <param name="appHandle">App handle</param> /// <param name="lastUpdatedTime">Last updated time</param> /// <param name="followerRelationshipLookupEntity">Follower relationship lookup entity</param> /// <param name="followingRelationshipLookupEntity">Following relationship lookup entity</param> /// <returns>Update relationship task</returns> public async Task UpdateRelationshipToTopic( ProcessType processType, RelationshipOperation relationshipOperation, string relationshipHandle, string followerUserHandle, string followingTopicHandle, string appHandle, DateTime lastUpdatedTime, ITopicRelationshipLookupEntity followerRelationshipLookupEntity, ITopicRelationshipLookupEntity followingRelationshipLookupEntity) { TopicRelationshipStatus topicRelationshipStatus = this.GetTopicRelationshipStatus(relationshipOperation); if (processType == ProcessType.Frontend) { await this.topicRelationshipsStore.UpdateTopicFollowerRelationship( StorageConsistencyMode.Strong, relationshipHandle, followingTopicHandle, followerUserHandle, appHandle, topicRelationshipStatus, lastUpdatedTime, followerRelationshipLookupEntity); await this.topicRelationshipsStore.UpdateTopicFollowingRelationship( StorageConsistencyMode.Strong, relationshipHandle, followerUserHandle, followingTopicHandle, appHandle, topicRelationshipStatus, lastUpdatedTime, followingRelationshipLookupEntity); // fanout an activity indicating that the followerUser is now following the followingTopicHandle await this.fanoutActivitiesQueue.SendFanoutActivityMessage( followerUserHandle, appHandle, relationshipHandle, ActivityType.Following, followerUserHandle, null, ContentType.Topic, followingTopicHandle, lastUpdatedTime); } }
/// <summary> /// Get topic relationship status /// </summary> /// <param name="relationshipOperation">Relationship operation</param> /// <returns>Topic relationship status</returns> private TopicRelationshipStatus GetTopicRelationshipStatus(RelationshipOperation relationshipOperation) { TopicRelationshipStatus topicRelationshipStatus = TopicRelationshipStatus.None; if (relationshipOperation == RelationshipOperation.UnfollowTopic) { topicRelationshipStatus = TopicRelationshipStatus.None; } else if (relationshipOperation == RelationshipOperation.FollowTopic) { topicRelationshipStatus = TopicRelationshipStatus.Follow; } else { this.log.LogError("Invalid relationship operation on topic"); } return(topicRelationshipStatus); }
/// <summary> /// Get target relationship status /// </summary> /// <param name="relationshipOperation">Relationship operation</param> /// <returns>Relationship status</returns> private UserRelationshipStatus GetRelationshipStatus(RelationshipOperation relationshipOperation) { UserRelationshipStatus userRelationshipStatus = UserRelationshipStatus.None; switch (relationshipOperation) { case RelationshipOperation.BlockUser: userRelationshipStatus = UserRelationshipStatus.Blocked; break; case RelationshipOperation.UnblockUser: userRelationshipStatus = UserRelationshipStatus.None; break; case RelationshipOperation.AcceptUser: userRelationshipStatus = UserRelationshipStatus.Follow; break; case RelationshipOperation.RejectUser: userRelationshipStatus = UserRelationshipStatus.None; break; case RelationshipOperation.FollowUser: userRelationshipStatus = UserRelationshipStatus.Follow; break; case RelationshipOperation.PendingUser: userRelationshipStatus = UserRelationshipStatus.Pending; break; case RelationshipOperation.UnfollowUser: userRelationshipStatus = UserRelationshipStatus.None; break; case RelationshipOperation.DeleteFollower: userRelationshipStatus = UserRelationshipStatus.None; break; } return(userRelationshipStatus); }
/// <summary> /// Send relationship message /// </summary> /// <param name="relationshipOperation">User relationship operation</param> /// <param name="relationshipHandle">Relationship handle</param> /// <param name="followerKeyUserHandle">Follower key user handle</param> /// <param name="followingKeyUserHandle">Following key user handle</param> /// <param name="appHandle">App handle</param> /// <param name="lastUpdatedTime">Last updated time</param> /// <returns>Send message task</returns> public async Task SendRelationshipMessage( RelationshipOperation relationshipOperation, string relationshipHandle, string followerKeyUserHandle, string followingKeyUserHandle, string appHandle, DateTime lastUpdatedTime) { RelationshipMessage message = new RelationshipMessage() { RelationshipOperation = relationshipOperation, RelationshipHandle = relationshipHandle, FollowerKeyUserHandle = followerKeyUserHandle, FollowingKeyUserHandle = followingKeyUserHandle, AppHandle = appHandle, LastUpdatedTime = lastUpdatedTime }; Queue queue = await this.QueueManager.GetQueue(this.QueueIdentifier); await queue.SendAsync(message); }
/// <summary> /// Visit relationships without disturbing the PackageRelationshipCollection iterator /// </summary> /// <param name="relationships">collection of relationships to visit</param> /// <param name="visit">function to call with each relationship in the list</param> /// <param name="context">context object - may be null</param> private void SafeVisitRelationships(PackageRelationshipCollection relationships, RelationshipOperation visit, Object context) { // make a local copy that will not be invalidated by any activity of the visitor function List<PackageRelationship> relationshipsToVisit = new List<PackageRelationship>(relationships); // now invoke the delegate for each member for (int i = 0; i < relationshipsToVisit.Count; i++) { // exit if visitor wants us to if (!visit(relationshipsToVisit[i], context)) break; } }
/// <summary> /// Visit relationships without disturbing the PackageRelationshipCollection iterator /// </summary> /// <param name="relationships">collection of relationships to visit</param> /// <param name="visit">function to call with each relationship in the list</param> private void SafeVisitRelationships(PackageRelationshipCollection relationships, RelationshipOperation visit) { SafeVisitRelationships(relationships, visit, null); }
/// <summary> /// Update a relationship between two users /// </summary> /// <param name="processType">Process type</param> /// <param name="relationshipOperation">User relationship operation</param> /// <param name="relationshipHandle">Relationship handle</param> /// <param name="followerKeyUserHandle">Follower key user handle</param> /// <param name="followingKeyUserHandle">Following key user handle</param> /// <param name="appHandle">App handle</param> /// <param name="lastUpdatedTime">Last updated time</param> /// <param name="followerRelationshipLookupEntity">Follower relationship lookup entity</param> /// <param name="followingRelationshipLookupEntity">Following relationship lookup entity</param> /// <returns>Update relationship task</returns> public async Task UpdateRelationshipToUser( ProcessType processType, RelationshipOperation relationshipOperation, string relationshipHandle, string followerKeyUserHandle, string followingKeyUserHandle, string appHandle, DateTime lastUpdatedTime, IUserRelationshipLookupEntity followerRelationshipLookupEntity, IUserRelationshipLookupEntity followingRelationshipLookupEntity) { UserRelationshipStatus userRelationshipStatus = this.GetRelationshipStatus(relationshipOperation); if (processType == ProcessType.Frontend) { await this.userRelationshipsStore.UpdateFollowerRelationship( StorageConsistencyMode.Strong, relationshipHandle, followerKeyUserHandle, followingKeyUserHandle, appHandle, userRelationshipStatus, lastUpdatedTime, followerRelationshipLookupEntity); await this.userRelationshipsStore.UpdateFollowingRelationship( StorageConsistencyMode.Strong, relationshipHandle, followingKeyUserHandle, followerKeyUserHandle, appHandle, userRelationshipStatus, lastUpdatedTime, followingRelationshipLookupEntity); await this.relationshipsQueue.SendRelationshipMessage( relationshipOperation, relationshipHandle, followerKeyUserHandle, followingKeyUserHandle, appHandle, lastUpdatedTime); } else if (processType == ProcessType.Backend || processType == ProcessType.BackendRetry) { if (relationshipOperation == RelationshipOperation.FollowUser) { await this.notificationsManager.CreateNotification( processType, followerKeyUserHandle, appHandle, relationshipHandle, ActivityType.Following, followingKeyUserHandle, followerKeyUserHandle, ContentType.Unknown, null, lastUpdatedTime); await this.fanoutActivitiesQueue.SendFanoutActivityMessage( followingKeyUserHandle, appHandle, relationshipHandle, ActivityType.Following, followingKeyUserHandle, followerKeyUserHandle, ContentType.Unknown, null, lastUpdatedTime); await this.followingImportsQueue.SendFollowingImportMessage( followingKeyUserHandle, appHandle, followerKeyUserHandle); } else if (relationshipOperation == RelationshipOperation.PendingUser) { await this.notificationsManager.CreateNotification( processType, followerKeyUserHandle, appHandle, relationshipHandle, ActivityType.FollowRequest, followingKeyUserHandle, followerKeyUserHandle, ContentType.Unknown, null, lastUpdatedTime); } else if (relationshipOperation == RelationshipOperation.AcceptUser) { await this.notificationsManager.CreateNotification( processType, followingKeyUserHandle, appHandle, relationshipHandle, ActivityType.FollowAccept, followerKeyUserHandle, followingKeyUserHandle, ContentType.Unknown, null, lastUpdatedTime); await this.followingImportsQueue.SendFollowingImportMessage( followingKeyUserHandle, appHandle, followerKeyUserHandle); } // Update popular user feed when follower count changes if (relationshipOperation == RelationshipOperation.FollowUser || relationshipOperation == RelationshipOperation.UnfollowUser || relationshipOperation == RelationshipOperation.DeleteFollower || relationshipOperation == RelationshipOperation.BlockUser || relationshipOperation == RelationshipOperation.AcceptUser) { long?followersCount = await this.userRelationshipsStore.QueryFollowersCount(followerKeyUserHandle, appHandle); long followersCountValue = followersCount.HasValue ? followersCount.Value : 0; if (followersCountValue % PopularUsersUpdateFollowersCount == 0) { await this.popularUsersManager.UpdatePopularUser(processType, followerKeyUserHandle, appHandle, followersCountValue); } } } }
/// <summary> /// Update a relationship with another user /// </summary> /// <param name="callerClassName">name of the controller class of the caller</param> /// <param name="callerMethodName">name of method insider controller class of the caller (should correspond to an HTTP action)</param> /// <param name="relationshipOperation">Relationship operation</param> /// <param name="actedOnUserHandle">Acted on user handle</param> /// <returns>No content on success</returns> protected async Task <IHttpActionResult> UpdateRelationshipToUser( string callerClassName, string callerMethodName, RelationshipOperation relationshipOperation, string actedOnUserHandle) { string actorUserHandle = this.UserHandle; DateTime currentTime = DateTime.UtcNow; if (actorUserHandle == actedOnUserHandle) { this.BadRequest(ResponseStrings.NotAllowed); } IUserProfileEntity userProfileEntity = await this.usersManager.ReadUserProfile(actedOnUserHandle, this.AppHandle); if (userProfileEntity == null) { this.NotFound(ResponseStrings.UserNotFound); } if (relationshipOperation == RelationshipOperation.FollowUser && userProfileEntity.Visibility == UserVisibilityStatus.Private) { relationshipOperation = RelationshipOperation.PendingUser; } string followerKeyUserHandle = actorUserHandle; string followingKeyUserHandle = actedOnUserHandle; if (relationshipOperation == RelationshipOperation.FollowUser || relationshipOperation == RelationshipOperation.PendingUser || relationshipOperation == RelationshipOperation.UnfollowUser) { followerKeyUserHandle = actedOnUserHandle; followingKeyUserHandle = actorUserHandle; } IUserRelationshipLookupEntity followerRelationshipLookupEntity = await this.relationshipsManager.ReadFollowerRelationship(followerKeyUserHandle, followingKeyUserHandle, this.AppHandle); if (followerRelationshipLookupEntity != null && followerRelationshipLookupEntity.LastUpdatedTime > currentTime) { return(this.Conflict(ResponseStrings.NewerItemExists)); } IUserRelationshipLookupEntity followingRelationshipLookupEntity = await this.relationshipsManager.ReadFollowingRelationshipToUser(followingKeyUserHandle, followerKeyUserHandle, this.AppHandle); if (followingRelationshipLookupEntity != null && followingRelationshipLookupEntity.LastUpdatedTime > currentTime) { return(this.Conflict(ResponseStrings.NewerItemExists)); } if (relationshipOperation == RelationshipOperation.AcceptUser) { if (followerRelationshipLookupEntity == null || followerRelationshipLookupEntity.UserRelationshipStatus != UserRelationshipStatus.Pending) { return(this.Forbidden(ResponseStrings.NotAllowed)); } } string relationshipHandle = null; if (relationshipOperation == RelationshipOperation.AcceptUser || relationshipOperation == RelationshipOperation.BlockUser || relationshipOperation == RelationshipOperation.FollowUser || relationshipOperation == RelationshipOperation.PendingUser) { relationshipHandle = this.handleGenerator.GenerateShortHandle(); } await this.relationshipsManager.UpdateRelationshipToUser( ProcessType.Frontend, relationshipOperation, relationshipHandle, followerKeyUserHandle, followingKeyUserHandle, this.AppHandle, currentTime, followerRelationshipLookupEntity, followingRelationshipLookupEntity); string logEntry = $"ActedOnUserHandle = {actedOnUserHandle}, RelationshipOperation = {relationshipOperation.ToString()}, RelationshipHandle = {relationshipHandle}"; logEntry += $", OldRelationshipStatus = {followingRelationshipLookupEntity?.UserRelationshipStatus.ToString()}, NewRelationshipStatus = {relationshipOperation.ToString()}"; this.LogControllerEnd(this.log, callerClassName, callerMethodName, logEntry); return(this.NoContent()); }
/// <summary> /// Update a relationship with a topic /// </summary> /// <param name="callerClassName">name of the controller class of the caller</param> /// <param name="callerMethodName">name of method insider controller class of the caller (should correspond to an HTTP action)</param> /// <param name="relationshipOperation">Relationship operation</param> /// <param name="actedOnTopicHandle">Acted on topic handle</param> /// <returns>No content on success</returns> protected async Task <IHttpActionResult> UpdateRelationshipToTopic( string callerClassName, string callerMethodName, RelationshipOperation relationshipOperation, string actedOnTopicHandle) { string actorUserHandle = this.UserHandle; DateTime currentTime = DateTime.UtcNow; if (relationshipOperation != RelationshipOperation.FollowTopic && relationshipOperation != RelationshipOperation.UnfollowTopic) { // the caller should never specify a operation other than FollowTopic or UnfollowTopic return(this.InternalServerError()); } // get the topic being followed TopicView topicView = await this.viewsManager.GetTopicView(actedOnTopicHandle, actorUserHandle); if (topicView == null) { // This could mean one of three situations: // (1) the topic has been banned and hence is no longer accessible to anyone, // (2) the topic has been deleted, // (3) the topic is from a private user that the actor user is not following; this should // not happen because the actor user should not be able to get a topicHandle for a topic // posted by a private user that they are not following return(this.NotFound(ResponseStrings.TopicNotFound)); } // lookup the existing relationships ITopicRelationshipLookupEntity followerRelationshipLookupEntity = await this.relationshipsManager.ReadTopicFollowerRelationship(actedOnTopicHandle, actorUserHandle, this.AppHandle); if (followerRelationshipLookupEntity != null && followerRelationshipLookupEntity.LastUpdatedTime > currentTime) { // this relationship has been updated more recently than this request return(this.Conflict(ResponseStrings.NewerItemExists)); } ITopicRelationshipLookupEntity followingRelationshipLookupEntity = await this.relationshipsManager.ReadFollowingRelationshipToTopic(actorUserHandle, actedOnTopicHandle, this.AppHandle); if (followingRelationshipLookupEntity != null && followingRelationshipLookupEntity.LastUpdatedTime > currentTime) { // this relationship has been updated more recently than this request return(this.Conflict(ResponseStrings.NewerItemExists)); } // if following a topic string relationshipHandle = null; if (relationshipOperation == RelationshipOperation.FollowTopic) { // create a relationship handle relationshipHandle = this.handleGenerator.GenerateShortHandle(); // insert the topic into the user's following topic feed await this.topicsManager.CreateFollowingTopic(actorUserHandle, this.AppHandle, actedOnTopicHandle); } if (relationshipOperation == RelationshipOperation.UnfollowTopic) { try { // remove the topic from the user's following topic feed await this.topicsManager.DeleteFollowingTopic(actorUserHandle, this.AppHandle, actedOnTopicHandle); } catch (NotFoundException) { return(this.NotFound(ResponseStrings.NotFollowingTopic)); } } // submit the request to the relationship manager await this.relationshipsManager.UpdateRelationshipToTopic( ProcessType.Frontend, relationshipOperation, relationshipHandle, actorUserHandle, actedOnTopicHandle, this.AppHandle, currentTime, followerRelationshipLookupEntity, followingRelationshipLookupEntity); string logEntry = $"TopicHandle = {topicView?.TopicHandle}, RelationshipHandle = {relationshipHandle}, RelationshipOperation = {relationshipOperation.ToString()}"; this.LogControllerEnd(this.log, callerClassName, callerMethodName, logEntry); return(this.NoContent()); }