public static async Task<UserText> CreateTextAsync( ApplicationDbContext dbContext, string content) { var entity = new UserText { Content = content, CreatedUtc = DateTime.UtcNow }; MatchCollection matches = _emailRefRe.Matches(content); if (matches.Count > 0) { entity.MentionsUser = new List<UserInfo>(); // We typically have few enough users that it's easiest just to get // all of them. IDictionary<string, UserInfo> allUsers = (await dbContext.UserInfos .ToListAsync()) .ToDictionary(u => u.Email); foreach (string match in matches.Cast<Match>().Select(m => m.Groups[1].Value).Distinct()) { UserInfo mentionedUser; if (allUsers.TryGetValue(match, out mentionedUser)) { entity.MentionsUser.Add(mentionedUser); } } } return entity; }
public static async Task<InvitationsIssued> InviteAsync( ApplicationDbContext dbContext, InvitationRequest request) { using (var tx = dbContext.Database.BeginTransaction()) { bool succeeded = false; try { InvitationsIssued current = await GetCurrentInvitations(dbContext); if (!current.Accepted.Contains(request.Email) && !current.Outstanding.Contains(request.Email)) { dbContext.Invitations.Add(new Invitation { Email = request.Email }); await dbContext.SaveChangesAsync(); current.Outstanding.Add(request.Email); } tx.Commit(); succeeded = true; return current; } finally { if (!succeeded) { tx.Rollback(); } } } }
public static async Task<InvitationsIssued> UninviteAsync( ApplicationDbContext dbContext, InvitationRequest request) { using (var tx = dbContext.Database.BeginTransaction()) { bool succeeded = false; try { Invitation existing = await dbContext.Invitations.SingleOrDefaultAsync(e => e.Email == request.Email); if (existing != null) { dbContext.Invitations.Remove(existing); await dbContext.SaveChangesAsync(); } InvitationsIssued current = await GetCurrentInvitations(dbContext); tx.Commit(); succeeded = true; return current; } finally { if (!succeeded) { tx.Rollback(); } } } }
public static async Task<TimelineEntryDetails> GetTimelineItemAsync( ApplicationDbContext dbContext, int entryId) { IList<TimelineEntryDetails> result = await EntriesQueryToDetailsAsync( dbContext.TimelineEntries.Where(e => e.TimelineEntryId == entryId)); return result.SingleOrDefault(); }
public static Task<SearchResults> SearchAsync( ApplicationDbContext dbContext, string term) { // TBD return Task.FromResult(new SearchResults { AlbumMatches = new SearchResult[0], MediaMatches = new SearchResult[0], TimelineMatches = new SearchResult[0] }); }
public static async Task<AlbumDetail> GetAlbumAsync( ApplicationDbContext dbContext, int albumId) { MediaAlbum entity = await dbContext.MediaAlbums .Include(a => a.UserMedias.Select(um => um.Description)) .Include(a => a.UserMedias.Select(um => um.Likes.Select(like => like.User))) .Include(a => a.UserMedias.Select(um => um.CommentThread.Comments.Select(c => c.Text))) .Include(a => a.UserMedias.Select(um => um.CommentThread.Comments.Select(c => c.User.Avatar))) .Include(a => a.Likes.Select(like => like.User)) .Include(a => a.CommentThread.Comments.Select(c => c.Text)) .Include(a => a.CommentThread.Comments.Select(c => c.User.Avatar)) .SingleOrDefaultAsync(a => a.MediaAlbumId == albumId); if (entity == null) { return null; } return new AlbumDetail { Title = entity.Title, UserId = entity.UserId, Description = entity.Description, Items = entity.UserMedias .Select( um => new AlbumItem { Id = um.UserMediaId, UserId = um.UserId, Title = um.Title, Description = um.Description?.Content, CreatedTime = um.CreatedUtc.ToString("s"), CreatedTimeAgo = TimeOperations.GetTimeAgo(um.CreatedUtc), MediaUrl = UserMediaOperations.GetUrl(um), LikeUrl = UserMediaOperations.GetLikeUrl(um), LikeGroups = LikeOperations.MakeLikeGroups(um.Likes), CommentUrl = UserMediaOperations.GetCommentUrl(um), Comments = CommentOperations.GetComments(um.CommentThread) }) .ToList(), LikeUrl = $"/api/Albums/{entity.MediaAlbumId}/Like", LikeGroups = LikeOperations.MakeLikeGroups(entity.Likes), CommentUrl = $"/api/Albums/{entity.MediaAlbumId}/Comment", Comments = CommentOperations.GetComments(entity.CommentThread) }; }
public static async Task<InvitationsIssued> GetCurrentInvitations( ApplicationDbContext dbContext) { var inv = await dbContext.Invitations .Select(i => new { Email = i.Email, Accepted = dbContext.UserInfos.Any(u => u.Email == i.Email) }) .ToListAsync(); return new InvitationsIssued { Outstanding = inv.Where(i => !i.Accepted).Select(i => i.Email).ToList(), Accepted = inv.Where(i => i.Accepted).Select(i => i.Email).ToList() }; }
public static async Task<IList<AlbumSummary>> GetAlbumSummariesAsync( ApplicationDbContext dbContext, UserInfo user) { var results = await dbContext.MediaAlbums .Where(a => a.UserId == user.UserInfoId) .Include(a => a.Likes.Select(like => like.User)) .Include(a => a.CommentThread.Comments.Select(c => c.Text)) .Include(a => a.CommentThread.Comments.Select(c => c.User.Avatar)) .Select(a => new { a.MediaAlbumId, a.Title, a.Description, Medias = a.UserMedias.Take(4), LatestMedia = a.UserMedias.OrderByDescending(um => um.CreatedUtc).FirstOrDefault(), Likes = a.Likes, a.CommentThread }) .ToListAsync(); return results .Select( a => new AlbumSummary { Id = a.MediaAlbumId, UserId = user.UserInfoId, Title = a.Title, Description = a.Description, SampleMediaUrls = a.Medias.Select(UserMediaOperations.GetUrl).ToList(), LastModifiedText = a.LatestMedia != null ? TimeOperations.GetTimeAgo(a.LatestMedia.CreatedUtc) : "", LikeUrl = $"/api/Albums/{a.MediaAlbumId}/Like", LikeGroups = LikeOperations.MakeLikeGroups(a.Likes), CommentUrl = $"/api/Albums/{a.MediaAlbumId}/Comment", Comments = CommentOperations.GetComments(a.CommentThread) }) .ToList(); }
public static async Task<IList<TimelineEntryDetails>> GetTimelineItemsAsync( ApplicationDbContext dbContext, string since) { IQueryable<TimelineEntry> q = dbContext.TimelineEntries; if (!string.IsNullOrWhiteSpace(since)) { DateTime after; if (!DateTime.TryParseExact(since, "s", CultureInfo.InvariantCulture, DateTimeStyles.None, out after)) { return null; } q = q.Where(te => te.Message.CreatedUtc > after); } q = q.OrderByDescending(te => te.Message.CreatedUtc).Take(30); return await EntriesQueryToDetailsAsync(q); }
public static async Task<HttpStatusCode> SetAvatarImage( ApplicationDbContext dbContext, UserInfo user, SetImage request) { UserMedia media = await dbContext.UserMedias .SingleOrDefaultAsync(um => um.UserMediaId == request.MediaId); if (media == null) { return HttpStatusCode.BadRequest; } if (!media.MediaAlbumId.HasValue) { if (user.AvatarsMediaAlbumId.HasValue) { media.MediaAlbumId = user.AvatarsMediaAlbumId; } else { var avatarsAlbum = new MediaAlbum { User = user, Title = "Avatar Images", Description = "Images used as my avatar" }; user.AvatarsAlbum = avatarsAlbum; media.MediaAlbum = avatarsAlbum; } } user.Avatar = media; await dbContext.SaveChangesAsync(); return HttpStatusCode.OK; }
public static async Task<HttpStatusCode> AddOrRemoveTimelineEntryLikeAsync( ApplicationDbContext dbContext, string userId, int entryId, LikeRequest like) { TimelineEntry entryEntity = await dbContext.TimelineEntries .SingleOrDefaultAsync(te => te.TimelineEntryId == entryId); if (entryEntity == null) { // The entry id is part of the URL, so return a 404. return HttpStatusCode.NotFound; } return await LikeOperations.AddOrRemoveLikeAsync( dbContext, userId, entryId, le => le.UserTextId, like); }
static void Main(string[] args) { string searchServiceName = args[0]; var credentials = new SearchCredentials(args[1]); var searchClient = new SearchServiceClient(searchServiceName, credentials); try { IndexDefinitionResponse getResponse = searchClient.Indexes.Get(IndexName); if (getResponse?.Index != null) { Console.WriteLine("Deleting and recreating index " + IndexName); searchClient.Indexes.Delete(IndexName); } } catch (CloudException) { // We expect this if the index does not yet exist. } IndexDefinitionResponse createIndexResponse = searchClient.Indexes.Create(new Index( IndexName, new[] { new Field("ItemId", DataType.String) { IsKey = true }, new Field("Title", DataType.String) { IsSearchable = true }, new Field("Content", DataType.String) { IsSearchable = true }, new Field("CommentThreadId", DataType.Int32), new Field("TimelineEntryId", DataType.Int32), new Field("MediaAlbumId", DataType.Int32), new Field("UserMediaId", DataType.Int32) })); Index index = createIndexResponse.Index; var indexClient = new SearchIndexClient(searchServiceName, IndexName, credentials); using (var dbContext = new ApplicationDbContext(args[2])) { IEnumerable<TimelineEntry> timelineEntries = dbContext.TimelineEntries .Include(te => te.Message) .Include(te => te.CommentThread.Comments.Select(c => c.Text)); foreach (TimelineEntry entry in timelineEntries) { var batchActions = new List<IndexAction<MessageIndexEntry>>(); batchActions.Add(new IndexAction<MessageIndexEntry>( IndexActionType.Upload, new MessageIndexEntry { ItemId = "timeline-" + entry.TimelineEntryId, Content = entry.Message.Content, TimelineEntryId = entry.TimelineEntryId })); if (entry.CommentThread != null) { foreach (Comment comment in entry.CommentThread.Comments) { batchActions.Add(new IndexAction<MessageIndexEntry>( IndexActionType.Upload, new MessageIndexEntry { ItemId = "comment-" + comment.CommentId, Content = comment.Text.Content, TimelineEntryId = entry.TimelineEntryId, CommentThreadId = comment.CommentThreadId })); } } var batch = new IndexBatch<MessageIndexEntry>(batchActions); DocumentIndexResponse indexDocumentsResponse = indexClient.Documents.Index(batch); } IEnumerable<MediaAlbum> albums = dbContext.MediaAlbums .Include(a => a.CommentThread.Comments.Select(c => c.Text)); foreach (MediaAlbum album in albums) { var batchActions = new List<IndexAction<MessageIndexEntry>>(); batchActions.Add(new IndexAction<MessageIndexEntry>( IndexActionType.Upload, new MessageIndexEntry { ItemId = "album-" + album.MediaAlbumId, Title = album.Title, Content = album.Description, MediaAlbumId = album.MediaAlbumId })); if (album.CommentThread != null) { foreach (Comment comment in album.CommentThread.Comments) { batchActions.Add(new IndexAction<MessageIndexEntry>( IndexActionType.Upload, new MessageIndexEntry { ItemId = "comment-" + comment.CommentId, Content = comment.Text.Content, MediaAlbumId = album.MediaAlbumId, CommentThreadId = comment.CommentThreadId })); } } var batch = new IndexBatch<MessageIndexEntry>(batchActions); DocumentIndexResponse indexDocumentsResponse = indexClient.Documents.Index(batch); } IEnumerable<UserMedia> medias = dbContext.UserMedias .Include(m => m.Description) .Include(m => m.CommentThread.Comments.Select(c => c.Text)); foreach (UserMedia media in medias) { var batchActions = new List<IndexAction<MessageIndexEntry>>(); batchActions.Add(new IndexAction<MessageIndexEntry>( IndexActionType.Upload, new MessageIndexEntry { ItemId = "media-" + media.UserMediaId, Title = media.Title, Content = media.Description?.Content, UserMediaId = media.UserMediaId, MediaAlbumId = media.MediaAlbumId })); if (media.CommentThread != null) { foreach (Comment comment in media.CommentThread.Comments) { batchActions.Add(new IndexAction<MessageIndexEntry>( IndexActionType.Upload, new MessageIndexEntry { ItemId = "comment-" + comment.CommentId, Content = comment.Text.Content, UserMediaId = media.UserMediaId, MediaAlbumId = media.MediaAlbumId, CommentThreadId = comment.CommentThreadId })); } } var batch = new IndexBatch<MessageIndexEntry>(batchActions); DocumentIndexResponse indexDocumentsResponse = indexClient.Documents.Index(batch); } } }
public static async Task<HttpStatusCode> AddOrRemoveLikeAsync( ApplicationDbContext dbContext, string userId, int likedId, Expression<Func<Like, int?>> entryIdProperty, LikeRequest like) { using (var transaction = dbContext.Database.BeginTransaction()) { bool succeeded = false; try { LikeKind kind; switch (like.LikeKind.ToLowerInvariant()) { case "like": kind = LikeKind.Like; break; case "frown": kind = LikeKind.Frown; break; case "hug": kind = LikeKind.Hug; break; default: return HttpStatusCode.BadRequest; } var match = (Expression<Func<Like, bool>>) Expression.Lambda( Expression.Equal( entryIdProperty.Body, Expression.Convert( Expression.Constant(likedId), typeof(int?))), entryIdProperty.Parameters[0]); // Using First rather than Single because there are race conditions in which // we can end up with multiple likes. Like existingLikeEntity = await dbContext.Likes .Where(le => le.Kind == kind) .FirstOrDefaultAsync(match); HttpStatusCode result; if (like.Set) { if (existingLikeEntity != null) { // Ignore a relike result = HttpStatusCode.OK; } var idProp = (PropertyInfo) ((MemberExpression) entryIdProperty.Body).Member; result = await AddLikeAsync(dbContext, userId, idProp, likedId, kind); } else { if (existingLikeEntity != null) { dbContext.Likes.Remove(existingLikeEntity); await dbContext.SaveChangesAsync(); } // Ignore a reunlike result = HttpStatusCode.OK; } transaction.Commit(); succeeded = true; return result; } finally { if (!succeeded) { transaction.Rollback(); } } } }
public static async Task<HttpStatusCode> AddAlbumCommentAsync( ApplicationDbContext dbContext, string userId, int albumId, CommentRequest comment) { MediaAlbum albumEntity = await dbContext.MediaAlbums .SingleOrDefaultAsync(te => te.MediaAlbumId == albumId); if (albumEntity == null) { // The entry id is part of the URL, so return a 404. return HttpStatusCode.NotFound; } return await CommentOperations.AddCommentAsync( dbContext, userId, new CommentItemIds { AlbumId = albumId }, e => e.CommentThread, albumEntity, comment); }
public static async Task<HttpStatusCode> AddOrRemoveAlbumLikeAsync( ApplicationDbContext dbContext, string userId, int entryId, LikeRequest like) { MediaAlbum albumEntity = await dbContext.MediaAlbums .SingleOrDefaultAsync(te => te.MediaAlbumId == entryId); if (albumEntity == null) { // The entry id is part of the URL, so return a 404. return HttpStatusCode.NotFound; } return await LikeOperations.AddOrRemoveLikeAsync( dbContext, userId, entryId, le => le.MediaAlbumId, like); }
public static async Task AddMediaToAlbumAsync( ApplicationDbContext dbContext, MediaAlbum albumEntity, UserMedia mediaEntity, AddImageToAlbum createRequest) { albumEntity.UserMedias.Add(mediaEntity); mediaEntity.MediaAlbum = albumEntity; if (mediaEntity.State == UserMediaState.UploadedButUnused) { mediaEntity.State = UserMediaState.InUse; } mediaEntity.Title = createRequest.Title; if (!string.IsNullOrWhiteSpace(createRequest.Description)) { mediaEntity.Description = await TextOperations.CreateTextAsync( dbContext, createRequest.Description); } await dbContext.SaveChangesAsync(); await SearchOperations.IndexMediaAsync(new[] { mediaEntity }); if (mediaEntity.Description != null) { await UserOperations.NotifyMentionsAsync( dbContext, "Album Entry", mediaEntity.UserId, mediaEntity.Description); } }
public static async Task<HttpStatusCode> ChangeAlbumAsync( ApplicationDbContext dbContext, UserInfo user, int albumId, AlbumDefinition modifyRequest) { MediaAlbum entity = await dbContext.MediaAlbums.SingleOrDefaultAsync( a => a.MediaAlbumId == albumId); if (entity == null) { return HttpStatusCode.NotFound; } if (entity.UserId != user.UserInfoId) { return HttpStatusCode.Forbidden; } entity.Title = modifyRequest.Title; entity.Description = modifyRequest.Description; await dbContext.SaveChangesAsync(); await SearchOperations.IndexAlbumAsync(entity); return HttpStatusCode.OK; }
private static async Task<HttpStatusCode> RemoveLikeAsync( ApplicationDbContext dbContext, string userId, int entryId, LikeKind kind) { TimelineEntry entryEntity = await dbContext.TimelineEntries .SingleOrDefaultAsync(te => te.TimelineEntryId == entryId); if (entryEntity == null) { return HttpStatusCode.NotFound; } var likeEntity = new Like { UserId = userId, Kind = kind, UserTextId = entryEntity.MessageUserTextId }; dbContext.Likes.Add(likeEntity); await dbContext.SaveChangesAsync(); return HttpStatusCode.OK; }
public static Task<bool> CheckNewUserAllowed( ApplicationDbContext dbContext, string email) { // This is where we'd be filtering based on invitation. As it is, // we let in anyone with a Microsoft account at the moment. return Task.FromResult(true); }
public static async Task<HttpStatusCode> AddTimelineEntryCommentAsync( ApplicationDbContext dbContext, string userId, int entryId, CommentRequest comment) { TimelineEntry entryEntity = await dbContext.TimelineEntries .SingleOrDefaultAsync(te => te.TimelineEntryId == entryId); if (entryEntity == null) { // The entry id is part of the URL, so return a 404. return HttpStatusCode.NotFound; } return await CommentOperations.AddCommentAsync( dbContext, userId, new CommentItemIds { TimelineEntryId = entryId }, e => e.CommentThread, entryEntity, comment); }
public static async Task NotifyMentionsAsync( ApplicationDbContext dbContext, string mentionedIn, string mentioningUserId, UserText text) { await dbContext.Entry(text).Collection(t => t.MentionsUser).LoadAsync(); if (text.MentionsUser != null && text.MentionsUser.Count > 0) { // TBD } }
private static async Task<MediaAlbum> EnsureTimelinePhotoAlbumExistsAsync( ApplicationDbContext dbContext, UserInfo userEntity) { if (!userEntity.TimelineImagesMediaAlbumId.HasValue) { var timelineAlbum = new MediaAlbum { User = userEntity, Title = "Timeline Photos", Description = "Photos posted to my timeline" }; userEntity.TimelineImagesAlbum = timelineAlbum; dbContext.MediaAlbums.Add(timelineAlbum); await dbContext.SaveChangesAsync(); return timelineAlbum; } if (userEntity.TimelineImagesAlbum == null) { await dbContext.Entry(userEntity).Reference(u => u.TimelineImagesAlbum).LoadAsync(); } return userEntity.TimelineImagesAlbum; }
public static async Task<AlbumSummary> CreateAlbumAsync( ApplicationDbContext dbContext, UserInfo user, AlbumDefinition createRequest) { var entity = new MediaAlbum { User = user, Title = createRequest.Title, Description = createRequest.Description }; dbContext.MediaAlbums.Add(entity); await dbContext.SaveChangesAsync(); await SearchOperations.IndexAlbumAsync(entity); return new AlbumSummary { Id = entity.MediaAlbumId, UserId = user.UserInfoId, Title = entity.Title, Description = entity.Description, SampleMediaUrls = new string[0] }; }
public static async Task<HttpStatusCode> AddTimelineEntryAsync( CreateTimelineEntry createMessage, ApplicationDbContext dbContext, UserInfo userEntity) { if (string.IsNullOrWhiteSpace(createMessage?.Message)) { return HttpStatusCode.BadRequest; } var text = await TextOperations.CreateTextAsync(dbContext, createMessage.Message); var timelineEntity = new TimelineEntry { UserId = userEntity.UserInfoId, Message = text }; if (createMessage.MediaIds != null && createMessage.MediaIds.Count > 0) { MediaAlbum timelineAlbum = await EnsureTimelinePhotoAlbumExistsAsync(dbContext, userEntity); timelineEntity.Media = new List<TimelineEntryMedia>(); int sequence = 0; var includedMedia = new List<UserMedia>(); foreach (int id in createMessage.MediaIds) { UserMedia mediaEntity = await dbContext.UserMedias .SingleAsync(um => um.UserMediaId == id); if (mediaEntity.UserId != userEntity.UserInfoId) { // Only allowed to post your own images here return HttpStatusCode.BadRequest; } includedMedia.Add(mediaEntity); mediaEntity.MediaAlbum = timelineAlbum; var mediaEntry = new TimelineEntryMedia { Media = mediaEntity, Sequence = sequence++, TimelineEntry = timelineEntity }; dbContext.TimelineEntryMedia.Add(mediaEntry); timelineEntity.Media.Add(mediaEntry); } foreach (UserMedia media in includedMedia) { if (media.State == UserMediaState.UploadedButUnused) { media.State = UserMediaState.InUse; } } } dbContext.UserTexts.Add(text); dbContext.TimelineEntries.Add(timelineEntity); await dbContext.SaveChangesAsync(); await UserOperations.NotifyMentionsAsync( dbContext, "Timeline Entry", userEntity.UserInfoId, text); await SearchOperations.IndexTimelineMessageAsync(timelineEntity); return HttpStatusCode.OK; }
private static async Task<HttpStatusCode> AddLikeAsync( ApplicationDbContext dbContext, string userId, PropertyInfo likedIdProperty, int likedId, LikeKind kind) { var likeEntity = new Like { UserId = userId, Kind = kind }; likedIdProperty.SetValue(likeEntity, likedId); dbContext.Likes.Add(likeEntity); await dbContext.SaveChangesAsync(); return HttpStatusCode.OK; }