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"> <project version="4">
<component name="DiscordProjectSettings"> <component name="DiscordProjectSettings">
<option name="show" value="PROJECT_FILES" /> <option name="show" value="PROJECT_FILES" />
<option name="nameOverrideEnabled" value="true" />
<option name="nameOverrideText" value="Project Lighthouse" />
<option name="description" value="" /> <option name="description" value="" />
</component> </component>
</project> </project>

View file

@ -16,8 +16,8 @@ public class DatabaseTests : LighthouseServerTest
await using Database database = new(); await using Database database = new();
int rand = new Random().Next(); int rand = new Random().Next();
User userA = await database.CreateUser("createUserTwiceTest" + rand, HashHelper.GenerateAuthToken()); User userA = await database.CreateUser("unitTestUser" + rand, HashHelper.GenerateAuthToken());
User userB = await database.CreateUser("createUserTwiceTest" + rand, HashHelper.GenerateAuthToken()); User userB = await database.CreateUser("unitTestUser" + rand, HashHelper.GenerateAuthToken());
Assert.NotNull(userA); Assert.NotNull(userA);
Assert.NotNull(userB); 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/=PCSA/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=PCSD/@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/=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/=Swingy/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=thumbsup/@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> <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/=Unheart/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Unpublish/@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/=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> <s:Boolean x:Key="/Default/UserDictionary/Words/=yourthumb/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View file

@ -1,10 +1,13 @@
#nullable enable #nullable enable
using System.Threading.Tasks; using System.Threading.Tasks;
using LBPUnion.ProjectLighthouse.Types; using LBPUnion.ProjectLighthouse.Types;
using LBPUnion.ProjectLighthouse.Types.Profiles;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
// ReSharper disable RouteTemplates.ActionRoutePrefixCanBeExtractedToControllerRoute
namespace LBPUnion.ProjectLighthouse.Controllers.Api; namespace LBPUnion.ProjectLighthouse.Controllers.Api;
/// <summary> /// <summary>
@ -36,4 +39,21 @@ public class UserEndpoints : ApiEndpointController
return this.Ok(user); 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.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Xml.Serialization; using System.Xml.Serialization;
using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Serialization; using LBPUnion.ProjectLighthouse.Serialization;
using LBPUnion.ProjectLighthouse.Types; using LBPUnion.ProjectLighthouse.Types;
using LBPUnion.ProjectLighthouse.Types.Levels; using LBPUnion.ProjectLighthouse.Types.Levels;
@ -39,14 +38,13 @@ public class CommentController : ControllerBase
return this.Ok(); return this.Ok();
} }
[HttpGet("comments/user/{slotId:int}")] [HttpGet("comments/user/{slotId:int}")]
[HttpGet("userComments/{username}")] [HttpGet("userComments/{username}")]
public async Task<IActionResult> GetComments([FromQuery] int pageStart, [FromQuery] int pageSize, string? username, int? slotId) public async Task<IActionResult> GetComments([FromQuery] int pageStart, [FromQuery] int pageSize, string? username, int? slotId)
{ {
User? user = await this.database.UserFromGameRequest(this.Request); User? user = await this.database.UserFromGameRequest(this.Request);
if (user == null) return this.StatusCode(403, ""); if (user == null) return this.StatusCode(403, "");
int targetId = slotId.GetValueOrDefault(); int targetId = slotId.GetValueOrDefault();
CommentType type = CommentType.Level; CommentType type = CommentType.Level;
if (!string.IsNullOrWhiteSpace(username)) if (!string.IsNullOrWhiteSpace(username))
@ -55,24 +53,24 @@ public class CommentController : ControllerBase
type = CommentType.Profile; type = CommentType.Profile;
} }
List<Comment> comments = await this.database.Comments List<Comment> comments = await this.database.Comments.Include
.Include(c => c.Poster) (c => c.Poster)
.Where(c => c.TargetId == targetId && c.Type == type) .Where(c => c.TargetId == targetId && c.Type == type)
.OrderByDescending(c => c.Timestamp) .OrderByDescending(c => c.Timestamp)
.Skip(pageStart - 1) .Skip(pageStart - 1)
.Take(Math.Min(pageSize, .Take(Math.Min(pageSize, 30))
30))
.ToListAsync(); .ToListAsync();
string outputXml = comments.Aggregate(string.Empty, (current, comment) => current + string outputXml = comments.Aggregate
comment.Serialize(this.getReaction(user.UserId, comment.CommentId).Result)); (string.Empty, (current, comment) => current + comment.Serialize(this.getReaction(user.UserId, comment.CommentId).Result));
return this.Ok(LbpSerializer.StringElement("comments", outputXml)); 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); Reaction? reaction = await this.database.Reactions.FirstOrDefaultAsync(r => r.UserId == userId && r.TargetId == commentId);
if (reaction == null) return 0; if (reaction == null) return 0;
return reaction.Rating; return reaction.Rating;
} }
@ -80,11 +78,11 @@ public class CommentController : ControllerBase
[HttpPost("postComment/user/{slotId:int}")] [HttpPost("postComment/user/{slotId:int}")]
public async Task<IActionResult> PostComment(string? username, int? slotId) 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(); string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync();
XmlSerializer serializer = new(typeof(Comment)); 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); 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); Comment? comment = await this.database.Comments.FirstOrDefaultAsync(c => c.CommentId == commentId);
if (comment == null) return this.NotFound(); if (comment == null) return this.NotFound();
// if you are not the poster // if you are not the poster
if (comment.PosterUserId != user.UserId) if (comment.PosterUserId != user.UserId)
{ {

View file

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

View file

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

View file

@ -33,10 +33,13 @@ public class PublishController : ControllerBase
[HttpPost("startPublish")] [HttpPost("startPublish")]
public async Task<IActionResult> StartPublish() public async Task<IActionResult> StartPublish()
{ {
User? user = await this.database.UserFromGameRequest(this.Request); (User, GameToken)? userAndToken = await this.database.UserAndGameTokenFromRequest(this.Request);
if (user == null) return this.StatusCode(403, "");
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(); Slot? slot = await this.getSlotFromBody();
if (slot == null) return this.BadRequest(); // if the level cant be parsed then it obviously cant be uploaded 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 == null) return this.NotFound();
if (oldSlot.CreatorId != user.UserId) return this.BadRequest(); 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 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 // ReSharper disable once PossibleInvalidOperationException
User user = userAndToken.Value.Item1; User user = userAndToken.Value.Item1;
GameToken gameToken = userAndToken.Value.Item2; GameToken gameToken = userAndToken.Value.Item2;
if (user.UsedSlots >= ServerSettings.Instance.EntitledSlots) return this.BadRequest();
Slot? slot = await this.getSlotFromBody(); Slot? slot = await this.getSlotFromBody();
if (slot?.Location == null) return this.BadRequest(); if (slot?.Location == null) return this.BadRequest();
@ -133,6 +137,11 @@ public class PublishController : ControllerBase
return this.Ok(oldSlot.Serialize(gameToken.GameVersion)); 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 //TODO: parse location in body
Location l = new() Location l = new()
{ {

View file

@ -17,7 +17,7 @@ namespace LBPUnion.ProjectLighthouse.Controllers.GameApi.Slots;
[ApiController] [ApiController]
[Route("LITTLEBIGPLANETPS3_XML/")] [Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/plain")] [Produces("text/xml")]
public class ReviewController : ControllerBase public class ReviewController : ControllerBase
{ {
private readonly Database database; private readonly Database database;
@ -141,51 +141,19 @@ public class ReviewController : ControllerBase
GameVersion gameVersion = gameToken.GameVersion; 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); Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == slotId);
if (slot == null) return this.BadRequest(); 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) IQueryable<Review?> reviews = this.database.Reviews.Where(r => r.SlotId == slotId && r.Slot.GameVersion <= gameVersion)
.Include(r => r.Reviewer) .Include(r => r.Reviewer)
.Include(r => r.Slot) .Include(r => r.Slot)
.OrderByDescending(r => r.ThumbsUp) .OrderByDescending(r => r.ThumbsUp)
.ThenByDescending(_ => EF.Functions.Random()) .ThenByDescending(r => r.Timestamp)
.Skip(pageStart - 1) .Skip(pageStart - 1)
.Take(pageSize); .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, string.Empty,
(current, review) => (current, review) =>

View file

@ -64,7 +64,9 @@ public class ScoreController : ControllerBase
break; 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()) if (existingScore.Any())
{ {
@ -80,7 +82,7 @@ public class ScoreController : ControllerBase
await this.database.SaveChangesAsync(); 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); return this.Ok(myRanking);
} }
@ -103,7 +105,7 @@ public class ScoreController : ControllerBase
} }
[SuppressMessage("ReSharper", "PossibleMultipleEnumeration")] [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 // This is hella ugly but it technically assigns the proper rank to a score
// var needed for Anonymous type returned from SELECT // var needed for Anonymous type returned from SELECT
@ -136,11 +138,11 @@ public class ScoreController : ControllerBase
); );
string res; string res;
if (myScore == null) res = LbpSerializer.StringElement("scores", serializedScores); if (myScore == null) res = LbpSerializer.StringElement(rootName, serializedScores);
else else
res = LbpSerializer.TaggedStringElement res = LbpSerializer.TaggedStringElement
( (
"scores", rootName,
serializedScores, serializedScores,
new Dictionary<string, object> new Dictionary<string, object>
{ {

View file

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

View file

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

View file

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

View file

@ -1,5 +1,7 @@
#nullable enable #nullable enable
using System.Threading.Tasks; using System.Threading.Tasks;
using Kettu;
using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.Types; using LBPUnion.ProjectLighthouse.Types;
using LBPUnion.ProjectLighthouse.Types.Levels; using LBPUnion.ProjectLighthouse.Types.Levels;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@ -34,15 +36,20 @@ public class SlotPageController : ControllerBase
return this.Redirect($"~/slot/{id}#{commentId}"); return this.Redirect($"~/slot/{id}#{commentId}");
} }
[HttpGet("postComment")] [HttpPost("postComment")]
public async Task<IActionResult> PostComment([FromRoute] int id, [FromQuery] string? msg) public async Task<IActionResult> PostComment([FromRoute] int id, [FromForm] string? msg)
{ {
User? user = this.database.UserFromWebRequest(this.Request); User? user = this.database.UserFromWebRequest(this.Request);
if (user == null) return this.Redirect("~/login"); 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); 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); return this.Redirect("~/slot/" + id);
} }

View file

@ -1,10 +1,9 @@
#nullable enable #nullable enable
using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Kettu;
using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.Types; using LBPUnion.ProjectLighthouse.Types;
using LBPUnion.ProjectLighthouse.Types.Profiles;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.CodeAnalysis;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Controllers.Website; namespace LBPUnion.ProjectLighthouse.Controllers.Website;
@ -20,20 +19,6 @@ public class UserPageController : ControllerBase
this.database = database; 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")] [HttpGet("rateComment")]
public async Task<IActionResult> RateComment([FromRoute] int id, [FromQuery] int? commentId, [FromQuery] int? rating) 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}"); return this.Redirect($"~/user/{id}#{commentId}");
} }
[HttpGet("postComment")] [HttpPost("postComment")]
public async Task<IActionResult> PostComment([FromRoute] int id, [FromQuery] string? msg) public async Task<IActionResult> PostComment([FromRoute] int id, [FromForm] string? msg)
{ {
User? user = this.database.UserFromWebRequest(this.Request); User? user = this.database.UserFromWebRequest(this.Request);
if (user == null) return this.Redirect("~/login"); 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); 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); return this.Redirect("~/user/" + id);
} }

View file

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Types; using LBPUnion.ProjectLighthouse.Types;
@ -46,7 +47,17 @@ public class Database : DbContext
public async Task<User> CreateUser(string username, string password) 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; User user;
if ((user = await this.Users.Where(u => u.Username == username).FirstOrDefaultAsync()) != null) return 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, UserId = user.UserId,
UserLocation = userLocation, UserLocation = userLocation,
GameVersion = npTicket.GameVersion, GameVersion = npTicket.GameVersion,
Platform = npTicket.Platform,
}; };
this.GameTokens.Add(gameToken); 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) public async Task<bool> PostComment(User user, int targetId, CommentType type, string message)
{ {
if (message.Length > 100) return false;
if (type == CommentType.Profile) if (type == CommentType.Profile)
{ {
User? targetUser = await this.Users.FirstOrDefaultAsync(u => u.UserId == targetId); User? targetUser = await this.Users.FirstOrDefaultAsync(u => u.UserId == targetId);
@ -151,7 +165,7 @@ public class Database : DbContext
else else
{ {
Slot? targetSlot = await this.Slots.FirstOrDefaultAsync(u => u.SlotId == targetId); Slot? targetSlot = await this.Slots.FirstOrDefaultAsync(u => u.SlotId == targetId);
if(targetSlot == null) return false; if (targetSlot == null) return false;
} }
this.Comments.Add 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;
using System.Collections.Concurrent;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text; 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.Files;
using LBPUnion.ProjectLighthouse.Types.Settings; 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 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(); 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(); 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.Timestamp = TimestampHelper.Timestamp;
lastContact.GameVersion = gameVersion; lastContact.GameVersion = gameVersion;
lastContact.Platform = platform;
await database.SaveChangesAsync(); await database.SaveChangesAsync();
} }

View file

@ -2,11 +2,13 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using Kettu; using Kettu;
using LBPUnion.ProjectLighthouse.Logging; using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.Types; using LBPUnion.ProjectLighthouse.Types;
using LBPUnion.ProjectLighthouse.Types.Levels; using LBPUnion.ProjectLighthouse.Types.Levels;
using LBPUnion.ProjectLighthouse.Types.Match; using LBPUnion.ProjectLighthouse.Types.Match;
using LBPUnion.ProjectLighthouse.Types.Profiles;
namespace LBPUnion.ProjectLighthouse.Helpers; namespace LBPUnion.ProjectLighthouse.Helpers;
@ -22,9 +24,25 @@ public class RoomHelper
private static int roomIdIncrement; 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++; 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) if (roomVersion == GameVersion.LittleBigPlanet1 || roomVersion == GameVersion.LittleBigPlanetPSP)
{ {
@ -42,6 +60,7 @@ public class RoomHelper
} }
rooms = rooms.Where(r => r.RoomVersion == roomVersion).ToList(); rooms = rooms.Where(r => r.RoomVersion == roomVersion).ToList();
if (platform != null) rooms = rooms.Where(r => r.RoomPlatform == platform).ToList();
foreach (Room room in rooms) foreach (Room room in rooms)
// Look for rooms looking for players before moving on to rooms that are idle. // Look for rooms looking for players before moving on to rooms that are idle.
@ -115,7 +134,7 @@ public class RoomHelper
return null; 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 => CreateRoom
( (
new List<User> new List<User>
@ -123,9 +142,10 @@ public class RoomHelper
user, user,
}, },
roomVersion, roomVersion,
roomPlatform,
slot 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() Room room = new()
{ {
@ -134,6 +154,7 @@ public class RoomHelper
State = RoomState.Idle, State = RoomState.Idle,
Slot = slot ?? PodSlot, Slot = slot ?? PodSlot,
RoomVersion = roomVersion, RoomVersion = roomVersion,
RoomPlatform = roomPlatform,
}; };
CleanupRooms(room.Host, room); CleanupRooms(room.Host, room);
@ -143,13 +164,22 @@ public class RoomHelper
return room; 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) lock(Rooms)
foreach (Room room in Rooms.Where(room => room.Players.Any(player => user == player))) foreach (Room room in Rooms.Where(room => room.Players.Any(player => user == player)))
return room; 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")] [SuppressMessage("ReSharper", "InvertIf")]
@ -157,6 +187,16 @@ public class RoomHelper
{ {
lock(Rooms) 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 // Delete old rooms based on host
if (host != null) if (host != null)
try try
@ -179,6 +219,13 @@ public class RoomHelper
Rooms.RemoveAll(r => r.Players.Count == 0); // Remove empty rooms Rooms.RemoveAll(r => r.Players.Count == 0); // Remove empty rooms
Rooms.RemoveAll(r => r.Players.Count > 4); // Remove obviously bogus 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 override string Name => "Influx";
} }
public class LoggerLevelComments : LoggerLevel
{
public static readonly LoggerLevelComments Instance = new();
public override string Name => "Comments";
}
public class LoggerLevelAspNet : LoggerLevel 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") b.Property<int>("GameVersion")
.HasColumnType("int"); .HasColumnType("int");
b.Property<int>("Platform")
.HasColumnType("int");
b.Property<bool>("Used") b.Property<bool>("Used")
.HasColumnType("tinyint(1)"); .HasColumnType("tinyint(1)");
@ -458,6 +461,9 @@ namespace ProjectLighthouse.Migrations
b.Property<int>("GameVersion") b.Property<int>("GameVersion")
.HasColumnType("int"); .HasColumnType("int");
b.Property<int>("Platform")
.HasColumnType("int");
b.Property<long>("Timestamp") b.Property<long>("Timestamp")
.HasColumnType("bigint"); .HasColumnType("bigint");

View file

@ -52,7 +52,7 @@
{ {
if (version == GameVersion.LittleBigPlanet1 || version == GameVersion.LittleBigPlanetPSP || version == GameVersion.Unknown) continue; 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; string text = response == null ? "No room found." : "Room " + response.RoomId;
<p><b>Best room for @version.ToPrettyString()</b>: @text</p> <p><b>Best room for @version.ToPrettyString()</b>: @text</p>
@ -72,7 +72,7 @@
<b>You are currently in this room.</b> <b>You are currently in this room.</b>
</p> </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> <p>Slot type: @room.Slot.SlotType, slot id: @room.Slot.SlotId</p>
@foreach (User player in room.Players) @foreach (User player in room.Players)
{ {

View file

@ -1,7 +1,6 @@
@using System.IO @using System.IO
@using System.Web @using System.Web
@using LBPUnion.ProjectLighthouse.Types.Profiles @using LBPUnion.ProjectLighthouse.Types.Profiles
<div class="ui yellow segment" id="comments"> <div class="ui yellow segment" id="comments">
<style> <style>
.comment { .comment {
@ -23,18 +22,35 @@
} }
else if (!Model.CommentsEnabled) 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]; Comment comment = Model.Comments[i];
DateTimeOffset timestamp = DateTimeOffset.FromUnixTimeSeconds(comment.Timestamp / 1000); DateTimeOffset timestamp = DateTimeOffset.FromUnixTimeSeconds(comment.Timestamp / 1000);
StringWriter messageWriter = new(); StringWriter messageWriter = new();
HttpUtility.HtmlDecode(comment.getComment(), messageWriter); HttpUtility.HtmlDecode(comment.getComment(), messageWriter);
string decodedMessage = messageWriter.ToString(); string decodedMessage = messageWriter.ToString();
string url = Url.RouteUrl(ViewContext.RouteData.Values); string url = Url.RouteUrl(ViewContext.RouteData.Values);
int rating = comment.ThumbsUp - comment.ThumbsDown; int rating = comment.ThumbsUp - comment.ThumbsDown;
<div style="display: flex" id="@comment.CommentId"> <div style="display: flex" id="@comment.CommentId">
<div class="voting"> <div class="voting">
<a href="@url/rateComment?commentId=@(comment.CommentId)&rating=@(comment.YourThumb == 1 ? 0 : 1)"> <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> <b><a href="/user/@comment.PosterUserId">@comment.Poster.Username</a>: </b>
@if (comment.Deleted) @if (comment.Deleted)
{ {
<i><span>@decodedMessage</span></i> <i>
<span>@decodedMessage</span>
</i>
} }
else else
{ {
@ -66,13 +84,4 @@
</div> </div>
</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> </div>

View file

@ -38,7 +38,7 @@
<div class="field"> <div class="field">
<label>Username</label> <label>Username</label>
<div class="ui left icon input"> <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> <i class="user icon"></i>
</div> </div>
</div> </div>

View file

@ -1,14 +1,10 @@
#nullable enable #nullable enable
using System; using System;
using System.Collections.Concurrent;
using System.Diagnostics; using System.Diagnostics;
using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using Kettu; using Kettu;
using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Logging; using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.Types.Files;
using LBPUnion.ProjectLighthouse.Types.Settings; using LBPUnion.ProjectLighthouse.Types.Settings;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@ -82,41 +78,10 @@ public static class Program
return; return;
} }
FileHelper.EnsureDirectoryCreated(Path.Combine(Environment.CurrentDirectory, "png")); FileHelper.ConvertAllTexturesToPng();
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(); Logger.Log("Starting room cleanup thread...", LoggerLevelStartup.Instance);
RoomHelper.StartCleanupThread();
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);
}
}
stopwatch.Stop(); stopwatch.Stop();
Logger.Log($"Ready! Startup took {stopwatch.ElapsedMilliseconds}ms. Passing off control to ASP.NET...", LoggerLevelStartup.Instance); 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) if (gameToken != null && gameToken.GameVersion == GameVersion.LittleBigPlanet1)
// Ignore UserFromGameToken null because user must exist for a token to exist // 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 #nullable disable

View file

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

View file

@ -1,6 +1,7 @@
#nullable enable #nullable enable
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using LBPUnion.ProjectLighthouse.Helpers.Extensions;
using LBPUnion.ProjectLighthouse.Types.Levels; using LBPUnion.ProjectLighthouse.Types.Levels;
namespace LBPUnion.ProjectLighthouse.Types.Categories; namespace LBPUnion.ProjectLighthouse.Types.Categories;
@ -34,6 +35,8 @@ public class CustomCategory : Category
public sealed override string IconHash { get; set; } public sealed override string IconHash { get; set; }
public sealed override string Endpoint { 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 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; public override int GetTotalSlots(Database database) => this.SlotIds.Count;
} }

View file

@ -2,6 +2,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using LBPUnion.ProjectLighthouse.Helpers.Extensions;
using LBPUnion.ProjectLighthouse.Types.Levels; using LBPUnion.ProjectLighthouse.Types.Levels;
using Microsoft.EntityFrameworkCore; 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 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 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) 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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using LBPUnion.ProjectLighthouse.Helpers.Extensions;
using LBPUnion.ProjectLighthouse.Types.Levels; using LBPUnion.ProjectLighthouse.Types.Levels;
namespace LBPUnion.ProjectLighthouse.Types.Categories; 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 Slot? GetPreviewSlot(Database database) => database.Slots.OrderByDescending(s => s.FirstUploaded).FirstOrDefault();
public override IEnumerable<Slot> GetSlots public override IEnumerable<Slot> GetSlots
(Database database, int pageStart, int pageSize) (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(); public override int GetTotalSlots(Database database) => database.Slots.Count();
} }

View file

@ -2,6 +2,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using LBPUnion.ProjectLighthouse.Helpers.Extensions;
using LBPUnion.ProjectLighthouse.Types.Levels; using LBPUnion.ProjectLighthouse.Types.Levels;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@ -18,7 +19,13 @@ public class QueueCategory : CategoryWithUser
public override Slot? GetPreviewSlot(Database database, User user) public override Slot? GetPreviewSlot(Database database, User user)
=> database.QueuedLevels.Include(q => q.Slot).FirstOrDefault(q => q.UserId == user.UserId)?.Slot; => 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) 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); 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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using LBPUnion.ProjectLighthouse.Helpers.Extensions;
using LBPUnion.ProjectLighthouse.Types.Levels; using LBPUnion.ProjectLighthouse.Types.Levels;
namespace LBPUnion.ProjectLighthouse.Types.Categories; 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 Slot? GetPreviewSlot(Database database) => database.Slots.OrderByDescending(s => s.FirstUploaded).FirstOrDefault(s => s.TeamPick);
public override IEnumerable<Slot> GetSlots public override IEnumerable<Slot> GetSlots
(Database database, int pageStart, int pageSize) (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); 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 GameVersion GameVersion { get; set; }
public Platform Platform { get; set; }
// Set by /authentication webpage // Set by /authentication webpage
public bool Approved { get; set; } public bool Approved { get; set; }

View file

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

View file

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

View file

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

View file

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