Merge branch 'main' into smtp

This commit is contained in:
jvyden 2022-03-01 14:43:14 -05:00
commit ff19bc6ffd
No known key found for this signature in database
GPG key ID: 18BCF2BE0262B278
42 changed files with 490 additions and 218 deletions

View file

@ -2,6 +2,8 @@
<project version="4">
<component name="DiscordProjectSettings">
<option name="show" value="PROJECT_FILES" />
<option name="nameOverrideEnabled" value="true" />
<option name="nameOverrideText" value="Project Lighthouse" />
<option name="description" value="" />
</component>
</project>

View file

@ -16,8 +16,8 @@ public class DatabaseTests : LighthouseServerTest
await using Database database = new();
int rand = new Random().Next();
User userA = await database.CreateUser("createUserTwiceTest" + rand, HashHelper.GenerateAuthToken());
User userB = await database.CreateUser("createUserTwiceTest" + rand, HashHelper.GenerateAuthToken());
User userA = await database.CreateUser("unitTestUser" + rand, HashHelper.GenerateAuthToken());
User userB = await database.CreateUser("unitTestUser" + rand, HashHelper.GenerateAuthToken());
Assert.NotNull(userA);
Assert.NotNull(userB);

View file

@ -133,6 +133,7 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=PCSA/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=PCSD/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=PCSF/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Sublevels/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Swingy/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=thumbsup/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=topscores/@EntryIndexedValue">True</s:Boolean>
@ -144,4 +145,5 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=Unheart/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Unpublish/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Unpushed/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=yourlbp/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=yourthumb/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View file

@ -1,10 +1,13 @@
#nullable enable
using System.Threading.Tasks;
using LBPUnion.ProjectLighthouse.Types;
using LBPUnion.ProjectLighthouse.Types.Profiles;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
// ReSharper disable RouteTemplates.ActionRoutePrefixCanBeExtractedToControllerRoute
namespace LBPUnion.ProjectLighthouse.Controllers.Api;
/// <summary>
@ -36,4 +39,21 @@ public class UserEndpoints : ApiEndpointController
return this.Ok(user);
}
/// <summary>
/// Gets a user and their information from the database.
/// </summary>
/// <param name="id">The ID of the user</param>
/// <returns>The user's status</returns>
/// <response code="200">The user's status, if successful.</response>
/// <response code="404">The user could not be found.</response>
[HttpGet("user/{id:int}/status")]
[ProducesResponseType(typeof(UserStatus), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> GetUserStatus(int id)
{
UserStatus userStatus = new(this.database, id);
return this.Ok(userStatus);
}
}

View file

@ -5,7 +5,6 @@ using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Xml.Serialization;
using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Serialization;
using LBPUnion.ProjectLighthouse.Types;
using LBPUnion.ProjectLighthouse.Types.Levels;
@ -39,14 +38,13 @@ public class CommentController : ControllerBase
return this.Ok();
}
[HttpGet("comments/user/{slotId:int}")]
[HttpGet("userComments/{username}")]
public async Task<IActionResult> GetComments([FromQuery] int pageStart, [FromQuery] int pageSize, string? username, int? slotId)
{
User? user = await this.database.UserFromGameRequest(this.Request);
if (user == null) return this.StatusCode(403, "");
int targetId = slotId.GetValueOrDefault();
CommentType type = CommentType.Level;
if (!string.IsNullOrWhiteSpace(username))
@ -55,24 +53,24 @@ public class CommentController : ControllerBase
type = CommentType.Profile;
}
List<Comment> comments = await this.database.Comments
.Include(c => c.Poster)
List<Comment> comments = await this.database.Comments.Include
(c => c.Poster)
.Where(c => c.TargetId == targetId && c.Type == type)
.OrderByDescending(c => c.Timestamp)
.Skip(pageStart - 1)
.Take(Math.Min(pageSize,
30))
.Take(Math.Min(pageSize, 30))
.ToListAsync();
string outputXml = comments.Aggregate(string.Empty, (current, comment) => current +
comment.Serialize(this.getReaction(user.UserId, comment.CommentId).Result));
string outputXml = comments.Aggregate
(string.Empty, (current, comment) => current + comment.Serialize(this.getReaction(user.UserId, comment.CommentId).Result));
return this.Ok(LbpSerializer.StringElement("comments", outputXml));
}
public async Task<int> getReaction(int userId, int commentId)
private async Task<int> getReaction(int userId, int commentId)
{
Reaction? reaction = await this.database.Reactions.FirstOrDefaultAsync(r => r.UserId == userId && r.TargetId == commentId);
if (reaction == null) return 0;
return reaction.Rating;
}
@ -80,11 +78,11 @@ public class CommentController : ControllerBase
[HttpPost("postComment/user/{slotId:int}")]
public async Task<IActionResult> PostComment(string? username, int? slotId)
{
this.Request.Body.Position = 0;
this.Request.Body.Position = 0;
string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync();
XmlSerializer serializer = new(typeof(Comment));
Comment? comment = (Comment?) serializer.Deserialize(new StringReader(bodyString));
Comment? comment = (Comment?)serializer.Deserialize(new StringReader(bodyString));
CommentType type = (slotId.GetValueOrDefault() == 0 ? CommentType.Profile : CommentType.Level);
@ -112,6 +110,7 @@ public class CommentController : ControllerBase
Comment? comment = await this.database.Comments.FirstOrDefaultAsync(c => c.CommentId == commentId);
if (comment == null) return this.NotFound();
// if you are not the poster
if (comment.PosterUserId != user.UserId)
{

View file

@ -34,10 +34,6 @@ public class LoginController : ControllerBase
await this.Request.Body.CopyToAsync(ms);
byte[] loginData = ms.ToArray();
#if DEBUG
await IOFile.WriteAllBytesAsync($"npTicket-{TimestampHelper.TimestampMillis}.txt", loginData);
#endif
NPTicket? npTicket;
try
{
@ -145,7 +141,7 @@ public class LoginController : ControllerBase
await this.database.SaveChangesAsync();
// Create a new room on LBP2/3/Vita
if (token.GameVersion != GameVersion.LittleBigPlanet1) RoomHelper.CreateRoom(user, token.GameVersion);
if (token.GameVersion != GameVersion.LittleBigPlanet1) RoomHelper.CreateRoom(user, token.GameVersion, token.Platform);
return this.Ok
(

View file

@ -74,14 +74,14 @@ public class MatchController : ControllerBase
#endregion
await LastContactHelper.SetLastContact(user, gameToken.GameVersion);
await LastContactHelper.SetLastContact(user, gameToken.GameVersion, gameToken.Platform);
#region Process match data
if (matchData is UpdateMyPlayerData playerData)
{
MatchHelper.SetUserLocation(user.UserId, gameToken.UserLocation);
Room? room = RoomHelper.FindRoomByUser(user, gameToken.GameVersion, true);
Room? room = RoomHelper.FindRoomByUser(user, gameToken.GameVersion, gameToken.Platform, true);
if (playerData.RoomState != null)
if (room != null && Equals(room.Host, user))
@ -90,7 +90,7 @@ public class MatchController : ControllerBase
if (matchData is FindBestRoom && MatchHelper.UserLocations.Count > 1)
{
FindBestRoomResponse? response = RoomHelper.FindBestRoom(user, gameToken.GameVersion, gameToken.UserLocation);
FindBestRoomResponse? response = RoomHelper.FindBestRoom(user, gameToken.GameVersion, gameToken.Platform, gameToken.UserLocation);
if (response == null) return this.NotFound();
@ -112,7 +112,7 @@ public class MatchController : ControllerBase
}
// Create a new one as requested
RoomHelper.CreateRoom(users, gameToken.GameVersion, createRoom.RoomSlot);
RoomHelper.CreateRoom(users, gameToken.GameVersion, gameToken.Platform, createRoom.RoomSlot);
}
if (matchData is UpdatePlayersInRoom updatePlayersInRoom)

View file

@ -33,10 +33,13 @@ public class PublishController : ControllerBase
[HttpPost("startPublish")]
public async Task<IActionResult> StartPublish()
{
User? user = await this.database.UserFromGameRequest(this.Request);
if (user == null) return this.StatusCode(403, "");
(User, GameToken)? userAndToken = await this.database.UserAndGameTokenFromRequest(this.Request);
if (user.UsedSlots >= ServerSettings.Instance.EntitledSlots) return this.BadRequest();
if (userAndToken == null) return this.StatusCode(403, "");
// ReSharper disable once PossibleInvalidOperationException
User user = userAndToken.Value.Item1;
GameToken gameToken = userAndToken.Value.Item2;
Slot? slot = await this.getSlotFromBody();
if (slot == null) return this.BadRequest(); // if the level cant be parsed then it obviously cant be uploaded
@ -52,6 +55,10 @@ public class PublishController : ControllerBase
if (oldSlot == null) return this.NotFound();
if (oldSlot.CreatorId != user.UserId) return this.BadRequest();
}
else if (user.GetUsedSlotsForGame(gameToken.GameVersion, database) > ServerSettings.Instance.EntitledSlots)
{
return this.StatusCode(403, "");
}
slot.ResourceCollection += "," + slot.IconHash; // tells LBP to upload icon after we process resources here
@ -76,9 +83,6 @@ public class PublishController : ControllerBase
// ReSharper disable once PossibleInvalidOperationException
User user = userAndToken.Value.Item1;
GameToken gameToken = userAndToken.Value.Item2;
if (user.UsedSlots >= ServerSettings.Instance.EntitledSlots) return this.BadRequest();
Slot? slot = await this.getSlotFromBody();
if (slot?.Location == null) return this.BadRequest();
@ -133,6 +137,11 @@ public class PublishController : ControllerBase
return this.Ok(oldSlot.Serialize(gameToken.GameVersion));
}
if (user.GetUsedSlotsForGame(gameToken.GameVersion, database) > ServerSettings.Instance.EntitledSlots)
{
return this.StatusCode(403, "");
}
//TODO: parse location in body
Location l = new()
{

View file

@ -17,7 +17,7 @@ namespace LBPUnion.ProjectLighthouse.Controllers.GameApi.Slots;
[ApiController]
[Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/plain")]
[Produces("text/xml")]
public class ReviewController : ControllerBase
{
private readonly Database database;
@ -141,51 +141,19 @@ public class ReviewController : ControllerBase
GameVersion gameVersion = gameToken.GameVersion;
Random rand = new();
Review? yourReview = await this.database.Reviews.FirstOrDefaultAsync
(r => r.ReviewerId == user.UserId && r.SlotId == slotId && r.Slot.GameVersion <= gameVersion);
VisitedLevel? visitedLevel = await this.database.VisitedLevels.FirstOrDefaultAsync
(v => v.UserId == user.UserId && v.SlotId == slotId && v.Slot.GameVersion <= gameVersion);
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == slotId);
if (slot == null) return this.BadRequest();
bool canNowReviewLevel = slot.CreatorId != user.UserId && visitedLevel != null && yourReview == null;
if (canNowReviewLevel)
{
RatedLevel? ratedLevel = await this.database.RatedLevels.FirstOrDefaultAsync
(r => r.UserId == user.UserId && r.SlotId == slotId && r.Slot.GameVersion <= gameVersion);
yourReview = new Review();
yourReview.ReviewerId = user.UserId;
yourReview.Reviewer = user;
yourReview.Thumb = ratedLevel?.Rating ?? 0;
yourReview.Slot = slot;
yourReview.SlotId = slotId;
yourReview.Deleted = false;
yourReview.DeletedBy = DeletedBy.None;
yourReview.Text = "You haven't reviewed this level yet. Edit this to write one!";
yourReview.LabelCollection = "";
yourReview.Timestamp = TimeHelper.UnixTimeMilliseconds();
}
IQueryable<Review?> reviews = this.database.Reviews.Where(r => r.SlotId == slotId && r.Slot.GameVersion <= gameVersion)
.Include(r => r.Reviewer)
.Include(r => r.Slot)
.OrderByDescending(r => r.ThumbsUp)
.ThenByDescending(_ => EF.Functions.Random())
.ThenByDescending(r => r.Timestamp)
.Skip(pageStart - 1)
.Take(pageSize);
IEnumerable<Review?> prependedReviews;
if (canNowReviewLevel) // this can only be true if you have not posted a review but have visited the level
// prepend the fake review to the top of the list to be easily edited
prependedReviews = reviews.ToList().Prepend(yourReview);
else prependedReviews = reviews.ToList();
string inner = prependedReviews.Aggregate
string inner = reviews.ToList().Aggregate
(
string.Empty,
(current, review) =>

View file

@ -64,7 +64,9 @@ public class ScoreController : ControllerBase
break;
}
IQueryable<Score> existingScore = this.database.Scores.Where(s => s.SlotId == score.SlotId && s.PlayerIdCollection == score.PlayerIdCollection);
IQueryable<Score> existingScore = this.database.Scores.Where(s => s.SlotId == score.SlotId)
.Where(s => s.PlayerIdCollection == score.PlayerIdCollection)
.Where(s => s.Type == score.Type);
if (existingScore.Any())
{
@ -80,7 +82,7 @@ public class ScoreController : ControllerBase
await this.database.SaveChangesAsync();
string myRanking = this.getScores(score.SlotId, score.Type, user);
string myRanking = this.getScores(score.SlotId, score.Type, user, -1, 5, "scoreboardSegment");
return this.Ok(myRanking);
}
@ -103,7 +105,7 @@ public class ScoreController : ControllerBase
}
[SuppressMessage("ReSharper", "PossibleMultipleEnumeration")]
private string getScores(int slotId, int type, User user, int pageStart = -1, int pageSize = 5)
private string getScores(int slotId, int type, User user, int pageStart = -1, int pageSize = 5, string rootName = "scores")
{
// This is hella ugly but it technically assigns the proper rank to a score
// var needed for Anonymous type returned from SELECT
@ -136,11 +138,11 @@ public class ScoreController : ControllerBase
);
string res;
if (myScore == null) res = LbpSerializer.StringElement("scores", serializedScores);
if (myScore == null) res = LbpSerializer.StringElement(rootName, serializedScores);
else
res = LbpSerializer.TaggedStringElement
(
"scores",
rootName,
serializedScores,
new Dictionary<string, object>
{

View file

@ -4,9 +4,11 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Helpers.Extensions;
using LBPUnion.ProjectLighthouse.Serialization;
using LBPUnion.ProjectLighthouse.Types;
using LBPUnion.ProjectLighthouse.Types.Levels;
using LBPUnion.ProjectLighthouse.Types.Reviews;
using LBPUnion.ProjectLighthouse.Types.Settings;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
@ -24,18 +26,6 @@ public class SlotsController : ControllerBase
this.database = database;
}
private IQueryable<Slot> getSlots(GameVersion gameVersion)
{
IQueryable<Slot> query = this.database.Slots.Include(s => s.Creator).Include(s => s.Location);
if (gameVersion == GameVersion.LittleBigPlanetVita || gameVersion == GameVersion.LittleBigPlanetPSP || gameVersion == GameVersion.Unknown)
{
return query.Where(s => s.GameVersion == gameVersion && !s.SubLevel);
}
return query.Where(s => s.GameVersion <= gameVersion && !s.SubLevel);
}
[HttpGet("slots/by")]
public async Task<IActionResult> SlotsBy([FromQuery] string u, [FromQuery] int pageStart, [FromQuery] int pageSize)
{
@ -49,8 +39,7 @@ public class SlotsController : ControllerBase
string response = Enumerable.Aggregate
(
this.getSlots
(gameVersion)
this.database.Slots.ByGameVersion(gameVersion, token.UserId == user.UserId)
.Where(s => s.Creator!.Username == user.Username)
.Skip(pageStart - 1)
.Take(Math.Min(pageSize, ServerSettings.Instance.EntitledSlots)),
@ -88,13 +77,14 @@ public class SlotsController : ControllerBase
GameVersion gameVersion = token.GameVersion;
Slot? slot = await this.getSlots(gameVersion).FirstOrDefaultAsync(s => s.SlotId == id);
Slot? slot = await this.database.Slots.ByGameVersion(gameVersion, true).FirstOrDefaultAsync(s => s.SlotId == id);
if (slot == null) return this.NotFound();
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(gameVersion, ratedLevel, visitedLevel));
Review? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.SlotId == id && r.ReviewerId == user.UserId);
return this.Ok(slot.Serialize(gameVersion, ratedLevel, visitedLevel, review));
}
[HttpGet("slots/cool")]
@ -129,7 +119,11 @@ public class SlotsController : ControllerBase
GameVersion gameVersion = token.GameVersion;
IQueryable<Slot> slots = this.getSlots(gameVersion).OrderByDescending(s => s.FirstUploaded).Skip(pageStart - 1).Take(Math.Min(pageSize, 30));
IQueryable<Slot> slots = this.database.Slots.ByGameVersion
(gameVersion)
.OrderByDescending(s => s.FirstUploaded)
.Skip(pageStart - 1)
.Take(Math.Min(pageSize, 30));
string response = Enumerable.Aggregate(slots, string.Empty, (current, slot) => current + slot.Serialize(gameVersion));
@ -160,7 +154,7 @@ public class SlotsController : ControllerBase
GameVersion gameVersion = token.GameVersion;
IQueryable<Slot> slots = this.getSlots(gameVersion)
IQueryable<Slot> slots = this.database.Slots.ByGameVersion(gameVersion)
.Where(s => s.TeamPick)
.OrderByDescending(s => s.LastUpdated)
.Skip(pageStart - 1)
@ -194,7 +188,7 @@ public class SlotsController : ControllerBase
GameVersion gameVersion = token.GameVersion;
IEnumerable<Slot> slots = this.getSlots(gameVersion).OrderBy(_ => EF.Functions.Random()).Take(Math.Min(pageSize, 30));
IEnumerable<Slot> slots = this.database.Slots.ByGameVersion(gameVersion).OrderBy(_ => EF.Functions.Random()).Take(Math.Min(pageSize, 30));
string response = slots.Aggregate(string.Empty, (current, slot) => current + slot.Serialize(gameVersion));
@ -383,7 +377,7 @@ public class SlotsController : ControllerBase
{
if (version == GameVersion.LittleBigPlanetVita || version == GameVersion.LittleBigPlanetPSP || version == GameVersion.Unknown)
{
return this.getSlots(version);
return this.database.Slots.ByGameVersion(version);
}
string _dateFilterType = dateFilterType ?? "";

View file

@ -62,10 +62,7 @@ public class AdminSlotController : ControllerBase
if (slot.Location == null) throw new ArgumentNullException();
this.database.Locations.Remove(slot.Location);
this.database.Slots.Remove(slot);
await this.database.SaveChangesAsync();
await this.database.RemoveSlot(slot);
return this.Ok();
}

View file

@ -26,7 +26,7 @@ public class RoomVisualizerController : ControllerBase
return this.NotFound();
#else
List<User> users = await this.database.Users.OrderByDescending(_ => EF.Functions.Random()).Take(2).ToListAsync();
RoomHelper.CreateRoom(users, GameVersion.LittleBigPlanet2);
RoomHelper.CreateRoom(users, GameVersion.LittleBigPlanet2, Platform.PS3);
foreach (User user in users)
{

View file

@ -1,5 +1,7 @@
#nullable enable
using System.Threading.Tasks;
using Kettu;
using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.Types;
using LBPUnion.ProjectLighthouse.Types.Levels;
using Microsoft.AspNetCore.Mvc;
@ -34,15 +36,20 @@ public class SlotPageController : ControllerBase
return this.Redirect($"~/slot/{id}#{commentId}");
}
[HttpGet("postComment")]
public async Task<IActionResult> PostComment([FromRoute] int id, [FromQuery] string? msg)
[HttpPost("postComment")]
public async Task<IActionResult> PostComment([FromRoute] int id, [FromForm] string? msg)
{
User? user = this.database.UserFromWebRequest(this.Request);
if (user == null) return this.Redirect("~/login");
if (msg == null) return this.Redirect("~/slot/" + id);
if (msg == null)
{
Logger.Log($"Refusing to post comment from {user.UserId} on user {id}, {nameof(msg)} is null", LoggerLevelComments.Instance);
return this.Redirect("~/slot/" + id);
}
await this.database.PostComment(user, id, CommentType.Level, msg);
Logger.Log($"Posted comment from {user.UserId}: \"{msg}\" on user {id}", LoggerLevelComments.Instance);
return this.Redirect("~/slot/" + id);
}

View file

@ -1,10 +1,9 @@
#nullable enable
using System;
using System.Threading.Tasks;
using Kettu;
using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.Types;
using LBPUnion.ProjectLighthouse.Types.Profiles;
using Microsoft.AspNetCore.Mvc;
using Microsoft.CodeAnalysis;
using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Controllers.Website;
@ -20,20 +19,6 @@ public class UserPageController : ControllerBase
this.database = database;
}
[HttpGet("heart")]
public async Task<IActionResult> HeartUser([FromRoute] int id)
{
User? user = this.database.UserFromWebRequest(this.Request);
if (user == null) return this.Redirect("~/login");
User? heartedUser = await this.database.Users.FirstOrDefaultAsync(u => u.UserId == id);
if (heartedUser == null) return this.NotFound();
await this.database.HeartUser(user, heartedUser);
return this.Redirect("~/user/" + id);
}
[HttpGet("rateComment")]
public async Task<IActionResult> RateComment([FromRoute] int id, [FromQuery] int? commentId, [FromQuery] int? rating)
{
@ -45,15 +30,34 @@ public class UserPageController : ControllerBase
return this.Redirect($"~/user/{id}#{commentId}");
}
[HttpGet("postComment")]
public async Task<IActionResult> PostComment([FromRoute] int id, [FromQuery] string? msg)
[HttpPost("postComment")]
public async Task<IActionResult> PostComment([FromRoute] int id, [FromForm] string? msg)
{
User? user = this.database.UserFromWebRequest(this.Request);
if (user == null) return this.Redirect("~/login");
if (msg == null) return this.Redirect("~/user/" + id);
if (msg == null)
{
Logger.Log($"Refusing to post comment from {user.UserId} on user {id}, {nameof(msg)} is null", LoggerLevelComments.Instance);
return this.Redirect("~/user/" + id);
}
await this.database.PostComment(user, id, CommentType.Profile, msg);
Logger.Log($"Posted comment from {user.UserId}: \"{msg}\" on user {id}", LoggerLevelComments.Instance);
return this.Redirect("~/user/" + id);
}
[HttpGet("heart")]
public async Task<IActionResult> HeartUser([FromRoute] int id)
{
User? user = this.database.UserFromWebRequest(this.Request);
if (user == null) return this.Redirect("~/login");
User? heartedUser = await this.database.Users.FirstOrDefaultAsync(u => u.UserId == id);
if (heartedUser == null) return this.NotFound();
await this.database.HeartUser(user, heartedUser);
return this.Redirect("~/user/" + id);
}

View file

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Types;
@ -46,7 +47,17 @@ public class Database : DbContext
public async Task<User> CreateUser(string username, string password)
{
if (!password.StartsWith("$")) throw new ArgumentException(nameof(password) + " is not a BCrypt hash");
if (!password.StartsWith('$')) throw new ArgumentException(nameof(password) + " is not a BCrypt hash");
// 16 is PSN max, 3 is PSN minimum
if (!ServerStatics.IsUnitTesting || !username.StartsWith("unitTestUser"))
{
if (username.Length > 16 || username.Length < 3) throw new ArgumentException(nameof(username) + " is either too long or too short");
Regex regex = new("^[a-zA-Z0-9_.-]*$");
if (!regex.IsMatch(username)) throw new ArgumentException(nameof(username) + " does not match the username regex");
}
User user;
if ((user = await this.Users.Where(u => u.Username == username).FirstOrDefaultAsync()) != null) return user;
@ -82,6 +93,7 @@ public class Database : DbContext
UserId = user.UserId,
UserLocation = userLocation,
GameVersion = npTicket.GameVersion,
Platform = npTicket.Platform,
};
this.GameTokens.Add(gameToken);
@ -143,6 +155,8 @@ public class Database : DbContext
public async Task<bool> PostComment(User user, int targetId, CommentType type, string message)
{
if (message.Length > 100) return false;
if (type == CommentType.Profile)
{
User? targetUser = await this.Users.FirstOrDefaultAsync(u => u.UserId == targetId);
@ -151,7 +165,7 @@ public class Database : DbContext
else
{
Slot? targetSlot = await this.Slots.FirstOrDefaultAsync(u => u.SlotId == targetId);
if(targetSlot == null) return false;
if (targetSlot == null) return false;
}
this.Comments.Add

View file

@ -0,0 +1,31 @@
using System.Linq;
using LBPUnion.ProjectLighthouse.Types;
using LBPUnion.ProjectLighthouse.Types.Levels;
using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Helpers.Extensions;
public static class SlotsExtensions
{
public static IQueryable<Slot> ByGameVersion
(this DbSet<Slot> set, GameVersion gameVersion, bool includeSublevels = false)
=> set.AsQueryable().ByGameVersion(gameVersion, includeSublevels);
public static IQueryable<Slot> ByGameVersion(this IQueryable<Slot> queryable, GameVersion gameVersion, bool includeSublevels = false)
{
IQueryable<Slot> query = queryable.Include(s => s.Creator).Include(s => s.Location);
if (gameVersion == GameVersion.LittleBigPlanetVita || gameVersion == GameVersion.LittleBigPlanetPSP || gameVersion == GameVersion.Unknown)
{
query = query.Where(s => s.GameVersion == gameVersion);
}
else
{
query = query.Where(s => s.GameVersion <= gameVersion);
}
if (!includeSublevels) query = query.Where(s => !s.SubLevel);
return query;
}
}

View file

@ -1,7 +1,13 @@
#nullable enable
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Kettu;
using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.Types.Files;
using LBPUnion.ProjectLighthouse.Types.Settings;
@ -101,4 +107,44 @@ public static class FileHelper
}
public static string[] ResourcesNotUploaded(params string[] hashes) => hashes.Where(hash => !ResourceExists(hash)).ToArray();
public static void ConvertAllTexturesToPng()
{
EnsureDirectoryCreated(Path.Combine(Environment.CurrentDirectory, "png"));
if (Directory.Exists("r"))
{
Logger.Log
("Converting all textures to PNG. This may take a while if this is the first time running this operation...", LoggerLevelStartup.Instance);
ConcurrentQueue<string> fileQueue = new();
foreach (string filename in Directory.GetFiles("r")) fileQueue.Enqueue(filename);
for(int i = 0; i < Environment.ProcessorCount; i++)
{
Task.Factory.StartNew
(
() =>
{
while (fileQueue.TryDequeue(out string? filename))
{
LbpFile? file = LbpFile.FromHash(filename.Replace("r" + Path.DirectorySeparatorChar, ""));
if (file == null) continue;
if (file.FileType == LbpFileType.Jpeg || file.FileType == LbpFileType.Png || file.FileType == LbpFileType.Texture)
{
ImageHelper.LbpFileToPNG(file);
}
}
}
);
}
while (!fileQueue.IsEmpty)
{
Thread.Sleep(100);
}
}
}
}

View file

@ -11,7 +11,7 @@ public static class LastContactHelper
{
private static readonly Database database = new();
public static async Task SetLastContact(User user, GameVersion gameVersion)
public static async Task SetLastContact(User user, GameVersion gameVersion, Platform platform)
{
LastContact? lastContact = await database.LastContacts.Where(l => l.UserId == user.UserId).FirstOrDefaultAsync();
@ -28,6 +28,7 @@ public static class LastContactHelper
lastContact.Timestamp = TimestampHelper.Timestamp;
lastContact.GameVersion = gameVersion;
lastContact.Platform = platform;
await database.SaveChangesAsync();
}

View file

@ -2,11 +2,13 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading.Tasks;
using Kettu;
using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.Types;
using LBPUnion.ProjectLighthouse.Types.Levels;
using LBPUnion.ProjectLighthouse.Types.Match;
using LBPUnion.ProjectLighthouse.Types.Profiles;
namespace LBPUnion.ProjectLighthouse.Helpers;
@ -22,9 +24,25 @@ public class RoomHelper
private static int roomIdIncrement;
public static void StartCleanupThread()
{
// ReSharper disable once FunctionNeverReturns
Task.Factory.StartNew
(
async () =>
{
while (true)
{
CleanupRooms();
await Task.Delay(10000);
}
}
);
}
internal static int RoomIdIncrement => roomIdIncrement++;
public static FindBestRoomResponse? FindBestRoom(User? user, GameVersion roomVersion, string? location)
public static FindBestRoomResponse? FindBestRoom(User? user, GameVersion roomVersion, Platform? platform, string? location)
{
if (roomVersion == GameVersion.LittleBigPlanet1 || roomVersion == GameVersion.LittleBigPlanetPSP)
{
@ -42,6 +60,7 @@ public class RoomHelper
}
rooms = rooms.Where(r => r.RoomVersion == roomVersion).ToList();
if (platform != null) rooms = rooms.Where(r => r.RoomPlatform == platform).ToList();
foreach (Room room in rooms)
// Look for rooms looking for players before moving on to rooms that are idle.
@ -115,7 +134,7 @@ public class RoomHelper
return null;
}
public static Room CreateRoom(User user, GameVersion roomVersion, RoomSlot? slot = null)
public static Room CreateRoom(User user, GameVersion roomVersion, Platform roomPlatform, RoomSlot? slot = null)
=> CreateRoom
(
new List<User>
@ -123,9 +142,10 @@ public class RoomHelper
user,
},
roomVersion,
roomPlatform,
slot
);
public static Room CreateRoom(List<User> users, GameVersion roomVersion, RoomSlot? slot = null)
public static Room CreateRoom(List<User> users, GameVersion roomVersion, Platform roomPlatform, RoomSlot? slot = null)
{
Room room = new()
{
@ -134,6 +154,7 @@ public class RoomHelper
State = RoomState.Idle,
Slot = slot ?? PodSlot,
RoomVersion = roomVersion,
RoomPlatform = roomPlatform,
};
CleanupRooms(room.Host, room);
@ -143,13 +164,22 @@ public class RoomHelper
return room;
}
public static Room? FindRoomByUser(User user, GameVersion roomVersion, bool createIfDoesNotExist = false)
public static Room? FindRoomByUser(User user, GameVersion roomVersion, Platform roomPlatform, bool createIfDoesNotExist = false)
{
lock(Rooms)
foreach (Room room in Rooms.Where(room => room.Players.Any(player => user == player)))
return room;
return createIfDoesNotExist ? CreateRoom(user, roomVersion) : null;
return createIfDoesNotExist ? CreateRoom(user, roomVersion, roomPlatform) : null;
}
public static Room? FindRoomByUserId(int userId)
{
lock(Rooms)
foreach (Room room in Rooms.Where(room => room.Players.Any(player => player.UserId == userId)))
return room;
return null;
}
[SuppressMessage("ReSharper", "InvertIf")]
@ -157,6 +187,16 @@ public class RoomHelper
{
lock(Rooms)
{
int roomCountBeforeCleanup = Rooms.Count;
// Remove offline players from rooms
foreach (Room room in Rooms)
{
// do not shorten, this prevents collection modified errors
List<User> playersToRemove = room.Players.Where(player => player.Status.StatusType == StatusType.Offline).ToList();
foreach (User user in playersToRemove) room.Players.Remove(user);
}
// Delete old rooms based on host
if (host != null)
try
@ -179,6 +219,13 @@ public class RoomHelper
Rooms.RemoveAll(r => r.Players.Count == 0); // Remove empty rooms
Rooms.RemoveAll(r => r.Players.Count > 4); // Remove obviously bogus rooms
int roomCountAfterCleanup = Rooms.Count;
if (roomCountBeforeCleanup != roomCountAfterCleanup)
{
Logger.Log($"Cleaned up {roomCountBeforeCleanup - roomCountAfterCleanup} rooms.", LoggerLevelMatch.Instance);
}
}
}
}

View file

@ -63,6 +63,12 @@ public class LoggerLevelInflux : LoggerLevel
public override string Name => "Influx";
}
public class LoggerLevelComments : LoggerLevel
{
public static readonly LoggerLevelComments Instance = new();
public override string Name => "Comments";
}
public class LoggerLevelAspNet : LoggerLevel
{

View file

@ -0,0 +1,41 @@
using LBPUnion.ProjectLighthouse;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ProjectLighthouse.Migrations
{
[DbContext(typeof(Database))]
[Migration("20220217045519_AddPlatformForLastContactsAndGameTokens")]
public partial class AddPlatformForLastContactsAndGameTokens : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "Platform",
table: "LastContacts",
type: "int",
nullable: false,
defaultValue: -1);
migrationBuilder.AddColumn<int>(
name: "Platform",
table: "GameTokens",
type: "int",
nullable: false,
defaultValue: -1);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Platform",
table: "LastContacts");
migrationBuilder.DropColumn(
name: "Platform",
table: "GameTokens");
}
}
}

View file

@ -81,6 +81,9 @@ namespace ProjectLighthouse.Migrations
b.Property<int>("GameVersion")
.HasColumnType("int");
b.Property<int>("Platform")
.HasColumnType("int");
b.Property<bool>("Used")
.HasColumnType("tinyint(1)");
@ -458,6 +461,9 @@ namespace ProjectLighthouse.Migrations
b.Property<int>("GameVersion")
.HasColumnType("int");
b.Property<int>("Platform")
.HasColumnType("int");
b.Property<long>("Timestamp")
.HasColumnType("bigint");

View file

@ -52,7 +52,7 @@
{
if (version == GameVersion.LittleBigPlanet1 || version == GameVersion.LittleBigPlanetPSP || version == GameVersion.Unknown) continue;
FindBestRoomResponse? response = RoomHelper.FindBestRoom(null, version, null);
FindBestRoomResponse? response = RoomHelper.FindBestRoom(null, version, null, null);
string text = response == null ? "No room found." : "Room " + response.RoomId;
<p><b>Best room for @version.ToPrettyString()</b>: @text</p>
@ -72,7 +72,7 @@
<b>You are currently in this room.</b>
</p>
}
<p>@room.Players.Count players, state is @room.State, version is @room.RoomVersion.ToPrettyString()</p>
<p>@room.Players.Count players, state is @room.State, version is @room.RoomVersion.ToPrettyString()on paltform @room.RoomPlatform</p>
<p>Slot type: @room.Slot.SlotType, slot id: @room.Slot.SlotId</p>
@foreach (User player in room.Players)
{

View file

@ -1,7 +1,6 @@
@using System.IO
@using System.Web
@using LBPUnion.ProjectLighthouse.Types.Profiles
<div class="ui yellow segment" id="comments">
<style>
.comment {
@ -23,18 +22,35 @@
}
else if (!Model.CommentsEnabled)
{
<b><i>Comments are disabled</i></b>
<b>
<i>Comments are disabled.</i>
</b>
}
@for (int i = 0; i < Model.Comments.Count; i++)
@if (Model.CommentsEnabled && Model.User != null)
{
<div class="ui divider"></div>
<form class="ui reply form" action="@Url.RouteUrl(ViewContext.RouteData.Values)/postComment" method="post">
<div class="field">
<textarea style="min-height: 70px; height: 70px; max-height:120px" name="msg"></textarea>
</div>
<input type="submit" class="ui blue button">
</form>
<br>
}
@for(int i = 0; i < Model.Comments.Count; i++)
{
Comment comment = Model.Comments[i];
DateTimeOffset timestamp = DateTimeOffset.FromUnixTimeSeconds(comment.Timestamp / 1000);
StringWriter messageWriter = new();
HttpUtility.HtmlDecode(comment.getComment(), messageWriter);
string decodedMessage = messageWriter.ToString();
string url = Url.RouteUrl(ViewContext.RouteData.Values);
int rating = comment.ThumbsUp - comment.ThumbsDown;
<div style="display: flex" id="@comment.CommentId">
<div class="voting">
<a href="@url/rateComment?commentId=@(comment.CommentId)&rating=@(comment.YourThumb == 1 ? 0 : 1)">
@ -50,7 +66,9 @@
<b><a href="/user/@comment.PosterUserId">@comment.Poster.Username</a>: </b>
@if (comment.Deleted)
{
<i><span>@decodedMessage</span></i>
<i>
<span>@decodedMessage</span>
</i>
}
else
{
@ -66,13 +84,4 @@
</div>
</div>
}
@if(Model.CommentsEnabled && Model.User != null){
<div class="ui divider"></div>
<form class="ui reply form" action="@Url.RouteUrl(ViewContext.RouteData.Values)/postComment">
<div class="field">
<textarea style="min-height: 70px; height: 70px; max-height:120px" name="msg"></textarea>
</div>
<input type="submit" class="ui blue button">
</form>
}
</div>

View file

@ -38,7 +38,7 @@
<div class="field">
<label>Username</label>
<div class="ui left icon input">
<input type="text" name="username" id="text" placeholder="Username">
<input type="text" name="username" id="text" placeholder="Username" pattern="^[a-zA-Z0-9_.-]*$" minlength="3" maxlength="16">
<i class="user icon"></i>
</div>
</div>

View file

@ -1,14 +1,10 @@
#nullable enable
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Kettu;
using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.Types.Files;
using LBPUnion.ProjectLighthouse.Types.Settings;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
@ -82,41 +78,10 @@ public static class Program
return;
}
FileHelper.EnsureDirectoryCreated(Path.Combine(Environment.CurrentDirectory, "png"));
if (Directory.Exists("r"))
{
Logger.Log
("Converting all textures to PNG. This may take a while if this is the first time running this operation...", LoggerLevelStartup.Instance);
FileHelper.ConvertAllTexturesToPng();
ConcurrentQueue<string> fileQueue = new();
foreach (string filename in Directory.GetFiles("r")) fileQueue.Enqueue(filename);
for(int i = 0; i < Environment.ProcessorCount; i++)
{
Task.Factory.StartNew
(
() =>
{
while (fileQueue.TryDequeue(out string? filename))
{
LbpFile? file = LbpFile.FromHash(filename.Replace("r" + Path.DirectorySeparatorChar, ""));
if (file == null) continue;
if (file.FileType == LbpFileType.Jpeg || file.FileType == LbpFileType.Png || file.FileType == LbpFileType.Texture)
{
ImageHelper.LbpFileToPNG(file);
}
}
}
);
}
while (!fileQueue.IsEmpty)
{
Thread.Sleep(100);
}
}
Logger.Log("Starting room cleanup thread...", LoggerLevelStartup.Instance);
RoomHelper.StartCleanupThread();
stopwatch.Stop();
Logger.Log($"Ready! Startup took {stopwatch.ElapsedMilliseconds}ms. Passing off control to ASP.NET...", LoggerLevelStartup.Instance);

View file

@ -250,7 +250,8 @@ public class Startup
if (gameToken != null && gameToken.GameVersion == GameVersion.LittleBigPlanet1)
// Ignore UserFromGameToken null because user must exist for a token to exist
await LastContactHelper.SetLastContact((await database.UserFromGameToken(gameToken))!, GameVersion.LittleBigPlanet1);
await LastContactHelper.SetLastContact
((await database.UserFromGameToken(gameToken))!, GameVersion.LittleBigPlanet1, gameToken.Platform);
}
#nullable disable

View file

@ -3,7 +3,7 @@ using Microsoft.AspNetCore.Mvc;
namespace LBPUnion.ProjectLighthouse.Types;
[ApiController]
[Route("/api/v1/")]
[Route("/api/v1")]
[Produces("application/json")]
public class ApiEndpointController : ControllerBase
{}

View file

@ -1,6 +1,7 @@
#nullable enable
using System.Collections.Generic;
using System.Linq;
using LBPUnion.ProjectLighthouse.Helpers.Extensions;
using LBPUnion.ProjectLighthouse.Types.Levels;
namespace LBPUnion.ProjectLighthouse.Types.Categories;
@ -34,6 +35,8 @@ public class CustomCategory : Category
public sealed override string IconHash { get; set; }
public sealed override string Endpoint { get; set; }
public override Slot? GetPreviewSlot(Database database) => database.Slots.FirstOrDefault(s => s.SlotId == this.SlotIds[0]);
public override IEnumerable<Slot> GetSlots(Database database, int pageStart, int pageSize) => database.Slots.Where(s => this.SlotIds.Contains(s.SlotId));
public override IEnumerable<Slot> GetSlots
(Database database, int pageStart, int pageSize)
=> database.Slots.ByGameVersion(GameVersion.LittleBigPlanet3).Where(s => this.SlotIds.Contains(s.SlotId));
public override int GetTotalSlots(Database database) => this.SlotIds.Count;
}

View file

@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using LBPUnion.ProjectLighthouse.Helpers.Extensions;
using LBPUnion.ProjectLighthouse.Types.Levels;
using Microsoft.EntityFrameworkCore;
@ -16,5 +17,11 @@ public class HeartedCategory : CategoryWithUser
public override Slot? GetPreviewSlot(Database database, User user) => database.HeartedLevels.FirstOrDefault(h => h.UserId == user.UserId)?.Slot;
public override int GetTotalSlots(Database database, User user) => database.HeartedLevels.Count(h => h.UserId == user.UserId);
public override IEnumerable<Slot> GetSlots(Database database, User user, int pageStart, int pageSize)
=> database.HeartedLevels.Where(h => h.UserId == user.UserId).Include(h => h.Slot).Select(h => h.Slot).Skip(pageStart).Take(Math.Min(pageSize, 20));
=> database.HeartedLevels.Where
(h => h.UserId == user.UserId)
.Include(h => h.Slot)
.Select(h => h.Slot)
.ByGameVersion(GameVersion.LittleBigPlanet3)
.Skip(pageStart)
.Take(Math.Min(pageSize, 20));
}

View file

@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using LBPUnion.ProjectLighthouse.Helpers.Extensions;
using LBPUnion.ProjectLighthouse.Types.Levels;
namespace LBPUnion.ProjectLighthouse.Types.Categories;
@ -15,6 +16,6 @@ public class NewestLevelsCategory : Category
public override Slot? GetPreviewSlot(Database database) => database.Slots.OrderByDescending(s => s.FirstUploaded).FirstOrDefault();
public override IEnumerable<Slot> GetSlots
(Database database, int pageStart, int pageSize)
=> database.Slots.OrderByDescending(s => s.FirstUploaded).Skip(pageStart - 1).Take(Math.Min(pageSize, 20));
=> database.Slots.ByGameVersion(GameVersion.LittleBigPlanet3).OrderByDescending(s => s.FirstUploaded).Skip(pageStart - 1).Take(Math.Min(pageSize, 20));
public override int GetTotalSlots(Database database) => database.Slots.Count();
}

View file

@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using LBPUnion.ProjectLighthouse.Helpers.Extensions;
using LBPUnion.ProjectLighthouse.Types.Levels;
using Microsoft.EntityFrameworkCore;
@ -18,7 +19,13 @@ public class QueueCategory : CategoryWithUser
public override Slot? GetPreviewSlot(Database database, User user)
=> database.QueuedLevels.Include(q => q.Slot).FirstOrDefault(q => q.UserId == user.UserId)?.Slot;
public override IEnumerable<Slot> GetSlots(Database database, User user, int pageStart, int pageSize)
=> database.QueuedLevels.Include(q => q.Slot).Include(q => q.Slot.Location).Select(q => q.Slot).Skip(pageStart - 1).Take(Math.Min(pageSize, 20));
=> database.QueuedLevels.Include
(q => q.Slot)
.Include(q => q.Slot.Location)
.Select(q => q.Slot)
.ByGameVersion(GameVersion.LittleBigPlanet3)
.Skip(pageStart - 1)
.Take(Math.Min(pageSize, 20));
public override int GetTotalSlots(Database database, User user) => database.QueuedLevels.Count(q => q.UserId == user.UserId);
}

View file

@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using LBPUnion.ProjectLighthouse.Helpers.Extensions;
using LBPUnion.ProjectLighthouse.Types.Levels;
namespace LBPUnion.ProjectLighthouse.Types.Categories;
@ -15,6 +16,11 @@ public class TeamPicksCategory : Category
public override Slot? GetPreviewSlot(Database database) => database.Slots.OrderByDescending(s => s.FirstUploaded).FirstOrDefault(s => s.TeamPick);
public override IEnumerable<Slot> GetSlots
(Database database, int pageStart, int pageSize)
=> database.Slots.OrderByDescending(s => s.FirstUploaded).Where(s => s.TeamPick).Skip(pageStart - 1).Take(Math.Min(pageSize, 20));
=> database.Slots.ByGameVersion
(GameVersion.LittleBigPlanet3)
.OrderByDescending(s => s.FirstUploaded)
.Where(s => s.TeamPick)
.Skip(pageStart - 1)
.Take(Math.Min(pageSize, 20));
public override int GetTotalSlots(Database database) => database.Slots.Count(s => s.TeamPick);
}

View file

@ -20,6 +20,8 @@ public class GameToken
public GameVersion GameVersion { get; set; }
public Platform Platform { get; set; }
// Set by /authentication webpage
public bool Approved { get; set; }

View file

@ -299,29 +299,41 @@ public class Slot
LbpSerializer.StringElement("leveltype", this.LevelType) +
LbpSerializer.StringElement("yourRating", yourRatingStats?.RatingLBP1) +
LbpSerializer.StringElement("yourDPadRating", yourRatingStats?.Rating) +
LbpSerializer.StringElement("yourLBP1PlayCount", yourVisitedStats?.PlaysLBP1) +
LbpSerializer.StringElement("yourLBP2PlayCount", yourVisitedStats?.PlaysLBP2) +
LbpSerializer.StringElement("yourLBP3PlayCount", yourVisitedStats?.PlaysLBP3) +
LbpSerializer.StringElement("yourlbpPlayCount", yourVisitedStats?.PlaysLBP1) +
LbpSerializer.StringElement("yourlbp3PlayCount", yourVisitedStats?.PlaysLBP3) +
yourReview?.Serialize("yourReview") +
LbpSerializer.StringElement("reviewsEnabled", ServerSettings.Instance.LevelReviewsEnabled) +
LbpSerializer.StringElement("commentsEnabled", ServerSettings.Instance.LevelCommentsEnabled) +
LbpSerializer.StringElement("reviewCount", this.ReviewCount);
int yourPlays;
int plays;
int playsComplete;
int playsUnique;
if (gameVersion == GameVersion.LittleBigPlanetVita)
{
slotData += LbpSerializer.StringElement("yourLBP2PlayCount", yourVisitedStats?.PlaysLBPVita) +
LbpSerializer.StringElement("lbp2PlayCount", this.PlaysLBPVita) +
LbpSerializer.StringElement("lbp2CompletionCount", this.PlaysLBPVitaComplete) +
LbpSerializer.StringElement("lbp2UniquePlayCount", this.PlaysLBPVitaUnique);
yourPlays = yourVisitedStats?.PlaysLBPVita ?? 0;
plays = this.PlaysLBPVita;
playsComplete = this.PlaysLBPVitaComplete;
playsUnique = this.PlaysLBPVitaUnique;
}
else
{
slotData += LbpSerializer.StringElement("yourLBP2PlayCount", yourVisitedStats?.PlaysLBPVita) +
LbpSerializer.StringElement("lbp2PlayCount", this.PlaysLBP2) +
LbpSerializer.StringElement("lbp2CompletionCount", this.PlaysLBP2Complete) +
LbpSerializer.StringElement("lbp2UniquePlayCount", this.PlaysLBP2Unique); // not actually used ingame, as per above comment
yourPlays = yourVisitedStats?.PlaysLBP2 ?? 0;
plays = this.PlaysLBP2;
playsComplete = this.PlaysLBP2Complete;
playsUnique = this.PlaysLBP2Unique;
}
slotData += LbpSerializer.StringElement("yourlbp2PlayCount", yourPlays) +
LbpSerializer.StringElement("lbp2PlayCount", plays) +
LbpSerializer.StringElement("playCount", plays) +
LbpSerializer.StringElement("lbp2CompletionCount", playsComplete) +
LbpSerializer.StringElement("completionCount", playsComplete) +
LbpSerializer.StringElement("lbp2UniquePlayCount", playsUnique) + // not actually used ingame, as per above comment
LbpSerializer.StringElement("uniquePlayCount", playsUnique);
return LbpSerializer.TaggedStringElement("slot", slotData, "type", "user");
}
}

