Add basic level review support

This commit is contained in:
LumaLivy 2021-11-19 21:47:08 -05:00
commit fde5505b5f
7 changed files with 337 additions and 9 deletions

View file

@ -1,8 +1,15 @@
#nullable enable
using System;
using System.IO;
using System.Xml.Serialization;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Serialization;
using LBPUnion.ProjectLighthouse.Types;
using LBPUnion.ProjectLighthouse.Types.Levels;
using LBPUnion.ProjectLighthouse.Types.Reviews;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
@ -74,5 +81,177 @@ namespace LBPUnion.ProjectLighthouse.Controllers
return this.Ok();
}
[HttpPost("postReview/user/{slotId:int}")]
public async Task<IActionResult> PostReview(int slotId) {
User? user = await this.database.UserFromRequest(this.Request);
if (user == null) return this.StatusCode(403, "");
Review? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.SlotId == slotId && r.ReviewerId == user.UserId);
Review? newReview = await this.GetReviewFromBody();
if (newReview == null) return this.BadRequest();
if (review == null) {
review = new();
review.SlotId = slotId;
review.ReviewerId = user.UserId;
review.DeletedBy = "none";
}
review.LabelCollection = newReview.LabelCollection;
review.Text = newReview.Text;
review.Deleted = false;
review.Timestamp = TimeHelper.UnixTimeMilliseconds();
// sometimes the game posts a review without also calling dpadrate/user/etc (why??)
RatedLevel? ratedLevel = await this.database.RatedLevels.FirstOrDefaultAsync(r => r.SlotId == slotId && r.UserId == user.UserId);
if (ratedLevel == null)
{
ratedLevel = new RatedLevel();
ratedLevel.SlotId = slotId;
ratedLevel.UserId = user.UserId;
ratedLevel.RatingLBP1 = 0;
this.database.RatedLevels.Add(ratedLevel);
}
ratedLevel.Rating = newReview.Thumb;
await this.database.SaveChangesAsync();
return this.Ok();
}
[HttpGet("reviewsFor/user/{slotId:int}")]
public async Task<IActionResult> ReviewsFor(int slotId, [FromQuery] int pageStart = 1, [FromQuery] int pageSize = 10)
{
User? user = await this.database.UserFromRequest(this.Request);
if (user == null) return this.StatusCode(403, "");
Token? token = await this.database.TokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
GameVersion gameVersion = token.GameVersion;
Random rand = new();
IEnumerable<Review> reviews = this.database.Reviews.Where(r => r.SlotId == slotId && r.Slot.GameVersion <= gameVersion)
.Include(r => r.Reviewer)
.Include(r => r.Slot)
.AsEnumerable() // performance? Needed for next line (ThumbsUp is not in DB)
.OrderByDescending(r => r.ThumbsUp)
.ThenByDescending(_ => rand.Next())
.Skip(pageStart - 1)
.Take(pageSize);
string inner = Enumerable.Aggregate(reviews, string.Empty, (current, review) => {
RatedLevel? ratedLevel = this.database.RatedLevels.FirstOrDefault(r => r.SlotId == slotId && r.UserId == review.ReviewerId);
RatedReview? ratedReview = this.database.RatedReviews.FirstOrDefault(r => r.ReviewId == review.ReviewId && r.UserId == user.UserId);
return current + review.Serialize(ratedLevel, ratedReview);
});
string response = LbpSerializer.TaggedStringElement("reviews", inner, new Dictionary<string, object>
{
{
"hint_start", pageStart + pageSize
},
{
"hint", pageStart // not sure
},
});
return this.Ok(response);
}
[HttpGet("reviewsBy/{username}")]
public async Task<IActionResult> ReviewsBy(string username, [FromQuery] int pageStart = 1, [FromQuery] int pageSize = 10)
{
User? user = await this.database.UserFromRequest(this.Request);
if (user == null) return this.StatusCode(403, "");
Token? token = await this.database.TokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
GameVersion gameVersion = token.GameVersion;
IEnumerable<Review> reviews = this.database.Reviews.Where(r => r.Reviewer.Username == username && r.Slot.GameVersion <= gameVersion)
.Include(r => r.Reviewer)
.Include(r => r.Slot)
.AsEnumerable() // performance?
.OrderByDescending(r => r.Timestamp)
.Skip(pageStart - 1)
.Take(pageSize);
string inner = Enumerable.Aggregate(reviews, string.Empty, (current, review) => {
RatedLevel? ratedLevel = this.database.RatedLevels.FirstOrDefault(r => r.SlotId == review.SlotId && r.UserId == user.UserId);
RatedReview? ratedReview = this.database.RatedReviews.FirstOrDefault(r => r.ReviewId == review.ReviewId && r.UserId == user.UserId);
return current + review.Serialize(ratedLevel, ratedReview);
});
string response = LbpSerializer.TaggedStringElement("reviews", inner, new Dictionary<string, object>
{
{
"hint_start", pageStart
},
{
"hint", reviews.Last().Timestamp // Seems to be the timestamp of oldest
},
});
return this.Ok(response);
}
[HttpPost("rateReview/user/{slotId:int}/{username}")]
public async Task<IActionResult> RateReview(int slotId, string username, [FromQuery] int rating = 0) {
User? user = await this.database.UserFromRequest(this.Request);
if (user == null) return this.StatusCode(403, "");
User? reviewer = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username);
if (reviewer == null) return this.StatusCode(403, "");
Review? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.SlotId == slotId && r.ReviewerId == reviewer.UserId);
if (review == null) return this.StatusCode(403, "");
RatedReview? ratedReview = await this.database.RatedReviews.FirstOrDefaultAsync(r => r.ReviewId == review.ReviewId && r.UserId == user.UserId);
if (ratedReview == null)
{
ratedReview = new RatedReview();
ratedReview.ReviewId = review.ReviewId;
ratedReview.UserId = user.UserId;
ratedReview.Thumb = 0;
this.database.RatedReviews.Add(ratedReview);
}
ratedReview.Thumb = Math.Max(Math.Min(1, rating), -1);
await this.database.SaveChangesAsync();
return this.Ok();
}
[HttpPost("deleteReview/user/{slotId:int}/{username}")]
public async Task<IActionResult> DeleteReview(int slotId, string username) {
User? reviewer = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username);
if (reviewer == null) return this.StatusCode(403, "");
Review? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.SlotId == slotId && r.ReviewerId == reviewer.UserId);
if (review == null) return this.StatusCode(403, "");
review.Deleted = true;
review.DeletedBy = "level_author"; // other value is "moderator"
await this.database.SaveChangesAsync();
return this.Ok();
}
public async Task<Review?> GetReviewFromBody()
{
this.Request.Body.Position = 0;
string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync();
XmlSerializer serializer = new(typeof(Review));
Review? review = (Review?)serializer.Deserialize(new StringReader(bodyString));
return review;
}
}
}

View file

@ -7,6 +7,7 @@ using LBPUnion.ProjectLighthouse.Serialization;
using LBPUnion.ProjectLighthouse.Types;
using LBPUnion.ProjectLighthouse.Types.Levels;
using LBPUnion.ProjectLighthouse.Types.Settings;
using LBPUnion.ProjectLighthouse.Types.Reviews;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
@ -85,7 +86,8 @@ namespace LBPUnion.ProjectLighthouse.Controllers
RatedLevel? ratedLevel = await this.database.RatedLevels.FirstOrDefaultAsync(r => r.SlotId == id && r.UserId == user.UserId);
VisitedLevel? visitedLevel = await this.database.VisitedLevels.FirstOrDefaultAsync(r => r.SlotId == id && r.UserId == user.UserId);
return this.Ok(slot.Serialize(ratedLevel, visitedLevel));
Review? yourReview = await this.database.Reviews.FirstOrDefaultAsync(r => r.SlotId == id && r.ReviewerId == user.UserId);
return this.Ok(slot.Serialize(ratedLevel, visitedLevel, yourReview));
}
[HttpGet("slots/lbp2cool")]

View file

@ -7,6 +7,7 @@ using LBPUnion.ProjectLighthouse.Types;
using LBPUnion.ProjectLighthouse.Types.Levels;
using LBPUnion.ProjectLighthouse.Types.Profiles;
using LBPUnion.ProjectLighthouse.Types.Settings;
using LBPUnion.ProjectLighthouse.Types.Reviews;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
@ -28,6 +29,8 @@ namespace LBPUnion.ProjectLighthouse
public DbSet<LastMatch> LastMatches { get; set; }
public DbSet<VisitedLevel> VisitedLevels { get; set; }
public DbSet<RatedLevel> RatedLevels { get; set; }
public DbSet<Review> Reviews { get; set; }
public DbSet<RatedReview> RatedReviews { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder options)
=> options.UseMySql(ServerSettings.DbConnectionString, MySqlServerVersion.LatestSupportedServerVersion);

View file

@ -5,6 +5,7 @@ using System.Linq;
using System.Xml.Serialization;
using LBPUnion.ProjectLighthouse.Serialization;
using LBPUnion.ProjectLighthouse.Types.Profiles;
using LBPUnion.ProjectLighthouse.Types.Reviews;
namespace LBPUnion.ProjectLighthouse.Types.Levels
{
@ -193,12 +194,22 @@ namespace LBPUnion.ProjectLighthouse.Types.Levels
[XmlElement("leveltype")]
public string LevelType { get; set; } = "";
[NotMapped]
[XmlElement("reviewCount")]
public int ReviewCount {
get {
using Database database = new();
return database.Reviews.Count(r => r.SlotId == this.SlotId);
}
}
public string SerializeResources()
{
return this.Resources.Aggregate("", (current, resource) => current + LbpSerializer.StringElement("resource", resource));
}
public string Serialize(RatedLevel? yourRatingStats = null, VisitedLevel? yourVisitedStats = null)
public string Serialize(RatedLevel? yourRatingStats = null, VisitedLevel? yourVisitedStats = null, Review? yourReview = null)
{
string slotData = LbpSerializer.StringElement("name", this.Name) +
@ -246,9 +257,12 @@ namespace LBPUnion.ProjectLighthouse.Types.Levels
LbpSerializer.StringElement("yourLBP1PlayCount", yourVisitedStats?.PlaysLBP1) +
LbpSerializer.StringElement("yourLBP2PlayCount", yourVisitedStats?.PlaysLBP2) +
LbpSerializer.StringElement("yourLBP3PlayCount", yourVisitedStats?.PlaysLBP3) +
LbpSerializer.StringElement
("yourLBPVitaPlayCount", yourVisitedStats?.PlaysLBPVita); // i doubt this is the right name but we'll go with it
LbpSerializer.StringElement("yourLBPVitaPlayCount", yourVisitedStats?.PlaysLBPVita) + // i doubt this is the right name but we'll go with it
yourReview?.Serialize("yourReview") +
LbpSerializer.StringElement("reviewsEnabled", true) +
LbpSerializer.StringElement("commentsEnabled", false) +
LbpSerializer.StringElement("reviewCount", this.ReviewCount);
return LbpSerializer.TaggedStringElement("slot", slotData, "type", "user");
}
}

View file

@ -0,0 +1,24 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace LBPUnion.ProjectLighthouse.Types.Reviews
{
public class RatedReview
{
// ReSharper disable once UnusedMember.Global
[Key]
public int RatedReviewId { get; set; }
public int UserId { get; set; }
[ForeignKey(nameof(UserId))]
public User User { get; set; }
public int ReviewId { get; set; }
[ForeignKey(nameof(ReviewId))]
public Review Review { get; set; }
public int Thumb { get; set; }
}
}

View file

@ -0,0 +1,101 @@
#nullable enable
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using LBPUnion.ProjectLighthouse.Types.Levels;
using LBPUnion.ProjectLighthouse.Serialization;
using System.Xml.Serialization;
namespace LBPUnion.ProjectLighthouse.Types.Reviews
{
[XmlRoot("review")]
[XmlType("review")]
public class Review
{
// ReSharper disable once UnusedMember.Global
[Key]
public int ReviewId { get; set; }
[XmlIgnore]
public int ReviewerId { get; set; }
[ForeignKey(nameof(ReviewerId))]
public User Reviewer { get; set; }
[XmlElement("slot_id")]
public int SlotId { get; set; }
[ForeignKey(nameof(SlotId))]
public Slot Slot { get; set; }
[XmlElement("timestamp")]
public long Timestamp { get; set; }
[XmlElement("labels")]
public string LabelCollection { get; set; }
[NotMapped]
[XmlIgnore]
public string[] Labels {
get => this.LabelCollection.Split(",");
set => this.LabelCollection = string.Join(',', value);
}
[XmlElement("deleted")]
public Boolean Deleted { get; set; }
[XmlElement("deleted_by")]
public string DeletedBy { get; set; } // enum ? Needs testing e.g. Moderated/Author/Level Author? etc.
[XmlElement("text")]
public string Text { get; set; }
[NotMapped]
[XmlElement("thumb")]
public int Thumb { get; set; } // (unused) -- temp value for getting thumb from review upload body for updating level rating
[NotMapped]
[XmlElement("thumbsup")]
public int ThumbsUp {
get {
using Database database = new();
return database.RatedReviews.Count(r => r.ReviewId == this.ReviewId && r.Thumb == 1);
}
}
[NotMapped]
[XmlElement("thumbsdown")]
public int ThumbsDown {
get {
using Database database = new();
return database.RatedReviews.Count(r => r.ReviewId == this.ReviewId && r.Thumb == -1);
}
}
public string Serialize(RatedLevel? yourLevelRating = null, RatedReview? yourRatingStats = null) {
return this.Serialize("review", yourLevelRating, yourRatingStats);
}
public string Serialize(string elementOverride, RatedLevel? yourLevelRating = null, RatedReview? yourRatingStats = null)
{
string reviewData = LbpSerializer.TaggedStringElement("slot_id", this.SlotId, "type", this.Slot.Type) +
LbpSerializer.StringElement("reviewer", this.Reviewer.Username) +
LbpSerializer.StringElement("thumb", yourLevelRating?.Rating) +
LbpSerializer.StringElement("timestamp", this.Timestamp) +
LbpSerializer.StringElement("labels", this.LabelCollection) +
LbpSerializer.StringElement("deleted", this.Deleted) +
LbpSerializer.StringElement("deleted_by", this.DeletedBy) +
LbpSerializer.StringElement("text", this.Text) +
LbpSerializer.StringElement("thumbsup", this.ThumbsUp) +
LbpSerializer.StringElement("thumbsdown", this.ThumbsDown) +
LbpSerializer.StringElement("yourthumb", yourRatingStats?.Thumb == null ? 0 : yourRatingStats?.Thumb);
return LbpSerializer.TaggedStringElement(elementOverride, reviewData, "id", this.SlotId + "." + this.Reviewer.Username);
}
}
}

View file

@ -24,13 +24,18 @@ namespace LBPUnion.ProjectLighthouse.Types
public string Biography { get; set; }
[NotMapped]
public int Reviews => 0;
public int Reviews {
get {
using Database database = new();
return database.Reviews.Count(r => r.ReviewerId == this.UserId);
}
}
[NotMapped]
public int Comments {
get {
using Database database = new();
return database.Comments.Count(c => c.PosterUserId == this.UserId);
return database.Comments.Count(c => c.TargetUserId == this.UserId);
}
}
@ -114,8 +119,8 @@ namespace LBPUnion.ProjectLighthouse.Types
LbpSerializer.StringElement("pins", this.Pins) +
LbpSerializer.StringElement("planets", this.PlanetHash) +
LbpSerializer.BlankElement("photos") +
LbpSerializer.StringElement("heartCount", this.Hearts);
this.ClientsConnected.Serialize();
LbpSerializer.StringElement("heartCount", this.Hearts)
+ this.ClientsConnected.Serialize();
return LbpSerializer.TaggedStringElement("user", user, "type", "user");
}