mirror of
https://github.com/LBPUnion/ProjectLighthouse.git
synced 2025-07-29 16:38:37 +00:00
Merge main into mod-panel
This commit is contained in:
commit
b6da930e20
300 changed files with 8417 additions and 700 deletions
|
@ -1,7 +1,9 @@
|
|||
#nullable enable
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Xml.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Configuration;
|
||||
using LBPUnion.ProjectLighthouse.PlayerData;
|
||||
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
|
@ -45,12 +47,56 @@ public class ClientConfigurationController : ControllerBase
|
|||
|
||||
[HttpGet("privacySettings")]
|
||||
[Produces("text/xml")]
|
||||
public IActionResult PrivacySettings()
|
||||
public async Task<IActionResult> GetPrivacySettings()
|
||||
{
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
PrivacySettings ps = new()
|
||||
{
|
||||
LevelVisibility = "all",
|
||||
ProfileVisibility = "all",
|
||||
LevelVisibility = user.LevelVisibility.ToSerializedString(),
|
||||
ProfileVisibility = user.ProfileVisibility.ToSerializedString(),
|
||||
};
|
||||
|
||||
return this.Ok(ps.Serialize());
|
||||
}
|
||||
|
||||
[HttpPost("privacySettings")]
|
||||
[Produces("text/xml")]
|
||||
public async Task<IActionResult> SetPrivacySetting()
|
||||
{
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
this.Request.Body.Position = 0;
|
||||
string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync();
|
||||
|
||||
XmlSerializer serializer = new(typeof(PrivacySettings));
|
||||
PrivacySettings? settings = (PrivacySettings?)serializer.Deserialize(new StringReader(bodyString));
|
||||
if (settings == null) return this.BadRequest();
|
||||
|
||||
if (settings.LevelVisibility != null)
|
||||
{
|
||||
PrivacyType? type = PrivacyTypeExtensions.FromSerializedString(settings.LevelVisibility);
|
||||
if (type == null) return this.BadRequest();
|
||||
|
||||
user.LevelVisibility = (PrivacyType)type;
|
||||
}
|
||||
|
||||
if (settings.ProfileVisibility != null)
|
||||
{
|
||||
PrivacyType? type = PrivacyTypeExtensions.FromSerializedString(settings.ProfileVisibility);
|
||||
if (type == null) return this.BadRequest();
|
||||
|
||||
user.ProfileVisibility = (PrivacyType)type;
|
||||
}
|
||||
|
||||
await this.database.SaveChangesAsync();
|
||||
|
||||
PrivacySettings ps = new()
|
||||
{
|
||||
LevelVisibility = user.LevelVisibility.ToSerializedString(),
|
||||
ProfileVisibility = user.ProfileVisibility.ToSerializedString(),
|
||||
};
|
||||
|
||||
return this.Ok(ps.Serialize());
|
||||
|
|
|
@ -23,32 +23,40 @@ public class CommentController : ControllerBase
|
|||
}
|
||||
|
||||
[HttpPost("rateUserComment/{username}")]
|
||||
[HttpPost("rateComment/user/{slotId:int}")]
|
||||
public async Task<IActionResult> RateComment([FromQuery] int commentId, [FromQuery] int rating, string? username, int? slotId)
|
||||
[HttpPost("rateComment/{slotType}/{slotId:int}")]
|
||||
public async Task<IActionResult> RateComment([FromQuery] int commentId, [FromQuery] int rating, string? username, string? slotType, int slotId)
|
||||
{
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
if (SlotHelper.IsTypeInvalid(slotType)) return this.BadRequest();
|
||||
|
||||
bool success = await this.database.RateComment(user, commentId, rating);
|
||||
if (!success) return this.BadRequest();
|
||||
|
||||
return this.Ok();
|
||||
}
|
||||
|
||||
[HttpGet("comments/user/{slotId:int}")]
|
||||
[HttpGet("comments/{slotType}/{slotId:int}")]
|
||||
[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, string? slotType, int slotId)
|
||||
{
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
int targetId = slotId.GetValueOrDefault();
|
||||
int targetId = slotId;
|
||||
CommentType type = CommentType.Level;
|
||||
if (!string.IsNullOrWhiteSpace(username))
|
||||
{
|
||||
targetId = this.database.Users.First(u => u.Username.Equals(username)).UserId;
|
||||
type = CommentType.Profile;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (SlotHelper.IsTypeInvalid(slotType) || slotId == 0) return this.BadRequest();
|
||||
}
|
||||
|
||||
if (type == CommentType.Level && slotType == "developer") targetId = await SlotHelper.GetPlaceholderSlotId(this.database, slotId, SlotType.Developer);
|
||||
|
||||
List<Comment> comments = await this.database.Comments.Include
|
||||
(c => c.Poster)
|
||||
|
@ -72,8 +80,8 @@ public class CommentController : ControllerBase
|
|||
}
|
||||
|
||||
[HttpPost("postUserComment/{username}")]
|
||||
[HttpPost("postComment/user/{slotId:int}")]
|
||||
public async Task<IActionResult> PostComment(string? username, int? slotId)
|
||||
[HttpPost("postComment/{slotType}/{slotId:int}")]
|
||||
public async Task<IActionResult> PostComment(string? username, string? slotType, int slotId)
|
||||
{
|
||||
User? poster = await this.database.UserFromGameRequest(this.Request);
|
||||
if (poster == null) return this.StatusCode(403, "");
|
||||
|
@ -86,14 +94,18 @@ public class CommentController : ControllerBase
|
|||
|
||||
SanitizationHelper.SanitizeStringsInClass(comment);
|
||||
|
||||
CommentType type = (slotId.GetValueOrDefault() == 0 ? CommentType.Profile : CommentType.Level);
|
||||
CommentType type = (slotId == 0 ? CommentType.Profile : CommentType.Level);
|
||||
|
||||
if (type == CommentType.Level && (SlotHelper.IsTypeInvalid(slotType) || slotId == 0)) return this.BadRequest();
|
||||
|
||||
if (comment == null) return this.BadRequest();
|
||||
|
||||
int targetId = slotId.GetValueOrDefault();
|
||||
int targetId = slotId;
|
||||
|
||||
if (type == CommentType.Profile) targetId = this.database.Users.First(u => u.Username == username).UserId;
|
||||
|
||||
if (slotType == "developer") targetId = await SlotHelper.GetPlaceholderSlotId(this.database, targetId, SlotType.Developer);
|
||||
|
||||
bool success = await this.database.PostComment(poster, targetId, type, comment.Message);
|
||||
if (success) return this.Ok();
|
||||
|
||||
|
@ -101,8 +113,8 @@ public class CommentController : ControllerBase
|
|||
}
|
||||
|
||||
[HttpPost("deleteUserComment/{username}")]
|
||||
[HttpPost("deleteComment/user/{slotId:int}")]
|
||||
public async Task<IActionResult> DeleteComment([FromQuery] int commentId, string? username, int? slotId)
|
||||
[HttpPost("deleteComment/{slotType}/{slotId:int}")]
|
||||
public async Task<IActionResult> DeleteComment([FromQuery] int commentId, string? username, string? slotType, int slotId)
|
||||
{
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
@ -110,6 +122,10 @@ public class CommentController : ControllerBase
|
|||
Comment? comment = await this.database.Comments.FirstOrDefaultAsync(c => c.CommentId == commentId);
|
||||
if (comment == null) return this.NotFound();
|
||||
|
||||
if (comment.Type == CommentType.Level && (SlotHelper.IsTypeInvalid(slotType) || slotId == 0)) return this.BadRequest();
|
||||
|
||||
if (slotType == "developer") slotId = await SlotHelper.GetPlaceholderSlotId(this.database, slotId, SlotType.Developer);
|
||||
|
||||
// if you are not the poster
|
||||
if (comment.PosterUserId != user.UserId)
|
||||
{
|
||||
|
@ -125,7 +141,7 @@ public class CommentController : ControllerBase
|
|||
{
|
||||
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == comment.TargetId);
|
||||
// if you aren't the creator of the level
|
||||
if (slot == null || slot.CreatorId != user.UserId || slotId.GetValueOrDefault() != slot.SlotId)
|
||||
if (slot == null || slot.CreatorId != user.UserId || slotId != slot.SlotId)
|
||||
{
|
||||
return this.StatusCode(403, "");
|
||||
}
|
||||
|
|
|
@ -57,9 +57,10 @@ public class LoginController : ControllerBase
|
|||
|
||||
string ipAddress = remoteIpAddress.ToString();
|
||||
|
||||
await this.database.RemoveExpiredTokens();
|
||||
|
||||
// Get an existing token from the IP & username
|
||||
GameToken? token = await this.database.GameTokens.Include
|
||||
(t => t.User)
|
||||
GameToken? token = await this.database.GameTokens.Include(t => t.User)
|
||||
.FirstOrDefaultAsync(t => t.UserLocation == ipAddress && t.User.Username == npTicket.Username && !t.Used);
|
||||
|
||||
if (token == null) // If we cant find an existing token, try to generate a new one
|
||||
|
@ -67,7 +68,8 @@ public class LoginController : ControllerBase
|
|||
token = await this.database.AuthenticateUser(npTicket, ipAddress);
|
||||
if (token == null)
|
||||
{
|
||||
Logger.Warn($"Unable to find/generate a token for username {npTicket.Username}", LogArea.Login);
|
||||
Logger.Warn($"Unable to " +
|
||||
$"find/generate a token for username {npTicket.Username}", LogArea.Login);
|
||||
return this.StatusCode(403, ""); // If not, then 403.
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@ using LBPUnion.ProjectLighthouse.Match.MatchCommands;
|
|||
using LBPUnion.ProjectLighthouse.Match.Rooms;
|
||||
using LBPUnion.ProjectLighthouse.PlayerData;
|
||||
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
|
@ -71,7 +70,7 @@ public class MatchController : ControllerBase
|
|||
|
||||
#endregion
|
||||
|
||||
await LastContactHelper.SetLastContact(user, gameToken.GameVersion, gameToken.Platform);
|
||||
await LastContactHelper.SetLastContact(this.database, user, gameToken.GameVersion, gameToken.Platform);
|
||||
|
||||
#region Process match data
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#nullable enable
|
||||
using System.Globalization;
|
||||
using LBPUnion.ProjectLighthouse.Configuration;
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Logging;
|
||||
|
@ -75,9 +76,10 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.";
|
|||
$"token.Used: {gameToken.Used}\n" +
|
||||
$"token.UserLocation: {gameToken.UserLocation}\n" +
|
||||
$"token.GameVersion: {gameToken.GameVersion}\n" +
|
||||
$"token.ExpiresAt: {gameToken.ExpiresAt.ToString(CultureInfo.CurrentCulture)}\n" +
|
||||
"---DEBUG INFO---" +
|
||||
#endif
|
||||
"\n"
|
||||
(announceText != "" ? "\n" : "")
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -3,11 +3,11 @@ using System.Xml.Serialization;
|
|||
using Discord;
|
||||
using LBPUnion.ProjectLighthouse.Configuration;
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Levels;
|
||||
using LBPUnion.ProjectLighthouse.Logging;
|
||||
using LBPUnion.ProjectLighthouse.PlayerData;
|
||||
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
|
||||
using LBPUnion.ProjectLighthouse.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
|
@ -53,6 +53,38 @@ public class PhotosController : ControllerBase
|
|||
photo.CreatorId = user.UserId;
|
||||
photo.Creator = user;
|
||||
|
||||
if (photo.XmlLevelInfo != null)
|
||||
{
|
||||
bool validLevel = false;
|
||||
PhotoSlot photoSlot = photo.XmlLevelInfo;
|
||||
if (photoSlot.SlotType is SlotType.Pod or SlotType.Local) photoSlot.SlotId = 0;
|
||||
switch (photoSlot.SlotType)
|
||||
{
|
||||
case SlotType.User:
|
||||
{
|
||||
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.Type == SlotType.User && s.SlotId == photoSlot.SlotId);
|
||||
if (slot != null) validLevel = slot.RootLevel == photoSlot.RootLevel;
|
||||
break;
|
||||
}
|
||||
case SlotType.Pod:
|
||||
case SlotType.Local:
|
||||
case SlotType.Developer:
|
||||
{
|
||||
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.Type == photoSlot.SlotType && s.InternalSlotId == photoSlot.SlotId);
|
||||
if (slot != null)
|
||||
photoSlot.SlotId = slot.SlotId;
|
||||
else
|
||||
photoSlot.SlotId = await SlotHelper.GetPlaceholderSlotId(this.database, photoSlot.SlotId, photoSlot.SlotType);
|
||||
validLevel = true;
|
||||
break;
|
||||
}
|
||||
default: Logger.Warn($"Invalid photo level type: {photoSlot.SlotType}", LogArea.Photos);
|
||||
break;
|
||||
}
|
||||
|
||||
if (validLevel) photo.SlotId = photo.XmlLevelInfo.SlotId;
|
||||
}
|
||||
|
||||
if (photo.Subjects.Count > 4) return this.BadRequest();
|
||||
|
||||
if (photo.Timestamp > TimeHelper.Timestamp) photo.Timestamp = TimeHelper.Timestamp;
|
||||
|
@ -104,11 +136,23 @@ public class PhotosController : ControllerBase
|
|||
return this.Ok();
|
||||
}
|
||||
|
||||
[HttpGet("photos/user/{id:int}")]
|
||||
public async Task<IActionResult> SlotPhotos(int id)
|
||||
[HttpGet("photos/{slotType}/{id:int}")]
|
||||
public async Task<IActionResult> SlotPhotos([FromQuery] int pageStart, [FromQuery] int pageSize, string slotType, int id)
|
||||
{
|
||||
List<Photo> photos = await this.database.Photos.Include(p => p.Creator).Take(10).ToListAsync();
|
||||
string response = photos.Aggregate(string.Empty, (s, photo) => s + photo.Serialize(id));
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
if (SlotHelper.IsTypeInvalid(slotType)) return this.BadRequest();
|
||||
|
||||
if (slotType == "developer") id = await SlotHelper.GetPlaceholderSlotId(this.database, id, SlotType.Developer);
|
||||
|
||||
List<Photo> photos = await this.database.Photos.Include(p => p.Creator)
|
||||
.Where(p => p.SlotId == id)
|
||||
.OrderByDescending(s => s.Timestamp)
|
||||
.Skip(pageStart - 1)
|
||||
.Take(Math.Min(pageSize, 30))
|
||||
.ToListAsync();
|
||||
string response = photos.Aggregate(string.Empty, (s, photo) => s + photo.Serialize(id, SlotHelper.ParseType(slotType)));
|
||||
return this.Ok(LbpSerializer.StringElement("photos", response));
|
||||
}
|
||||
|
||||
|
@ -126,7 +170,7 @@ public class PhotosController : ControllerBase
|
|||
.Skip(pageStart - 1)
|
||||
.Take(Math.Min(pageSize, 30))
|
||||
.ToListAsync();
|
||||
string response = photos.Aggregate(string.Empty, (s, photo) => s + photo.Serialize(0));
|
||||
string response = photos.Aggregate(string.Empty, (s, photo) => s + photo.Serialize());
|
||||
return this.Ok(LbpSerializer.StringElement("photos", response));
|
||||
}
|
||||
|
||||
|
@ -145,7 +189,7 @@ public class PhotosController : ControllerBase
|
|||
(s => s.Timestamp)
|
||||
.Skip(pageStart - 1)
|
||||
.Take(Math.Min(pageSize, 30))
|
||||
.Aggregate(string.Empty, (s, photo) => s + photo.Serialize(0));
|
||||
.Aggregate(string.Empty, (s, photo) => s + photo.Serialize());
|
||||
|
||||
return this.Ok(LbpSerializer.StringElement("photos", response));
|
||||
}
|
||||
|
|
|
@ -75,7 +75,7 @@ public class ResourcesController : ControllerBase
|
|||
|
||||
FileHelper.EnsureDirectoryCreated(assetsDirectory);
|
||||
// lbp treats code 409 as success and as an indicator that the file is already present
|
||||
if (FileHelper.ResourceExists(hash)) this.Conflict();
|
||||
if (FileHelper.ResourceExists(hash)) return this.Conflict();
|
||||
|
||||
Logger.Info($"Processing resource upload (hash: {hash})", LogArea.Resources);
|
||||
LbpFile file = new(await readFromPipeReader(this.Request.BodyReader));
|
||||
|
|
|
@ -47,6 +47,8 @@ public class CollectionController : ControllerBase
|
|||
}
|
||||
);
|
||||
|
||||
categoriesSerialized += LbpSerializer.StringElement("text_search", LbpSerializer.StringElement("url", "/slots/searchLBP3"));
|
||||
|
||||
return this.Ok
|
||||
(
|
||||
LbpSerializer.TaggedStringElement
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
#nullable enable
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Extensions;
|
||||
using LBPUnion.ProjectLighthouse.Levels;
|
||||
using LBPUnion.ProjectLighthouse.PlayerData;
|
||||
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
|
||||
using LBPUnion.ProjectLighthouse.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
|
@ -25,24 +26,23 @@ public class ListController : ControllerBase
|
|||
#region Level Queue (lolcatftw)
|
||||
|
||||
[HttpGet("slots/lolcatftw/{username}")]
|
||||
public async Task<IActionResult> GetLevelQueue(string username, [FromQuery] int pageSize, [FromQuery] int pageStart)
|
||||
public async Task<IActionResult> GetQueuedLevels(string username, [FromQuery] int pageSize, [FromQuery] int pageStart)
|
||||
{
|
||||
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
|
||||
if (token == null) return this.StatusCode(403, "");
|
||||
|
||||
GameVersion gameVersion = token.GameVersion;
|
||||
|
||||
IEnumerable<QueuedLevel> queuedLevels = this.database.QueuedLevels.Include(q => q.User)
|
||||
.Include(q => q.Slot)
|
||||
.Include(q => q.Slot.Location)
|
||||
IEnumerable<Slot> queuedLevels = this.database.QueuedLevels.Where(q => q.User.Username == username)
|
||||
.Include(q => q.Slot.Creator)
|
||||
.Where(q => q.Slot.GameVersion <= gameVersion)
|
||||
.Where(q => q.User.Username == username)
|
||||
.Include(q => q.Slot.Location)
|
||||
.Select(q => q.Slot)
|
||||
.ByGameVersion(gameVersion)
|
||||
.Skip(pageStart - 1)
|
||||
.Take(Math.Min(pageSize, 30))
|
||||
.AsEnumerable();
|
||||
|
||||
string response = queuedLevels.Aggregate(string.Empty, (current, q) => current + q.Slot.Serialize(gameVersion));
|
||||
string response = queuedLevels.Aggregate(string.Empty, (current, q) => current + q.Serialize(gameVersion));
|
||||
|
||||
return this.Ok
|
||||
(
|
||||
|
@ -104,22 +104,24 @@ public class ListController : ControllerBase
|
|||
|
||||
GameVersion gameVersion = token.GameVersion;
|
||||
|
||||
IEnumerable<HeartedLevel> heartedLevels = this.database.HeartedLevels.Include(q => q.User)
|
||||
.Include(q => q.Slot)
|
||||
.Include(q => q.Slot.Location)
|
||||
IEnumerable<Slot> heartedLevels = this.database.HeartedLevels.Where(q => q.User.Username == username)
|
||||
.Include(q => q.Slot.Creator)
|
||||
.Where(q => q.Slot.GameVersion <= gameVersion)
|
||||
.Where(q => q.User.Username == username)
|
||||
.Include(q => q.Slot.Location)
|
||||
.Select(q => q.Slot)
|
||||
.ByGameVersion(gameVersion)
|
||||
.Skip(pageStart - 1)
|
||||
.Take(Math.Min(pageSize, 30))
|
||||
.AsEnumerable();
|
||||
|
||||
string response = heartedLevels.Aggregate(string.Empty, (current, q) => current + q.Slot.Serialize(gameVersion));
|
||||
string response = heartedLevels.Aggregate(string.Empty, (current, q) => current + q.Serialize(gameVersion));
|
||||
|
||||
return this.Ok
|
||||
(
|
||||
LbpSerializer.TaggedStringElement
|
||||
("favouriteSlots", response, "total", this.database.HeartedLevels.Include(q => q.User).Count(q => q.User.Username == username))
|
||||
LbpSerializer.TaggedStringElement("favouriteSlots", response, new Dictionary<string, object>
|
||||
{
|
||||
{ "total", this.database.HeartedLevels.Include(q => q.User).Count(q => q.User.Username == username) },
|
||||
{ "hint_start", pageStart + Math.Min(pageSize, 30) },
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -176,8 +178,11 @@ public class ListController : ControllerBase
|
|||
|
||||
return this.Ok
|
||||
(
|
||||
LbpSerializer.TaggedStringElement
|
||||
("favouriteUsers", response, "total", this.database.HeartedProfiles.Include(q => q.User).Count(q => q.User.Username == username))
|
||||
LbpSerializer.TaggedStringElement("favouriteUsers", response, new Dictionary<string, object>
|
||||
{
|
||||
{ "total", this.database.HeartedProfiles.Include(q => q.User).Count(q => q.User.Username == username) },
|
||||
{ "hint_start", pageStart + Math.Min(pageSize, 30) },
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@ using LBPUnion.ProjectLighthouse.Logging;
|
|||
using LBPUnion.ProjectLighthouse.PlayerData;
|
||||
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
|
||||
using LBPUnion.ProjectLighthouse.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
|
@ -41,9 +40,16 @@ public class PublishController : ControllerBase
|
|||
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
|
||||
if (slot == null) {
|
||||
Logger.Warn("Rejecting level upload, slot is null", LogArea.Publish);
|
||||
return this.BadRequest(); // if the level cant be parsed then it obviously cant be uploaded
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(slot.RootLevel)) return this.BadRequest();
|
||||
if (string.IsNullOrEmpty(slot.RootLevel))
|
||||
{
|
||||
Logger.Warn("Rejecting level upload, slot does not include rootLevel", LogArea.Publish);
|
||||
return this.BadRequest();
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(slot.ResourceCollection)) slot.ResourceCollection = slot.RootLevel;
|
||||
|
||||
|
@ -51,8 +57,16 @@ public class PublishController : ControllerBase
|
|||
if (slot.SlotId != 0)
|
||||
{
|
||||
Slot? oldSlot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == slot.SlotId);
|
||||
if (oldSlot == null) return this.NotFound();
|
||||
if (oldSlot.CreatorId != user.UserId) return this.BadRequest();
|
||||
if (oldSlot == null)
|
||||
{
|
||||
Logger.Warn("Rejecting level reupload, could not find old slot", LogArea.Publish);
|
||||
return this.NotFound();
|
||||
}
|
||||
if (oldSlot.CreatorId != user.UserId)
|
||||
{
|
||||
Logger.Warn("Rejecting level reupload, old slot's creator is not publishing user", LogArea.Publish);
|
||||
return this.BadRequest();
|
||||
}
|
||||
}
|
||||
else if (user.GetUsedSlotsForGame(gameToken.GameVersion) > ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots)
|
||||
{
|
||||
|
@ -127,6 +141,11 @@ public class PublishController : ControllerBase
|
|||
return this.BadRequest();
|
||||
}
|
||||
|
||||
GameVersion slotVersion = FileHelper.ParseLevelVersion(rootLevel);
|
||||
|
||||
slot.GameVersion = slotVersion;
|
||||
if (slotVersion == GameVersion.Unknown) slot.GameVersion = gameToken.GameVersion;
|
||||
|
||||
// Republish logic
|
||||
if (slot.SlotId != 0)
|
||||
{
|
||||
|
@ -177,16 +196,6 @@ public class PublishController : ControllerBase
|
|||
|
||||
slot.TeamPick = oldSlot.TeamPick;
|
||||
|
||||
// Only update a slot's gameVersion if the level was actually change
|
||||
if (oldSlot.RootLevel != slot.RootLevel)
|
||||
{
|
||||
slot.GameVersion = gameToken.GameVersion;
|
||||
}
|
||||
else
|
||||
{
|
||||
slot.GameVersion = oldSlot.GameVersion;
|
||||
}
|
||||
|
||||
if (slot.MinimumPlayers == 0 || slot.MaximumPlayers == 0)
|
||||
{
|
||||
slot.MinimumPlayers = 1;
|
||||
|
@ -198,7 +207,7 @@ public class PublishController : ControllerBase
|
|||
return this.Ok(oldSlot.Serialize(gameToken.GameVersion));
|
||||
}
|
||||
|
||||
if (user.GetUsedSlotsForGame(gameToken.GameVersion) > ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots)
|
||||
if (user.GetUsedSlotsForGame(slotVersion) > ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots)
|
||||
{
|
||||
Logger.Warn("Rejecting level upload, too many published slots", LogArea.Publish);
|
||||
return this.BadRequest();
|
||||
|
@ -216,7 +225,6 @@ public class PublishController : ControllerBase
|
|||
slot.CreatorId = user.UserId;
|
||||
slot.FirstUploaded = TimeHelper.UnixTimeMilliseconds();
|
||||
slot.LastUpdated = TimeHelper.UnixTimeMilliseconds();
|
||||
slot.GameVersion = gameToken.GameVersion;
|
||||
|
||||
if (slot.MinimumPlayers == 0 || slot.MaximumPlayers == 0)
|
||||
{
|
||||
|
@ -226,13 +234,13 @@ public class PublishController : ControllerBase
|
|||
|
||||
this.database.Slots.Add(slot);
|
||||
await this.database.SaveChangesAsync();
|
||||
|
||||
await WebhookHelper.SendWebhook
|
||||
(
|
||||
"New level published!",
|
||||
$"**{user.Username}** just published a new level: [**{slot.Name}**]({ServerConfiguration.Instance.ExternalUrl}/slot/{slot.SlotId})\n{slot.Description}"
|
||||
);
|
||||
|
||||
if (user.LevelVisibility == PrivacyType.All)
|
||||
{
|
||||
await WebhookHelper.SendWebhook("New level published!",
|
||||
$"**{user.Username}** just published a new level: [**{slot.Name}**]({ServerConfiguration.Instance.ExternalUrl}/slot/{slot.SlotId})\n{slot.Description}");
|
||||
}
|
||||
|
||||
Logger.Success($"Successfully published level {slot.Name} (id: {slot.SlotId}) by {user.Username} (id: {user.UserId})", LogArea.Publish);
|
||||
|
||||
return this.Ok(slot.Serialize(gameToken.GameVersion));
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#nullable enable
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Xml.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Extensions;
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Levels;
|
||||
using LBPUnion.ProjectLighthouse.PlayerData;
|
||||
|
@ -23,13 +24,15 @@ public class ScoreController : ControllerBase
|
|||
this.database = database;
|
||||
}
|
||||
|
||||
[HttpPost("scoreboard/user/{id:int}")]
|
||||
public async Task<IActionResult> SubmitScore(int id, [FromQuery] bool lbp1 = false, [FromQuery] bool lbp2 = false, [FromQuery] bool lbp3 = false)
|
||||
[HttpPost("scoreboard/{slotType}/{id:int}")]
|
||||
public async Task<IActionResult> SubmitScore(string slotType, int id, [FromQuery] bool lbp1 = false, [FromQuery] bool lbp2 = false, [FromQuery] bool lbp3 = false)
|
||||
{
|
||||
(User, GameToken)? userAndToken = await this.database.UserAndGameTokenFromRequest(this.Request);
|
||||
|
||||
if (userAndToken == null) return this.StatusCode(403, "");
|
||||
|
||||
if (SlotHelper.IsTypeInvalid(slotType)) return this.BadRequest();
|
||||
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
User user = userAndToken.Value.Item1;
|
||||
GameToken gameToken = userAndToken.Value.Item2;
|
||||
|
@ -43,6 +46,8 @@ public class ScoreController : ControllerBase
|
|||
|
||||
SanitizationHelper.SanitizeStringsInClass(score);
|
||||
|
||||
if (slotType == "developer") id = await SlotHelper.GetPlaceholderSlotId(this.database, id, SlotType.Developer);
|
||||
|
||||
score.SlotId = id;
|
||||
|
||||
Slot? slot = this.database.Slots.FirstOrDefault(s => s.SlotId == score.SlotId);
|
||||
|
@ -92,15 +97,19 @@ public class ScoreController : ControllerBase
|
|||
//=> await TopScores(slotId, type);
|
||||
=> this.Ok(LbpSerializer.BlankElement("scores"));
|
||||
|
||||
[HttpGet("topscores/user/{slotId:int}/{type:int}")]
|
||||
[HttpGet("topscores/{slotType}/{slotId:int}/{type:int}")]
|
||||
[SuppressMessage("ReSharper", "PossibleMultipleEnumeration")]
|
||||
public async Task<IActionResult> TopScores(int slotId, int type, [FromQuery] int pageStart = -1, [FromQuery] int pageSize = 5)
|
||||
public async Task<IActionResult> TopScores(string slotType, int slotId, int type, [FromQuery] int pageStart = -1, [FromQuery] int pageSize = 5)
|
||||
{
|
||||
// Get username
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
if (SlotHelper.IsTypeInvalid(slotType)) return this.BadRequest();
|
||||
|
||||
if (slotType == "developer") slotId = await SlotHelper.GetPlaceholderSlotId(this.database, slotId, SlotType.Developer);
|
||||
|
||||
return this.Ok(this.getScores(slotId, type, user, pageStart, pageSize));
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ using Microsoft.EntityFrameworkCore;
|
|||
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Slots;
|
||||
|
||||
[ApiController]
|
||||
[Route("LITTLEBIGPLANETPS3_XML/")]
|
||||
[Route("LITTLEBIGPLANETPS3_XML/slots")]
|
||||
[Produces("text/xml")]
|
||||
public class SearchController : ControllerBase
|
||||
{
|
||||
|
@ -19,8 +19,17 @@ public class SearchController : ControllerBase
|
|||
this.database = database;
|
||||
}
|
||||
|
||||
[HttpGet("slots/search")]
|
||||
public async Task<IActionResult> SearchSlots([FromQuery] string query, [FromQuery] int pageSize, [FromQuery] int pageStart)
|
||||
[HttpGet("searchLBP3")]
|
||||
public Task<IActionResult> SearchSlotsLBP3([FromQuery] int pageSize, [FromQuery] int pageStart, [FromQuery] string textFilter)
|
||||
=> SearchSlots(textFilter, pageSize, pageStart, "results");
|
||||
|
||||
[HttpGet("search")]
|
||||
public async Task<IActionResult> SearchSlots(
|
||||
[FromQuery] string query,
|
||||
[FromQuery] int pageSize,
|
||||
[FromQuery] int pageStart,
|
||||
string? keyName = "slots"
|
||||
)
|
||||
{
|
||||
GameToken? gameToken = await this.database.GameTokenFromRequest(this.Request);
|
||||
if (gameToken == null) return this.StatusCode(403, "");
|
||||
|
@ -31,8 +40,7 @@ public class SearchController : ControllerBase
|
|||
|
||||
string[] keywords = query.Split(" ");
|
||||
|
||||
IQueryable<Slot> dbQuery = this.database.Slots.Include
|
||||
(s => s.Creator)
|
||||
IQueryable<Slot> dbQuery = this.database.Slots.Include(s => s.Creator)
|
||||
.Include(s => s.Location)
|
||||
.OrderBy(s => !s.TeamPick)
|
||||
.ThenByDescending(s => s.FirstUploaded)
|
||||
|
@ -52,6 +60,9 @@ public class SearchController : ControllerBase
|
|||
|
||||
string response = slots.Aggregate("", (current, slot) => current + slot.Serialize(gameToken.GameVersion));
|
||||
|
||||
return this.Ok(LbpSerializer.TaggedStringElement("slots", response, "total", dbQuery.Count()));
|
||||
return this.Ok(LbpSerializer.TaggedStringElement(keyName, response, "total", dbQuery.Count()));
|
||||
}
|
||||
}
|
||||
|
||||
// /LITTLEBIGPLANETPS3_XML?pageStart=1&pageSize=10&resultTypes[]=slot&resultTypes[]=playlist&resultTypes[]=user&adventure=dontCare&textFilter=qwer
|
||||
|
||||
}
|
||||
|
|
|
@ -3,11 +3,11 @@ using LBPUnion.ProjectLighthouse.Configuration;
|
|||
using LBPUnion.ProjectLighthouse.Extensions;
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Levels;
|
||||
using LBPUnion.ProjectLighthouse.Match.Rooms;
|
||||
using LBPUnion.ProjectLighthouse.PlayerData;
|
||||
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
|
||||
using LBPUnion.ProjectLighthouse.PlayerData.Reviews;
|
||||
using LBPUnion.ProjectLighthouse.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
|
@ -64,6 +64,72 @@ public class SlotsController : ControllerBase
|
|||
);
|
||||
}
|
||||
|
||||
[HttpGet("slotList")]
|
||||
public async Task<IActionResult> GetSlotListAlt([FromQuery] int[] s)
|
||||
{
|
||||
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
|
||||
if (token == null) return this.StatusCode(403, "");
|
||||
|
||||
List<string?> serializedSlots = new();
|
||||
foreach (int slotId in s)
|
||||
{
|
||||
Slot? slot = await this.database.Slots.Include(t => t.Creator).Include(t => t.Location).Where(t => t.SlotId == slotId && t.Type == SlotType.User).FirstOrDefaultAsync();
|
||||
if (slot == null)
|
||||
{
|
||||
slot = await this.database.Slots.Where(t => t.InternalSlotId == slotId && t.Type == SlotType.Developer).FirstOrDefaultAsync();
|
||||
if (slot == null)
|
||||
{
|
||||
serializedSlots.Add($"<slot type=\"developer\"><id>{slotId}</id></slot>");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
serializedSlots.Add(slot.Serialize());
|
||||
}
|
||||
string serialized = serializedSlots.Aggregate(string.Empty, (current, slot) => slot == null ? current : current + slot);
|
||||
|
||||
return this.Ok(LbpSerializer.TaggedStringElement("slots", serialized, "total", serializedSlots.Count));
|
||||
}
|
||||
|
||||
[HttpGet("slots/developer")]
|
||||
public async Task<IActionResult> StoryPlayers()
|
||||
{
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
|
||||
if (token == null) return this.StatusCode(403, "");
|
||||
|
||||
List<int> activeSlotIds = RoomHelper.Rooms.Where(r => r.Slot.SlotType == SlotType.Developer).Select(r => r.Slot.SlotId).ToList();
|
||||
|
||||
List<string> serializedSlots = new();
|
||||
|
||||
foreach (int id in activeSlotIds)
|
||||
{
|
||||
int placeholderSlotId = await SlotHelper.GetPlaceholderSlotId(this.database, id, SlotType.Developer);
|
||||
Slot slot = await this.database.Slots.FirstAsync(s => s.SlotId == placeholderSlotId);
|
||||
serializedSlots.Add(slot.SerializeDevSlot(false));
|
||||
}
|
||||
|
||||
string serialized = serializedSlots.Aggregate(string.Empty, (current, slot) => current + slot);
|
||||
|
||||
return this.Ok(LbpSerializer.StringElement("slots", serialized));
|
||||
}
|
||||
|
||||
[HttpGet("s/developer/{id:int}")]
|
||||
public async Task<IActionResult> SDev(int id)
|
||||
{
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
|
||||
if (token == null) return this.StatusCode(403, "");
|
||||
|
||||
int slotId = await SlotHelper.GetPlaceholderSlotId(this.database, id, SlotType.Developer);
|
||||
Slot slot = await this.database.Slots.FirstAsync(s => s.SlotId == slotId);
|
||||
|
||||
return this.Ok(slot.SerializeDevSlot());
|
||||
}
|
||||
|
||||
[HttpGet("s/user/{id:int}")]
|
||||
public async Task<IActionResult> SUser(int id)
|
||||
{
|
||||
|
@ -353,6 +419,69 @@ public class SlotsController : ControllerBase
|
|||
)
|
||||
);
|
||||
}
|
||||
|
||||
// /slots/busiest?pageStart=1&pageSize=30&gameFilterType=both&players=1&move=true
|
||||
[HttpGet("slots/busiest")]
|
||||
public async Task<IActionResult> BusiestLevels
|
||||
(
|
||||
[FromQuery] int pageStart,
|
||||
[FromQuery] int pageSize,
|
||||
[FromQuery] string? gameFilterType = null,
|
||||
[FromQuery] int? players = null,
|
||||
[FromQuery] bool? move = null
|
||||
)
|
||||
{
|
||||
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
|
||||
if (token == null) return this.StatusCode(403, "");
|
||||
|
||||
Dictionary<int, int> playersBySlotId = new();
|
||||
|
||||
foreach (Room room in RoomHelper.Rooms)
|
||||
{
|
||||
// TODO: support developer slotTypes?
|
||||
if(room.Slot.SlotType != SlotType.User) continue;
|
||||
|
||||
if (!playersBySlotId.TryGetValue(room.Slot.SlotId, out int playerCount))
|
||||
playersBySlotId.Add(room.Slot.SlotId, 0);
|
||||
|
||||
playerCount += room.PlayerIds.Count;
|
||||
|
||||
playersBySlotId.Remove(room.Slot.SlotId);
|
||||
playersBySlotId.Add(room.Slot.SlotId, playerCount);
|
||||
}
|
||||
|
||||
IEnumerable<int> orderedPlayersBySlotId = playersBySlotId
|
||||
.Skip(pageStart - 1)
|
||||
.Take(Math.Min(pageSize, 30))
|
||||
.OrderByDescending(kvp => kvp.Value)
|
||||
.Select(kvp => kvp.Key);
|
||||
|
||||
List<Slot> slots = new();
|
||||
|
||||
foreach (int slotId in orderedPlayersBySlotId)
|
||||
{
|
||||
Slot? slot = await this.database.Slots.ByGameVersion(token.GameVersion, false, true)
|
||||
.FirstOrDefaultAsync(s => s.SlotId == slotId);
|
||||
if(slot == null) continue; // shouldn't happen ever unless the room is borked
|
||||
|
||||
slots.Add(slot);
|
||||
}
|
||||
|
||||
string response = slots.Aggregate(string.Empty, (current, slot) => current + slot.Serialize(token.GameVersion));
|
||||
|
||||
return this.Ok(LbpSerializer.TaggedStringElement("slots",
|
||||
response,
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
{
|
||||
"hint_start", pageStart + Math.Min(pageSize, ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots)
|
||||
},
|
||||
{
|
||||
"total", playersBySlotId.Count
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
private GameVersion getGameFilter(string? gameFilterType, GameVersion version)
|
||||
{
|
||||
|
@ -394,10 +523,10 @@ public class SlotsController : ControllerBase
|
|||
if (gameFilterType == "both")
|
||||
// Get game versions less than the current version
|
||||
// Needs support for LBP3 ("both" = LBP1+2)
|
||||
whereSlots = this.database.Slots.Where(s => s.GameVersion <= gameVersion && s.FirstUploaded >= oldestTime);
|
||||
whereSlots = this.database.Slots.Where(s => s.Type == SlotType.User && s.GameVersion <= gameVersion && s.FirstUploaded >= oldestTime);
|
||||
else
|
||||
// Get game versions exactly equal to gamefiltertype
|
||||
whereSlots = this.database.Slots.Where(s => s.GameVersion == gameVersion && s.FirstUploaded >= oldestTime);
|
||||
whereSlots = this.database.Slots.Where(s => s.Type == SlotType.User && s.GameVersion == gameVersion && s.FirstUploaded >= oldestTime);
|
||||
|
||||
return whereSlots.Include(s => s.Creator).Include(s => s.Location);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("/LITTLEBIGPLANETPS3_XML")]
|
||||
[Produces("application/json")]
|
||||
public class StatusController : ControllerBase
|
||||
{
|
||||
[HttpGet("status")]
|
||||
public IActionResult GetStatus() => this.Ok();
|
||||
}
|
|
@ -104,9 +104,15 @@ public class UserController : ControllerBase
|
|||
user.Biography = update.Biography;
|
||||
}
|
||||
|
||||
// ReSharper disable once LoopCanBeConvertedToQuery
|
||||
foreach (string? resource in new[]{update.IconHash, update.YayHash, update.MehHash, update.BooHash, update.PlanetHash,})
|
||||
{
|
||||
if (resource != null && !resource.StartsWith('g') && !FileHelper.ResourceExists(resource)) return this.BadRequest();
|
||||
if (resource == "0") continue;
|
||||
|
||||
if (resource != null && !resource.StartsWith('g') && !FileHelper.ResourceExists(resource))
|
||||
{
|
||||
return this.BadRequest();
|
||||
}
|
||||
}
|
||||
|
||||
if (update.IconHash != null) user.IconHash = update.IconHash;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue