public async Task <PhotoResponse.Photo> GetPhotoInfo(PhotoId photoId, CancellationToken ct) { var photoInfo = await _pictureFetchingService.GetPhotoInfo(photoId.flickrId, ct); ColoredConsole.WriteLine($" > info for #{photoId.flickrId} fetched: {photoInfo.photo.dates.taken}", Colors.txtInfo); return(photoInfo.photo); }
private async Task RemoveLikeRecord(UserId userId, PhotoId photoId) { logWriter.LogInformation($"{nameof(RemoveLikeRecord)}({nameof(userId)} = '{userId}', {nameof(photoId)} = '{photoId}')"); var photo = await photoRepository.GetPhotoById(photoId, Guid.Empty); var newLikeCount = photo.LikeCount - 1; var deleteItemRequest = new DeleteItemRequest( tableName, Mappers.PhotoLike.ToDbKey(new PhotoLikeRecord { UserId = userId, PhotoId = photoId, CreatedTime = system.Time.UtcNow })); try { await dynamoDbCore.DeleteItem(deleteItemRequest); await UpdateLikeCount(photo, newLikeCount); } catch (Exception ex) { logWriter.LogError(ex, $"Error in {nameof(RemoveLikeRecord)}({nameof(userId)} = '{userId}', {nameof(photoId)} = '{photoId}'):\n{ex.ToString()}"); throw; } }
public async Task <Sizes> GetSizes(PhotoId photoId, CancellationToken ct) { var response = await _pictureFetchingService.GetSize(photoId.flickrId, ct); ColoredConsole.WriteLine($" > sizes for #{photoId.flickrId} fetched: {response.sizes.size.FirstOrDefault()?.url}", Colors.txtInfo); return(response.sizes); }
private async Task AddLikeRecord(UserId userId, PhotoId photoId) { logWriter.LogInformation($"{nameof(AddLikeRecord)}({nameof(userId)} = '{userId}', {nameof(photoId)} = '{photoId}')"); var photo = await photoRepository.GetPhotoById(photoId, Guid.Empty); var newLikeCount = photo.LikeCount + 1; var putItemRequest = new PutItemRequest() { TableName = tableName, Item = Mappers.PhotoLike.ToDbItem(new PhotoLikeRecord { UserId = userId, PhotoId = photoId, CreatedTime = DateTimeOffset.UtcNow }), }; try { await dynamoDbCore.PutItem(putItemRequest); await UpdateLikeCount(photo, newLikeCount); } catch (Exception ex) { logWriter.LogError(ex, $"Error in {nameof(AddLikeRecord)}({nameof(userId)} = '{userId}', {nameof(photoId)} = '{photoId}'):\n{ex.ToString()}"); throw; } }
public async Task SetPhotoState(UserId userId, PhotoId photoId, PhotoState photoState) { logWriter.LogInformation($"{nameof(SetPhotoState)}({nameof(userId)} = '{userId}', {nameof(photoId)} = '{photoId}', {nameof(photoState)} = '{photoState}'"); var request = new UpdateItemRequest { TableName = tableName, Key = new Dictionary <string, AttributeValue> { { FieldMappings.PartitionKey, new AttributeValue(photoId.ToDbValue()) }, { FieldMappings.SortKey, new AttributeValue(photoId.ToDbValue()) } }, UpdateExpression = $"SET {FieldMappings.Photo.State} = :newstate", ExpressionAttributeValues = new Dictionary <string, AttributeValue> { { ":newstate", new AttributeValue { S = photoState.ToString() } } } }; await dynamoDbCore.UpdateItem(request); }
public override void ToStream(Stream output) { output.Write(TLUtils.SignatureToBytes(Signature)); PhotoId.ToStream(output); Caption.ToStream(output); }
public async Task DeletePhoto(PhotoId photoId) { logWriter.LogInformation($"{nameof(DeletePhoto)}({nameof(photoId)} = '{photoId}'"); // Tirst we need to get the keys for all items to delete IEnumerable <Dictionary <string, AttributeValue> > itemKeys = await GetAllItemKeys(photoId); var request = new BatchWriteItemRequest { RequestItems = new Dictionary <string, List <WriteRequest> > { { tableName, itemKeys.Distinct(new DbKeyEqualityComparer()).Select(key => new WriteRequest { DeleteRequest = new DeleteRequest { Key = key } }).ToList() } } }; await dynamoDbCore.BatchWriteItem(request); }
public override byte[] ToBytes() { return(TLUtils.Combine( TLUtils.SignatureToBytes(Signature), PhotoId.ToBytes(), Caption.ToBytes())); }
public async Task SetPhotoText(UserId userId, PhotoId photoId, string text, IEnumerable <HashtagModel> hashtags) { logWriter.LogInformation($"{nameof(SetPhotoText)}({nameof(userId)} = '{userId}', {nameof(photoId)} = '{photoId}', {nameof(text)} = '{text}')"); var existingItem = await GetPhotoById(photoId, userId); BatchWriteItemRequest writeItemsRequest = GetHashtagsRequests(text, hashtags, existingItem, HashtagUpdateMode.Update); var request = GetUpdateTextRequest(new PhotoModel { PhotoId = photoId, UserId = userId, RawText = text, Hashtags = hashtags }); logWriter.LogInformation($"UpdateItemRequest:\n{JsonConvert.SerializeObject(request)}"); logWriter.LogInformation($"BatchWriteItemRequest:\n{JsonConvert.SerializeObject(writeItemsRequest)}"); await dynamoDbCore.UpdateItem(request); if (writeItemsRequest != null) { await dynamoDbCore.BatchWriteItem(writeItemsRequest); } }
public async Task AddPhotoComment(PhotoId photoId, UserId userId, string userName, string text) { logWriter.LogInformation($"{nameof(AddPhotoComment)}({nameof(photoId)} = '{photoId}', {nameof(userId)} = '{userId}', {nameof(userName)} = '{userName}', {nameof(text)} = '{text}')"); if (string.IsNullOrWhiteSpace(text)) { throw new Exception("Cannot add empty comments."); } PhotoComment comment = new PhotoComment { PhotoId = photoId, UserId = userId, UserName = userName, Text = text, CreatedTime = DateTimeOffset.UtcNow }; try { await commentRepository.AddPhotoComment(comment); } catch (Exception ex) { logWriter.LogError(ex, $"{nameof(AddPhotoComment)}({nameof(photoId)} = '{photoId}', {nameof(userId)} = '{userId}', {nameof(userName)} = '{userName}', {nameof(text)} = '{text}'):\n{ex.ToString()}"); throw; } }
private async Task <string> ProcessImages( S3Event s3Event, IImageScaler imageScaler, ILogWriter <ImageResizeHandler> logger, IPhotosService photoService) { logger.LogInformation($"{nameof(ProcessImages)}"); var s3Entity = s3Event.Records?[0].S3; if (s3Entity == null) { logger.LogCritical($"{nameof(s3Entity)} is null"); return("NULL"); } var urlDecodedKey = System.Web.HttpUtility.UrlDecode(s3Entity.Object.Key); UserId userId = GetUserIdFromKey(urlDecodedKey); PhotoId photoId = GetPhotoIdFromKey(urlDecodedKey); try { logger.LogInformation($"{nameof(urlDecodedKey)} is '{urlDecodedKey}'"); await photoService.SetPhotoState(userId, photoId, PhotoState.ProcessingStarted); // generate filenames to use for the scaled images ImageKeys imageKeys = GetImageKeysWithoutExtension(urlDecodedKey); IEnumerable <Size> imageSizes; using (var s3InputObject = await S3Client.GetObjectAsync(s3Entity.Bucket.Name, urlDecodedKey)) using (var originalImageStream = new MemoryStream()) { await ReadImageIntoStream(logger, originalImageStream, s3InputObject.ResponseStream); imageSizes = await CreateScaledImages(imageScaler, logger, imageKeys, originalImageStream, GetTargetBucket()); } logger.LogInformation($"Updating photo data, making it available."); await UpdatePhotoData(photoService, photoId, imageSizes); // finish with deleting the upload file await DeleteOriginalSourceFile(logger, s3Entity, urlDecodedKey); logger.LogInformation($"Done with {urlDecodedKey}"); return("OK"); } catch (Exception ex) { logger.LogError(ex, $"Error when resizing {s3Entity.Object.Key} from bucket {s3Entity.Bucket.Name}:\n{ex.ToString()}"); // set photo state to ProcessingFailed await photoService.SetPhotoState(userId, photoId, PhotoState.ProcessingFailed); throw; } }
private IEnumerable <HashtagModel> GetHashtags(PhotoId photoId, string text, DateTimeOffset utcNow) { return(textSplitter.Split(text, TextPatterns.Hashtags) .Where(x => x.ItemType == TextItemType.HashTag) .Select(x => new HashtagModel { PhotoId = photoId, Hashtag = x.Text, CreatedTime = utcNow })); }
private static async Task UpdatePhotoData(IPhotosService photoService, PhotoId photoId, IEnumerable <Size> imageSizes) { var photoFromDb = await photoService.GetPhoto(photoId, Guid.Empty); photoFromDb.State = PhotoState.PhotoAvailable; photoFromDb.Sizes = imageSizes; await photoService.UpdatePhoto(photoFromDb); }
public Task SetLikeState(PhotoId photoId, UserId userId, bool like) { logWriter.LogInformation($"{nameof(SetLikeState)}({nameof(photoId)} = '{photoId}', {nameof(userId)} = '{userId}', {nameof(like)} = '{like}')"); return(like ? AddLikeRecord(userId, photoId) : RemoveLikeRecord(userId, photoId)); }
public PhotoLikeRecord FromDbItem(Dictionary <string, AttributeValue> input) { return(new PhotoLikeRecord { UserId = input.GetValue(FieldMappings.PhotoLike.UserId, value => UserId.FromDbValue(value)), PhotoId = input.GetValue(FieldMappings.PartitionKey, value => PhotoId.FromDbValue(value)), CreatedTime = input.GetDateTimeOffset(FieldMappings.PhotoLike.CreatedTime) }); }
public HashtagModel FromDbItem(Dictionary <string, AttributeValue> input) { return(new HashtagModel { Hashtag = input.GetValue(FieldMappings.Hashtag.HastagId, value => $"#{value.Split('|')[1]}"), PhotoId = input.GetValue(FieldMappings.Hashtag.PhotoId, value => PhotoId.FromDbValue(value)), CreatedTime = input.GetDateTimeOffset(FieldMappings.Hashtag.CreatedTime) }); }
private IEnumerable <PhotoWithCommentsAndLikes> ParsePhotosWithCommentsAndLikes(IOrderedEnumerable <Dictionary <string, AttributeValue> > sortedResult) { PhotoId latestPhotoId = Guid.Empty; List <PhotoWithCommentsAndLikes> result = new List <PhotoWithCommentsAndLikes>(); PhotoWithCommentsAndLikes currentResult = null; List <PhotoComment> currentPhotoComments = null; List <PhotoLikeRecord> currentPhotoLikes = null; foreach (var item in sortedResult) { var photoId = PhotoId.FromDbValue(item[FieldMappings.PartitionKey].S); string recordType = item[FieldMappings.RecordType].S; if (latestPhotoId != photoId) { if (currentResult != null) { result.Add(currentResult); } currentResult = new PhotoWithCommentsAndLikes(); currentResult.Comments = (currentPhotoComments = new List <PhotoComment>()); currentResult.Likes = (currentPhotoLikes = new List <PhotoLikeRecord>()); latestPhotoId = photoId; } switch (recordType.ToLowerInvariant()) { case "photo": currentResult.Photo = Mappers.PhotoModel.FromDbItem(item); break; case "photocomment": currentPhotoComments.Add(Mappers.PhotoComment.FromDbItem(item)); break; case "photolike": currentPhotoLikes.Add(Mappers.PhotoLike.FromDbItem(item)); break; default: logWriter.LogWarning($"RecordType '{recordType}' was not expected in {nameof(ParsePhotosWithCommentsAndLikes)}"); break; } } if (currentResult != null) { result.Add(currentResult); } return(result); }
public async Task <string> SetPhotoText(PhotoId photoId, UserId userId, string text) { logWriter.LogInformation($"{nameof(SetPhotoText)}({nameof(photoId)} = '{photoId}', {nameof(userId)} = '{userId}', {nameof(text)} = '{text}')"); var utcNow = DateTimeOffset.UtcNow; IEnumerable <HashtagModel> hashtags = GetHashtags(photoId, text, utcNow); await dataRepository.SetPhotoText(userId, photoId, text, hashtags); return(text.GetHtmlText(textSplitter)); }
public PhotoModel FromDbItem(Dictionary <string, AttributeValue> input) { PhotoId photoId = input.TryGetValue(FieldMappings.Photo.PhotoId, out var photoIdValue) ? PhotoId.FromDbValue(photoIdValue.S) : (PhotoId)Guid.Empty; var result = new PhotoModel { CreatedTime = input.GetDateTimeOffset(FieldMappings.Photo.CreatedTime), PhotoId = photoId, ObjectKey = input.GetString(FieldMappings.Photo.ObjectKey), State = input.GetValue(FieldMappings.Photo.State, value => (PhotoState)Enum.Parse(typeof(PhotoState), value)), UserId = input.GetValue(FieldMappings.Photo.UserId, value => UserId.FromDbValue(value)), UserName = input.GetString(FieldMappings.Photo.UserName), LikeCount = input.GetInt32(FieldMappings.Photo.LikeCount), CommentCount = input.GetInt32(FieldMappings.Photo.CommentCount), Hashtags = input.GetList(FieldMappings.Photo.Hashtags, value => new HashtagModel { PhotoId = photoId, Hashtag = value }) }; if (input.TryGetValue(FieldMappings.Photo.RawText, out var rawCommentValue)) { result.RawText = rawCommentValue.S; } if (input.TryGetValue(FieldMappings.Photo.Score, out var scoreValue)) { result.Score = double.Parse(scoreValue.N); } if (input.TryGetValue(FieldMappings.Photo.Sizes, out var sizeValues)) { result.Sizes = sizeValues.L.Select(value => { int width; int height; if (!value.M.TryGetValue("Width", out var widthValue) || !int.TryParse(widthValue.S, out width)) { throw new Exception($"Failed to parse '{widthValue.S}' as a Size Width"); } if (!value.M.TryGetValue("Height", out var heightValue) || !int.TryParse(heightValue.S, out height)) { throw new Exception($"Failed to parse '{heightValue.S}' as a Size Height"); } return(new Size(width, height)); }); } return(result); }
public IObservable <SavePhotoModel> GetPhotoInfoForSave(PhotoId x) { return(Observable.FromAsync(async(ct) => { var photoTask = _m.GetPhotoInfo(x, ct); var locationTask = _m.GetLocation(x, ct); var sizesTask = _m.GetSizes(x, ct); await Task.WhenAll(photoTask, locationTask, sizesTask); var model = new SavePhotoModel(photoTask.Result, locationTask.Result, sizesTask.Result); return model; })); }
public async Task <Location> GetLocation(PhotoId photoId, CancellationToken ct) { var locationResponse = await _pictureFetchingService.GetLocation(photoId.flickrId, ct); if (locationResponse == null) { ColoredConsole.WriteLine($"empty location for photo #{photoId.flickrId}", Colors.txtWarning); return(null); } ColoredConsole.WriteLine($" > location for #{photoId.flickrId} fetched: {locationResponse.photo.location.place_id}", Colors.txtInfo); return(locationResponse.photo.location); }
private async Task <IActionResult> SetLikeState(PhotoId photoId, UserId userId, bool likeState) { try { await feedbackService.SetLikeState(photoId, userId, likeState); } catch (Exception ex) { logger.LogError(ex, $"Error in {nameof(SetLikeState)}({photoId})"); throw; } return(Json("OK")); }
public Task <IEnumerable <PhotoComment> > GetComments(PhotoId photoId) { logWriter.LogInformation($"{nameof(GetComments)}({nameof(photoId)} = '{photoId}')"); QueryRequest request = new QueryRequest { TableName = tableName, KeyConditions = new Dictionary <string, Condition> { { FieldMappings.PartitionKey, dynamoDbCore.GetStringEqualsCondition(photoId.ToDbValue()) }, { FieldMappings.SortKey, dynamoDbCore.GetStringBeginsWithCondition("comment") } } }; return(dynamoDbCore.Query(request, Mappers.PhotoComment)); }
public async Task <PhotoState> GetPhotoState(PhotoId photoId) { logWriter.LogInformation($"{nameof(GetPhotoState)}({nameof(photoId)} = '{photoId}')"); var request = new GetItemRequest(tableName, new Dictionary <string, AttributeValue> { { FieldMappings.PartitionKey, new AttributeValue(photoId.ToDbValue()) }, { FieldMappings.SortKey, new AttributeValue(photoId.ToDbValue()) } }); var photo = await dynamoDbCore.GetItem(request, Mappers.PhotoModel); if (photo == null) { logWriter.LogInformation($"No photo found for id '{photoId.ToDbValue()}'"); return(PhotoState.Undefined); } return(photo.State); }
private async Task <IEnumerable <PhotoId> > GetTouchedPhotoIds(DateTimeOffset dateTimeOffset) { logWriter.LogInformation($"{nameof(GetTouchedPhotoIds)}({nameof(dateTimeOffset)} = '{dateTimeOffset.ToString(Constants.DateTimeFormatWithMilliseconds)}')"); ScanRequest query = new ScanRequest(tableName) { // 'photolike', 'photocomment', 'photo' IndexName = "GSI2", FilterExpression = $"(RecordType = :photolike OR RecordType = :photocomment OR RecordType = :photo) AND CreatedTime > :createdTime", ExpressionAttributeValues = new Dictionary <string, AttributeValue> { { ":photolike", new AttributeValue("photolike") }, { ":photocomment", new AttributeValue("photocomment") }, { ":photo", new AttributeValue("photo") }, { ":createdTime", new AttributeValue(dateTimeOffset.ToString(Constants.DateTimeFormatWithMilliseconds)) }, }, ReturnConsumedCapacity = ReturnConsumedCapacity.INDEXES }; var response = await dynamoDbCore.Scan(query, Mappers.Noop); return(response.Select(x => PhotoId.FromDbValue(x[FieldMappings.PartitionKey].S)).Distinct()); }
private async Task AddLikeRecord(UserId userId, PhotoId photoId) { logWriter.LogInformation($"{nameof(AddLikeRecord)}({nameof(userId)} = '{userId}', {nameof(photoId)} = '{photoId}')"); var photo = await photoRepository.GetPhotoById(photoId, Guid.Empty); if (photo.PhotoIsLikedByCurrentUser) { return; } PhotoLikeRecord likeRecord = new PhotoLikeRecord { UserId = userId, PhotoId = photoId, CreatedTime = DateTimeOffset.UtcNow }; var scoreDelta = scoreCalculator.GetLikeScore(likeRecord); var putItemRequest = new PutItemRequest() { TableName = tableName, Item = Mappers.PhotoLike.ToDbItem(likeRecord), }; try { await dynamoDbCore.PutItem(putItemRequest); await UpdateLikeCountAndScore(photo, 1, scoreDelta); } catch (Exception ex) { logWriter.LogError(ex, $"Error in {nameof(AddLikeRecord)}({nameof(userId)} = '{userId}', {nameof(photoId)} = '{photoId}'):\n{ex.ToString()}"); throw; } }
public async Task <bool> RequireDatabaseUpdate(PhotoId photoId, CancellationToken ct) { var(flickrId, title) = photoId; try { var photo = await _photoRepository.GetByHostingId(flickrId); if (photo == null) { return(true); } // ColoredConsole.WriteLine("already saved: " + JsonSerializer.Serialize(photo), Colors.txtWarning); // todo: detect if caption is changed and the reupload is required. return(false); } catch (Exception e) { ColoredConsole.WriteLine($"{e}", Colors.bgDanger); return(false); } }
=> Apply(new PhotoCreated(PhotoId, name));
private async Task AddLikeDataForUser(UserId currentUserId, ConcurrentDictionary <PhotoId, PhotoModel> result) { if (currentUserId != (UserId)Guid.Empty) { var request = new QueryRequest { TableName = tableName, IndexName = "GSI1", KeyConditionExpression = $"{FieldMappings.Gsi1PartitionKey} = :userId and {FieldMappings.SortKey} = :userlike", ExpressionAttributeValues = new Dictionary <string, AttributeValue> { { ":userId", new AttributeValue { S = currentUserId.ToDbValue() } }, { ":userlike", new AttributeValue { S = $"like|{currentUserId.ToDbValue()}" } } }, ProjectionExpression = FieldMappings.PartitionKey }; var likedPhotoIds = (await dynamoDbCore.Query(request, Mappers.Noop)).Select(x => PhotoId.FromDbValue(x[FieldMappings.PartitionKey].S)); foreach (var photoId in likedPhotoIds) { if (result.TryGetValue(photoId, out var photo)) { photo.PhotoIsLikedByCurrentUser = true; } } } }
private async Task <IEnumerable <Dictionary <string, AttributeValue> > > GetAllItemKeys(PhotoId photoId) { logWriter.LogInformation($"{nameof(GetAllItemKeys)}({nameof(photoId)} = '{photoId}')"); var photo = await GetPhotoById(photoId, Guid.Empty); UserId userId = photo.UserId; QueryRequest requestForPhotoWithCommentsAndLikes = new QueryRequest(tableName) { KeyConditionExpression = $"{FieldMappings.PartitionKey} = :pkvalue", ExpressionAttributeValues = new Dictionary <string, AttributeValue> { { ":pkvalue", new AttributeValue(photoId.ToDbValue()) } } }; // get all items related to the photo (mainly hashtags) QueryRequest requestForItemsRelatedToPhoto = new QueryRequest(tableName) { IndexName = "GSI1", KeyConditionExpression = $"{FieldMappings.Gsi1PartitionKey} = :userId AND {FieldMappings.SortKey} = :photoId", ExpressionAttributeValues = new Dictionary <string, AttributeValue> { { ":userId", new AttributeValue(userId.ToDbValue()) }, { ":photoId", new AttributeValue(photoId.ToDbValue()) } } }; var queryTasks = new[] { dynamoDbCore.Query(requestForPhotoWithCommentsAndLikes, Mappers.Noop), dynamoDbCore.Query(requestForItemsRelatedToPhoto, Mappers.Noop), }; await Task.WhenAll(queryTasks); var comparer = EqualityComparer <Dictionary <string, AttributeValue> > .Default; return(queryTasks // flatten into one sequence of dictionaries .SelectMany(x => x.Result) // strip all values except main partition- and sort key .Select(x => x.Where(y => y.Key == FieldMappings.PartitionKey || y.Key == FieldMappings.SortKey).ToDictionary(y => y.Key, y => y.Value)) // filter down to distinct values .Distinct(comparer)); }