mirror of
https://github.com/LBPUnion/ProjectLighthouse.git
synced 2025-07-29 16:38:37 +00:00
Add basic level review support
This commit is contained in:
parent
93fe204563
commit
fde5505b5f
7 changed files with 337 additions and 9 deletions
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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")]
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
24
ProjectLighthouse/Types/Reviews/RatedReview.cs
Normal file
24
ProjectLighthouse/Types/Reviews/RatedReview.cs
Normal 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; }
|
||||
}
|
||||
}
|
101
ProjectLighthouse/Types/Reviews/Review.cs
Normal file
101
ProjectLighthouse/Types/Reviews/Review.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue