Skip to content

Commit

Permalink
Report Feature Enhancement
Browse files Browse the repository at this point in the history
- User can only report photo once.
- User cannot report own photo.
- Once a photo is reported three times, its status is changed to "Under review."
- Photos placed "Under review" are removed from all streams except for user's own profile
  • Loading branch information
ghki authored Jul 6, 2016
1 parent dea0afa commit 8cb7869
Show file tree
Hide file tree
Showing 14 changed files with 153 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public abstract class EnvironmentDefinitionBase
private const int FirstProfilePhotoUpdateGoldAwardCount = 5;
private const string IapCertificateValidationContainer = "validation";
private const string IapPublicCertificateName = "IapReceiptProduction.cer";
private const int MaxReportsPermitted = 3;
private const int NewUserGoldAwardCount = 40;
private CloudStorageAccount _storageAccount;

Expand Down Expand Up @@ -96,6 +97,14 @@ public virtual string IapValidationContainer
/// </summary>
public abstract string InstrumentationKey { get; }

/// <summary>
/// Maximum number of reports permitted until photo is "under review"
/// </summary>
public virtual int MaxReports
{
get { return MaxReportsPermitted; }
}

/// <summary>
/// Default new user gold balance.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,10 @@ public string DocumentType
public string OSPlatform { get; set; }

/// <summary>
/// The report for this photo.
/// The reports for this photo.
/// </summary>
[JsonProperty(PropertyName = "Report")]
public ReportDocument Report { get; set; }
[JsonProperty(PropertyName = "Reports")]
public List<ReportDocument> Reports { get; set; } = new List<ReportDocument>();

/// <summary>
/// The standard-sized image url.
Expand Down Expand Up @@ -202,6 +202,7 @@ public PhotoContract ToContract(ICollection<UserDocument> userDocumentsForPhotos
NumberOfGoldVotes = GoldCount,
NumberOfAnnotations = Annotations.Count,
OSPlatform = OSPlatform,
Reports = Reports.Select(r => r.ToContract(Id, ContentType.Photo)).ToList(),
Status = Status
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,16 @@ public Task<PhotoContract> GetPhoto(string id)
return _repository.GetPhoto(id);
}

/// <summary>
/// Gets the list of photos with a specific status.
/// </summary>
/// <param name="status">The photo status.</param>
/// <returns>A list of photos with provided status.</returns>
public async Task<PagedResponse<PhotoContract>> GetPhotosWithStatus(PhotoStatus status)
{
return await _repository.GetPhotosWithStatus(status);
}

/// <summary>
/// Gets the user by an existing app user id OR registrationReference
/// from Azure App Services auth mechanism as the userId may not be known
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,15 @@ public class DocumentDbRepository : IRepository

private readonly int _firstProfilePhotoUpdateGoldIncrement;
private readonly int _newUserGoldBalance;
private readonly int _maxReportsPermitted;

/// <summary>
/// The <see cref="DocumentDbRepository" /> constructor.
/// </summary>
/// <param name="environmentDefinition">The specified environment definition.</param>
public DocumentDbRepository(EnvironmentDefinitionBase environmentDefinition)
{
_maxReportsPermitted = environmentDefinition.MaxReports;
_newUserGoldBalance = environmentDefinition.NewUserGoldBalance;
_firstProfilePhotoUpdateGoldIncrement = environmentDefinition.FirstProfilePhotoUpdateGoldAward;

Expand Down Expand Up @@ -421,7 +423,7 @@ public async Task<IList<CategoryPreviewContract>> GetCategoriesPreview(int numbe
numberOfThumbnails,
_currentDocumentVersion);


var mostRecentCategoryPhotos = photosQuery.Response;

if (mostRecentCategoryPhotos != null && mostRecentCategoryPhotos.Any())
Expand Down Expand Up @@ -733,6 +735,35 @@ private PhotoDocument GetPhotoDocument(string id)
return document;
}

/// <summary>
/// Gets the list of photos with a specific status.
/// </summary>
/// <param name="status">The photo status.</param>
/// <returns>A list of photos with provided status.</returns>
public async Task<PagedResponse<PhotoContract>> GetPhotosWithStatus(PhotoStatus status)
{
var query = _documentClient.CreateDocumentQuery<PhotoDocument>(DocumentCollectionUri)
.Where(d => d.DocumentType == PhotoDocument.DocumentTypeIdentifier)
.Where(d => d.DocumentVersion == _currentDocumentVersion)
.Where(p => p.Status == status);

var documentQuery = query
.OrderByDescending(p => p.CreatedDateTime.Epoch)
.AsDocumentQuery();

var documentResponse = await documentQuery.ExecuteNextAsync<PhotoDocument>();

var photoContracts = await CreatePhotoContractsAndLoadUserData(documentResponse.ToList());

var result = new PagedResponse<PhotoContract>
{
Items = photoContracts,
ContinuationToken = documentResponse.ResponseContinuation
};

return result;
}

private string GetRecentPhotosForCategoriesStoredProcedureUri
{
get { return $"{StoredProceduresUri}/{GetRecentPhotosForCategoriesStoredProcedureId}"; }
Expand Down Expand Up @@ -1025,7 +1056,13 @@ public async Task<ReportContract> InsertReport(ReportContract reportContract, st
{
case ContentType.Photo:
var photoDocument = GetPhotoDocument(reportContract.ContentId);
photoDocument.Report = reportDocument;
photoDocument.Reports.Add(reportDocument);

if (photoDocument.Reports.Count >= _maxReportsPermitted)
{
photoDocument.Status = PhotoStatus.UnderReview;
}

await ReplacePhotoDocument(photoDocument);
break;
case ContentType.Annotation:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,13 @@ Task<LeaderboardContract> GetLeaderboard(int mostGoldCategoriesCount, int mostGo
/// <returns>The requested photo.</returns>
Task<PhotoContract> GetPhoto(string id);

/// <summary>
/// Gets the list of photos with a specific status.
/// </summary>
/// <param name="status">The photo status.</param>
/// <returns>A list of photos with provided status.</returns>
Task<PagedResponse<PhotoContract>> GetPhotosWithStatus(PhotoStatus status);

/// <summary>
/// Gets the user by an existing app user id OR registrationReference
/// from Azure App Services auth mechanism as the userId may not be known
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using PhotoSharingApp.AppService.Shared;
using PhotoSharingApp.AppService.Shared.Context;
using PhotoSharingApp.AppService.Shared.Models.DocumentDB;
using PhotoSharingApp.AppService.Shared.Repositories;
using PhotoSharingApp.AppService.Tests.Context;
using PhotoSharingApp.Portable.DataContracts;
Expand Down Expand Up @@ -182,6 +183,38 @@ public async Task InsertAndDeletePhotoTest()
await _repository.GetPhoto(actionResult.Id);
}

[TestMethod]
public async Task PhotoStatusChangeAfterReportsTest()
{
// Populate db with necessary objects
var user1 = await _repository.CreateUser("test user " + System.DateTime.UtcNow.Ticks);
var category = await _repository.CreateCategory("Test Category " + System.DateTime.UtcNow.Ticks);
var photo = await _repository.InsertPhoto(CreateTestPhoto(category, user1), 1);
var maxReports = _environmentDefinition.MaxReports;

// Verify that every report before maxReports does not change photo status.
for (int i = 0; i < maxReports - 1; i++)
{
var newUser = await _repository.CreateUser("test user " + System.DateTime.UtcNow.Ticks);
var newReport = CreateTestReport(photo.Id, ContentType.Photo, ReportReason.Spam, newUser.UserId);
await _repository.InsertReport(newReport, newUser.RegistrationReference);
var updatedPhoto = await _repository.GetPhoto(photo.Id);

// Sanity checks
Assert.AreEqual(PhotoStatus.Active, updatedPhoto.Status);
Assert.AreEqual(i + 1, updatedPhoto.Reports.Count);
}

var user2 = await _repository.CreateUser("test user " + System.DateTime.UtcNow.Ticks);
var finalReport = CreateTestReport(photo.Id, ContentType.Photo, ReportReason.Spam, user2.UserId);
await _repository.InsertReport(finalReport, user2.RegistrationReference);
var finalUpdatedPhoto = await _repository.GetPhoto(photo.Id);

// Verify
Assert.AreEqual(maxReports, finalUpdatedPhoto.Reports.Count, "Reports count mismatch.");
Assert.AreEqual(PhotoStatus.UnderReview, finalUpdatedPhoto.Status, "Photo is not under review.");
}

/// <summary>
/// Files a report against an annotation
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,11 @@ public Task<PhotoContract> GetPhoto(string id)
throw new NotImplementedException();
}

public Task<PagedResponse<PhotoContract>> GetPhotosWithStatus(PhotoStatus status)
{
throw new NotImplementedException();
}

public Task<UserContract> GetUser(string userId, string registrationReference = null)
{
throw new NotImplementedException();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,11 @@ public class PhotoContract
/// </summary>
public long Rank { get; set; }

/// <summary>
/// The reports for this photo
/// </summary>
public List<ReportContract> Reports { get; set; } = new List<ReportContract>();

/// <summary>
/// The standard-sized image url.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ public enum PhotoStatus
/// <summary>
/// Photo is hidden.
/// </summary>
Hidden = 5
Hidden = 5,

/// <summary>
/// Photo is under review.
/// </summary>
UnderReview = 6
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ public static Photo ToDataModel(this PhotoContract photoContract)
NumberOfAnnotations = photoContract.NumberOfAnnotations,
GoldCount = photoContract.NumberOfGoldVotes,
CategoryName = photoContract.CategoryName,
Reports = photoContract.Reports,
Status = photoContract.Status
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ public static string ToReadableString(this PhotoStatus photoStatus)
return "Hidden";
case PhotoStatus.ObjectionableContent:
return "Objectionable content";
case PhotoStatus.UnderReview:
return "Under review";
default:
return "Unknown";
}
Expand Down
6 changes: 6 additions & 0 deletions PhotoSharingApp/PhotoSharingApp.Universal/Models/Photo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
// ---------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using PhotoSharingApp.Portable.DataContracts;
using PhotoSharingApp.Universal.ComponentModel;
Expand Down Expand Up @@ -163,6 +164,11 @@ public bool IsProfilePicture
/// </summary>
public int NumberOfAnnotations { get; set; }

/// <summary>
/// The reports for this photo.
/// </summary>
public List<ReportContract> Reports { get; set; } = new List<ReportContract>();

/// <summary>
/// Gets or sets the standard image URL.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,30 @@ public bool IsUserAbleToReportAnnotation
}
}

/// <summary>
/// Determines if user can report the photo.
/// </summary>
public bool IsUserAbleToReportPhoto
{
get
{
if (AppEnvironment.Instance.CurrentUser == null ||
AppEnvironment.Instance.CurrentUser.UserId == _photo.User.UserId)
{
return false;
}

foreach (var report in _photo.Reports)
{
if (report.ReporterUserId == AppEnvironment.Instance.CurrentUser.UserId)
{
return false;
}
}
return true;
}
}

/// <summary>
/// Determines if user can update the photo.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,8 @@
Visibility="{Binding IsUserAbleToReportAnnotation, Mode=TwoWay, Converter={StaticResource BooleanToVisibilityConverter}}"
Label="Report Annotation" Command="{Binding ReportAnnotationCommand}" />
<AppBarButton Icon="ReportHacked"
Label="Report Photo">
Visibility="{Binding IsUserAbleToReportPhoto, Mode=TwoWay, Converter={StaticResource BooleanToVisibilityConverter}}"
Label="Report Photo" Command="{Binding ReportPhotoCommand}" >
<AppBarButton.Flyout>
<Flyout>
<ListView x:Name="reportPhotoRoot" ItemsSource="{Binding ReportReasons}" SelectionMode="None">
Expand Down

0 comments on commit 8cb7869

Please sign in to comment.