View file

@ -1,23 +1,37 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization;
using LBPUnion.ProjectLighthouse.Types.Levels;
namespace LBPUnion.ProjectLighthouse.Types.Match;
public class Room
{
public List<User> Players;
public int RoomId;
[JsonIgnore]
public List<User> Players { get; set; }
public GameVersion RoomVersion;
public RoomSlot Slot;
public RoomState State;
public int RoomId { get; set; }
[JsonIgnore]
public GameVersion RoomVersion { get; set; }
[JsonIgnore]
public Platform RoomPlatform { get; set; }
public RoomSlot Slot { get; set; }
public RoomState State { get; set; }
[JsonIgnore]
public bool IsInPod => this.Slot.SlotType == SlotType.Pod;
[JsonIgnore]
public bool IsLookingForPlayers => this.State == RoomState.PlayingLevel || this.State == RoomState.DivingInWaiting;
[JsonIgnore]
public User Host => this.Players[0];
public int PlayerCount => this.Players.Count;
#nullable enable
public override bool Equals(object? obj)
{

View file

@ -4,6 +4,6 @@ namespace LBPUnion.ProjectLighthouse.Types.Match;
public class RoomSlot
{
public int SlotId;
public SlotType SlotType;
public int SlotId { get; set; }
public SlotType SlotType { get; set; }
}

View file

@ -10,4 +10,6 @@ public class LastContact
public long Timestamp { get; set; }
public GameVersion GameVersion { get; set; } = GameVersion.Unknown;
public Platform Platform { get; set; } = Platform.Unknown;
}

View file

@ -0,0 +1,7 @@
namespace LBPUnion.ProjectLighthouse.Types.Profiles;
public enum StatusType
{
Offline = 0,
Online = 1,
}

View file

@ -0,0 +1,48 @@
#nullable enable
using System.Linq;
using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Types.Match;
namespace LBPUnion.ProjectLighthouse.Types.Profiles;
public class UserStatus
{
public StatusType StatusType { get; set; }
public GameVersion? CurrentVersion { get; set; }
public Platform? CurrentPlatform { get; set; }
public Room? CurrentRoom { get; set; }
public UserStatus()
{}
public UserStatus(Database database, int userId)
{
LastContact? lastContact = database.LastContacts.Where(l => l.UserId == userId).FirstOrDefault(l => TimestampHelper.Timestamp - l.Timestamp < 300);
if (lastContact == null)
{
StatusType = StatusType.Offline;
CurrentVersion = null;
}
else
{
StatusType = StatusType.Online;
CurrentVersion = lastContact.GameVersion;
CurrentPlatform = lastContact.Platform;
}
CurrentRoom = RoomHelper.FindRoomByUserId(userId);
}
public override string ToString()
{
CurrentVersion ??= GameVersion.Unknown;
CurrentPlatform ??= Platform.Unknown;
return this.StatusType switch
{
StatusType.Online => $"Currently online on {((GameVersion)this.CurrentVersion).ToPrettyString()} on {((Platform)this.CurrentPlatform)}",
StatusType.Offline => "Offline",
_ => "Unknown",
};
}
}

View file

@ -2,7 +2,6 @@ using System.ComponentModel.DataAnnotations.Schema;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text.Json.Serialization;
using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Serialization;
using LBPUnion.ProjectLighthouse.Types.Profiles;
using LBPUnion.ProjectLighthouse.Types.Settings;
@ -153,16 +152,10 @@ public class User
#nullable enable
[NotMapped]
[JsonIgnore]
public string Status {
public UserStatus Status {
get {
using Database database = new();
LastContact? lastMatch = database.LastContacts.Where
(l => l.UserId == this.UserId)
.FirstOrDefault(l => TimestampHelper.Timestamp - l.Timestamp < 300);
if (lastMatch == null) return "Offline";
return "Currently online on " + lastMatch.GameVersion.ToPrettyString();
return new UserStatus(database, this.UserId);
}
}
#nullable disable
@ -208,9 +201,9 @@ public class User
"planets",
gameVersion switch
{
GameVersion.LittleBigPlanet2 => PlanetHashLBP2,
GameVersion.LittleBigPlanet3 => PlanetHashLBP3,
GameVersion.LittleBigPlanetVita => PlanetHashLBPVita,
GameVersion.LittleBigPlanet2 => this.PlanetHashLBP2,
GameVersion.LittleBigPlanet3 => this.PlanetHashLBP3,
GameVersion.LittleBigPlanetVita => this.PlanetHashLBPVita,
_ => "", // other versions do not have custom planets
}
);
@ -230,11 +223,14 @@ public class User
}
}
public int GetUsedSlotsForGame(GameVersion version)
#nullable enable
public int GetUsedSlotsForGame(GameVersion version, Database? database = null)
{
using Database database = new();
database ??= new Database();
return database.Slots.Count(s => s.CreatorId == this.UserId && s.GameVersion == version);
}
#nullable disable
/// <summary>
/// The number of slots remaining on the earth