mirror of
https://github.com/LBPUnion/ProjectLighthouse.git
synced 2025-08-02 18:18:39 +00:00
Merge remote-tracking branch 'upstream/main' into Pullrequests
This commit is contained in:
commit
f61b19e331
37 changed files with 609 additions and 119 deletions
11
.github/dependabot.yml
vendored
Normal file
11
.github/dependabot.yml
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "nuget" # See documentation for possible values
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "daily"
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -43,5 +43,3 @@ backup*
|
|||
|
||||
*.tmp
|
||||
*.bin
|
||||
*.png
|
||||
*.jpg
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="JavaScriptLibraryMappings">
|
||||
<file url="PROJECT" libraries="{fomantic-ui, sha256}" />
|
||||
<file url="PROJECT" libraries="{api, fomantic-ui, sha256}" />
|
||||
</component>
|
||||
</project>
|
|
@ -9,8 +9,8 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2021.3.0"/>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.0"/>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.0">
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.2"/>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
@ -20,7 +20,7 @@
|
|||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="3.1.0">
|
||||
<PackageReference Include="coverlet.collector" Version="3.1.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
|
|
@ -9,20 +9,20 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2021.3.0"/>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.0"/>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.0">
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.2"/>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0"/>
|
||||
<PackageReference Include="Selenium.WebDriver" Version="4.1.0"/>
|
||||
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="96.0.4664.4500"/>
|
||||
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="98.0.4758.8000"/>
|
||||
<PackageReference Include="xunit" Version="2.4.1"/>
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="3.1.0">
|
||||
<PackageReference Include="coverlet.collector" Version="3.1.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
|
|
@ -14,8 +14,8 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2021.3.0"/>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.0"/>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.0">
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.2"/>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
@ -25,7 +25,7 @@
|
|||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="3.1.0">
|
||||
<PackageReference Include="coverlet.collector" Version="3.1.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
@ -7,6 +8,7 @@ using System.Xml.Serialization;
|
|||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using LBPUnion.ProjectLighthouse.Types.Levels;
|
||||
using LBPUnion.ProjectLighthouse.Types.Profiles;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
@ -24,37 +26,130 @@ public class CommentController : ControllerBase
|
|||
this.database = database;
|
||||
}
|
||||
|
||||
[HttpGet("userComments/{username}")]
|
||||
public async Task<IActionResult> GetComments(string username)
|
||||
[HttpPost("rateUserComment/{username}")]
|
||||
[HttpPost("rateComment/user/{slotId:int}")]
|
||||
public async Task<IActionResult> RateComment([FromQuery] int commentId, [FromQuery] int rating, string? username, int? slotId)
|
||||
{
|
||||
List<Comment> comments = await this.database.Comments.Include
|
||||
(c => c.Target)
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
Comment? comment = await this.database.Comments.Include(c => c.Poster).FirstOrDefaultAsync(c => commentId == c.CommentId);
|
||||
|
||||
if (comment == null) return this.BadRequest();
|
||||
|
||||
Reaction? reaction = await this.database.Reactions.FirstOrDefaultAsync(r => r.UserId == user.UserId && r.TargetId == commentId);
|
||||
if (reaction == null)
|
||||
{
|
||||
Reaction newReaction = new Reaction()
|
||||
{
|
||||
UserId = user.UserId,
|
||||
TargetId = commentId,
|
||||
Rating = 0,
|
||||
};
|
||||
this.database.Reactions.Add(newReaction);
|
||||
await this.database.SaveChangesAsync();
|
||||
reaction = newReaction;
|
||||
}
|
||||
int oldRating = reaction.Rating;
|
||||
if (oldRating == rating) return this.Ok();
|
||||
|
||||
reaction.Rating = rating;
|
||||
// if rating changed then we count the number of reactions to ensure accuracy
|
||||
List<Reaction> reactions = await this.database.Reactions
|
||||
.Where(c => c.TargetId == commentId)
|
||||
.ToListAsync();
|
||||
int yay = 0;
|
||||
int boo = 0;
|
||||
foreach (Reaction r in reactions)
|
||||
{
|
||||
switch (r.Rating)
|
||||
{
|
||||
case -1:
|
||||
boo++;
|
||||
break;
|
||||
case 1:
|
||||
yay++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
comment.ThumbsDown = boo;
|
||||
comment.ThumbsUp = yay;
|
||||
await this.database.SaveChangesAsync();
|
||||
return this.Ok();
|
||||
}
|
||||
|
||||
|
||||
[HttpGet("comments/user/{slotId:int}")]
|
||||
[HttpGet("userComments/{username}")]
|
||||
public async Task<IActionResult> GetComments([FromQuery] int pageStart, [FromQuery] int pageSize, string? username, int? slotId)
|
||||
{
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
int targetId = slotId.GetValueOrDefault();
|
||||
CommentType type = CommentType.Level;
|
||||
if (!string.IsNullOrWhiteSpace(username))
|
||||
{
|
||||
targetId = this.database.Users.First(u => u.Username.Equals(username)).UserId;
|
||||
type = CommentType.Profile;
|
||||
}
|
||||
|
||||
List<Comment> comments = await this.database.Comments
|
||||
.Include(c => c.Poster)
|
||||
.Where(c => c.Target.Username == username)
|
||||
.Where(c => c.TargetId == targetId && c.Type == type)
|
||||
.OrderByDescending(c => c.Timestamp)
|
||||
.Skip(pageStart - 1)
|
||||
.Take(Math.Min(pageSize,
|
||||
30))
|
||||
.ToListAsync();
|
||||
|
||||
string outputXml = comments.Aggregate(string.Empty, (current, comment) => current + comment.Serialize());
|
||||
string outputXml = comments.Aggregate(string.Empty, (current, comment) => current +
|
||||
comment.Serialize(this.getReaction(user.UserId, comment.CommentId).Result));
|
||||
return this.Ok(LbpSerializer.StringElement("comments", outputXml));
|
||||
}
|
||||
|
||||
[HttpPost("postUserComment/{username}")]
|
||||
public async Task<IActionResult> PostComment(string username)
|
||||
public async Task<int> getReaction(int userId, int commentId)
|
||||
{
|
||||
this.Request.Body.Position = 0;
|
||||
Reaction? reaction = await this.database.Reactions.FirstOrDefaultAsync(r => r.UserId == userId && r.TargetId == commentId);
|
||||
if (reaction == null) return 0;
|
||||
return reaction.Rating;
|
||||
}
|
||||
|
||||
[HttpPost("postUserComment/{username}")]
|
||||
[HttpPost("postComment/user/{slotId:int}")]
|
||||
public async Task<IActionResult> PostComment(string? username, int? slotId)
|
||||
{
|
||||
this.Request.Body.Position = 0;
|
||||
string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync();
|
||||
|
||||
XmlSerializer serializer = new(typeof(Comment));
|
||||
Comment? comment = (Comment?)serializer.Deserialize(new StringReader(bodyString));
|
||||
Comment? comment = (Comment?) serializer.Deserialize(new StringReader(bodyString));
|
||||
|
||||
CommentType type = (slotId.GetValueOrDefault() == 0 ? CommentType.Profile : CommentType.Level);
|
||||
|
||||
User? poster = await this.database.UserFromGameRequest(this.Request);
|
||||
if (poster == null) return this.StatusCode(403, "");
|
||||
|
||||
User? target = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username);
|
||||
if (comment == null || target == null) return this.BadRequest();
|
||||
if (comment == null) return this.BadRequest();
|
||||
|
||||
int targetId = slotId.GetValueOrDefault();
|
||||
|
||||
if (type == CommentType.Profile)
|
||||
{
|
||||
User? target = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username);
|
||||
if (target == null) return this.BadRequest();
|
||||
targetId = target.UserId;
|
||||
}
|
||||
else
|
||||
{
|
||||
Slot? target = await this.database.Slots.FirstOrDefaultAsync(u => u.SlotId == slotId);
|
||||
if (target == null) return this.BadRequest();
|
||||
}
|
||||
|
||||
comment.PosterUserId = poster.UserId;
|
||||
comment.TargetUserId = target.UserId;
|
||||
comment.TargetId = targetId;
|
||||
comment.Type = type;
|
||||
|
||||
comment.Timestamp = TimeHelper.UnixTimeMilliseconds();
|
||||
|
||||
|
@ -64,17 +159,40 @@ public class CommentController : ControllerBase
|
|||
}
|
||||
|
||||
[HttpPost("deleteUserComment/{username}")]
|
||||
public async Task<IActionResult> DeleteComment([FromQuery] int commentId, string username)
|
||||
[HttpPost("deleteComment/user/{slotId:int}")]
|
||||
public async Task<IActionResult> DeleteComment([FromQuery] int commentId, string? username, int? slotId)
|
||||
{
|
||||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
Comment? comment = await this.database.Comments.FirstOrDefaultAsync(c => c.CommentId == commentId);
|
||||
if (comment == null) return this.NotFound();
|
||||
// if you are not the poster
|
||||
if (comment.PosterUserId != user.UserId)
|
||||
{
|
||||
if (comment.Type == CommentType.Profile)
|
||||
{
|
||||
// if you aren't the poster and aren't the profile owner
|
||||
if (comment.TargetId != user.UserId)
|
||||
{
|
||||
return this.StatusCode(403, "");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
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)
|
||||
{
|
||||
return this.StatusCode(403, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (comment.TargetUserId != user.UserId && comment.PosterUserId != user.UserId) return this.StatusCode(403, "");
|
||||
comment.Deleted = true;
|
||||
comment.DeletedBy = user.Username;
|
||||
comment.DeletedType = "user";
|
||||
|
||||
this.database.Comments.Remove(comment);
|
||||
await this.database.SaveChangesAsync();
|
||||
|
||||
return this.Ok();
|
||||
|
|
|
@ -74,7 +74,7 @@ public class ReviewController : ControllerBase
|
|||
this.database.RatedLevels.Add(ratedLevel);
|
||||
}
|
||||
|
||||
ratedLevel.Rating = Math.Max(Math.Min(1, rating), -1);
|
||||
ratedLevel.Rating = Math.Clamp(rating, -1, 1);
|
||||
|
||||
Review? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.SlotId == slotId && r.ReviewerId == user.UserId);
|
||||
if (review != null) review.Thumb = ratedLevel.Rating;
|
||||
|
@ -161,12 +161,12 @@ public class ReviewController : ControllerBase
|
|||
yourReview = new Review();
|
||||
yourReview.ReviewerId = user.UserId;
|
||||
yourReview.Reviewer = user;
|
||||
yourReview.Thumb = ratedLevel?.Rating == null ? 0 : ratedLevel.Rating;
|
||||
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 blank review to upload one!";
|
||||
yourReview.Text = "You haven't reviewed this level yet. Edit this to write one!";
|
||||
yourReview.LabelCollection = "";
|
||||
yourReview.Timestamp = TimeHelper.UnixTimeMilliseconds();
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ public class Database : DbContext
|
|||
public DbSet<RatedReview> RatedReviews { get; set; }
|
||||
public DbSet<UserApprovedIpAddress> UserApprovedIpAddresses { get; set; }
|
||||
public DbSet<DatabaseCategory> CustomCategories { get; set; }
|
||||
public DbSet<Reaction> Reactions { get; set; }
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder options)
|
||||
=> options.UseMySql(ServerSettings.Instance.DbConnectionString, MySqlServerVersion.LatestSupportedServerVersion);
|
||||
|
@ -272,6 +273,7 @@ public class Database : DbContext
|
|||
this.Comments.RemoveRange(this.Comments.Where(c => c.PosterUserId == user.UserId));
|
||||
this.Reviews.RemoveRange(this.Reviews.Where(r => r.ReviewerId == user.UserId));
|
||||
this.Photos.RemoveRange(this.Photos.Where(p => p.CreatorId == user.UserId));
|
||||
this.Reactions.RemoveRange(this.Reactions.Where(p => p.UserId == user.UserId));
|
||||
|
||||
this.Users.Remove(user);
|
||||
|
||||
|
|
37
ProjectLighthouse/Helpers/CaptchaHelper.cs
Normal file
37
ProjectLighthouse/Helpers/CaptchaHelper.cs
Normal file
|
@ -0,0 +1,37 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using LBPUnion.ProjectLighthouse.Types.Settings;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Helpers;
|
||||
|
||||
public static class CaptchaHelper
|
||||
{
|
||||
private static readonly HttpClient client = new()
|
||||
{
|
||||
BaseAddress = new Uri("https://hcaptcha.com"),
|
||||
};
|
||||
|
||||
public static async Task<bool> Verify(string token)
|
||||
{
|
||||
if (!ServerSettings.Instance.HCaptchaEnabled) return true;
|
||||
|
||||
List<KeyValuePair<string, string>> payload = new()
|
||||
{
|
||||
new("secret", ServerSettings.Instance.HCaptchaSecret),
|
||||
new("response", token),
|
||||
};
|
||||
|
||||
HttpResponseMessage response = await client.PostAsync("/siteverify", new FormUrlEncodedContent(payload));
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
string responseBody = await response.Content.ReadAsStringAsync();
|
||||
|
||||
// We only really care about the success result, nothing else that hcaptcha sends us, so lets only parse that.
|
||||
bool success = bool.Parse(JObject.Parse(responseBody)["success"]?.ToString() ?? "false");
|
||||
return success;
|
||||
}
|
||||
}
|
|
@ -1,5 +1,9 @@
|
|||
#nullable enable
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using LBPUnion.ProjectLighthouse.Types.Settings;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Helpers.Extensions;
|
||||
|
@ -11,4 +15,17 @@ public static class RequestExtensions
|
|||
("Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Compiled);
|
||||
|
||||
public static bool IsMobile(this HttpRequest request) => mobileCheck.IsMatch(request.Headers[HeaderNames.UserAgent].ToString());
|
||||
|
||||
public static async Task<bool> CheckCaptchaValidity(this HttpRequest request)
|
||||
{
|
||||
if (ServerSettings.Instance.HCaptchaEnabled)
|
||||
{
|
||||
bool gotCaptcha = request.Form.TryGetValue("h-captcha-response", out StringValues values);
|
||||
if (!gotCaptcha) return false;
|
||||
|
||||
if (!await CaptchaHelper.Verify(values[0])) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -21,6 +21,7 @@ public static class FileHelper
|
|||
|
||||
return file.FileType switch
|
||||
{
|
||||
LbpFileType.MotionRecording => true,
|
||||
LbpFileType.FileArchive => false,
|
||||
LbpFileType.Painting => true,
|
||||
LbpFileType.Unknown => false,
|
||||
|
@ -56,17 +57,18 @@ public static class FileHelper
|
|||
|
||||
return Encoding.ASCII.GetString(header) switch
|
||||
{
|
||||
"REC" => LbpFileType.MotionRecording,
|
||||
"PTG" => LbpFileType.Painting,
|
||||
"TEX" => LbpFileType.Texture,
|
||||
"FSH" => LbpFileType.Script,
|
||||
"VOP" => LbpFileType.Voice,
|
||||
"LVL" => LbpFileType.Level,
|
||||
"PLN" => LbpFileType.Plan,
|
||||
_ => determineFileTypePartTwoWeirdName(reader),
|
||||
_ => readAlternateHeader(reader),
|
||||
};
|
||||
}
|
||||
|
||||
private static LbpFileType determineFileTypePartTwoWeirdName(BinaryReader reader)
|
||||
private static LbpFileType readAlternateHeader(BinaryReader reader)
|
||||
{
|
||||
reader.BaseStream.Position = 0;
|
||||
|
||||
|
|
114
ProjectLighthouse/Migrations/20220205132152_CommentRefactor.cs
Normal file
114
ProjectLighthouse/Migrations/20220205132152_CommentRefactor.cs
Normal file
|
@ -0,0 +1,114 @@
|
|||
using LBPUnion.ProjectLighthouse;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ProjectLighthouse.Migrations
|
||||
{
|
||||
[DbContext(typeof(Database))]
|
||||
[Migration("20220205132152_CommentRefactor")]
|
||||
public partial class CommentRefactor : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Comments_Users_TargetUserId",
|
||||
table: "Comments");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Comments_TargetUserId",
|
||||
table: "Comments");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "TargetUserId",
|
||||
table: "Comments",
|
||||
newName: "TargetId");
|
||||
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "Deleted",
|
||||
table: "Comments",
|
||||
type: "tinyint(1)",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "DeletedBy",
|
||||
table: "Comments",
|
||||
type: "longtext",
|
||||
nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "DeletedType",
|
||||
table: "Comments",
|
||||
type: "longtext",
|
||||
nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "Type",
|
||||
table: "Comments",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Reactions",
|
||||
columns: table => new
|
||||
{
|
||||
RatingId = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||
UserId = table.Column<int>(type: "int", nullable: false),
|
||||
TargetId = table.Column<int>(type: "int", nullable: false),
|
||||
Rating = table.Column<int>(type: "int", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Reactions", x => x.RatingId);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "Reactions");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Deleted",
|
||||
table: "Comments");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "DeletedBy",
|
||||
table: "Comments");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "DeletedType",
|
||||
table: "Comments");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Type",
|
||||
table: "Comments");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "TargetId",
|
||||
table: "Comments",
|
||||
newName: "TargetUserId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Comments_TargetUserId",
|
||||
table: "Comments",
|
||||
column: "TargetUserId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Comments_Users_TargetUserId",
|
||||
table: "Comments",
|
||||
column: "TargetUserId",
|
||||
principalTable: "Users",
|
||||
principalColumn: "UserId",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -412,13 +412,22 @@ namespace ProjectLighthouse.Migrations
|
|||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<bool>("Deleted")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<string>("DeletedBy")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("DeletedType")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Message")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int>("PosterUserId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("TargetUserId")
|
||||
b.Property<int>("TargetId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("ThumbsDown")
|
||||
|
@ -430,12 +439,13 @@ namespace ProjectLighthouse.Migrations
|
|||
b.Property<long>("Timestamp")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("CommentId");
|
||||
|
||||
b.HasIndex("PosterUserId");
|
||||
|
||||
b.HasIndex("TargetUserId");
|
||||
|
||||
b.ToTable("Comments");
|
||||
});
|
||||
|
||||
|
@ -473,6 +483,26 @@ namespace ProjectLighthouse.Migrations
|
|||
b.ToTable("Locations");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Reaction", b =>
|
||||
{
|
||||
b.Property<int>("RatingId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("Rating")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("TargetId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("RatingId");
|
||||
|
||||
b.ToTable("Reactions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Reviews.RatedReview", b =>
|
||||
{
|
||||
b.Property<int>("RatedReviewId")
|
||||
|
@ -829,15 +859,7 @@ namespace ProjectLighthouse.Migrations
|
|||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "Target")
|
||||
.WithMany()
|
||||
.HasForeignKey("TargetUserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Poster");
|
||||
|
||||
b.Navigation("Target");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Reviews.RatedReview", b =>
|
||||
|
|
|
@ -35,19 +35,24 @@ else
|
|||
<div class="ui blue segment">
|
||||
<h3>@command.Name()</h3>
|
||||
<form>
|
||||
<input type="text" name="command" style="display: none;" value="@command.FirstAlias">
|
||||
@if (command.RequiredArgs() > 0)
|
||||
{
|
||||
<div class="ui input" style="width: @(Model.Request.IsMobile() ? 100 : 30)%;">
|
||||
<div class="ui left action input" style="width: 100%">
|
||||
<button type="submit" class="ui green button">
|
||||
<i class="play icon"></i>
|
||||
Execute
|
||||
</button>
|
||||
<input type="text" name="args" placeholder="@command.Arguments()">
|
||||
</div>
|
||||
<br>
|
||||
<br>
|
||||
}
|
||||
<input type="text" name="command" style="display: none;" value="@command.FirstAlias">
|
||||
<button type="submit" class="ui green button">
|
||||
<i class="play icon"></i>
|
||||
Execute
|
||||
</button>
|
||||
else
|
||||
{
|
||||
<button type="submit" class="ui green button">
|
||||
<i class="play icon"></i>
|
||||
Execute
|
||||
</button>
|
||||
}
|
||||
</form>
|
||||
</div>
|
||||
}
|
||||
|
|
|
@ -74,17 +74,6 @@
|
|||
gtag('config', '@ServerSettings.Instance.GoogleAnalyticsId');
|
||||
</script>
|
||||
}
|
||||
|
||||
<style>
|
||||
canvas.photo-subjects {
|
||||
opacity: 1;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
canvas.hide-subjects {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="pageContainer">
|
||||
|
|
|
@ -50,6 +50,11 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
@if (ServerSettings.Instance.HCaptchaEnabled)
|
||||
{
|
||||
@await Html.PartialAsync("Partials/CaptchaPartial")
|
||||
}
|
||||
|
||||
<input type="submit" value="Log in" id="submit" class="ui blue button">
|
||||
@if (ServerSettings.Instance.RegistrationEnabled)
|
||||
{
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
#nullable enable
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
using Kettu;
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Helpers.Extensions;
|
||||
using LBPUnion.ProjectLighthouse.Logging;
|
||||
using LBPUnion.ProjectLighthouse.Pages.Layouts;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
|
@ -18,8 +21,6 @@ public class LoginForm : BaseLayout
|
|||
|
||||
public string Error { get; private set; }
|
||||
|
||||
public bool WasLoginRequest { get; private set; }
|
||||
|
||||
[UsedImplicitly]
|
||||
public async Task<IActionResult> OnPost(string username, string password)
|
||||
{
|
||||
|
@ -35,6 +36,12 @@ public class LoginForm : BaseLayout
|
|||
return this.Page();
|
||||
}
|
||||
|
||||
if (!await Request.CheckCaptchaValidity())
|
||||
{
|
||||
this.Error = "You must complete the captcha correctly.";
|
||||
return this.Page();
|
||||
}
|
||||
|
||||
User? user = await this.Database.Users.FirstOrDefaultAsync(u => u.Username == username);
|
||||
if (user == null)
|
||||
{
|
||||
|
@ -66,7 +73,17 @@ public class LoginForm : BaseLayout
|
|||
this.Database.WebTokens.Add(webToken);
|
||||
await this.Database.SaveChangesAsync();
|
||||
|
||||
this.Response.Cookies.Append("LighthouseToken", webToken.UserToken);
|
||||
this.Response.Cookies.Append
|
||||
(
|
||||
"LighthouseToken",
|
||||
webToken.UserToken,
|
||||
new CookieOptions
|
||||
{
|
||||
Expires = DateTimeOffset.Now.AddDays(7),
|
||||
}
|
||||
);
|
||||
|
||||
Logger.Log($"User {user.Username} (id: {user.UserId}) successfully logged in on web", LoggerLevelLogin.Instance);
|
||||
|
||||
if (user.PasswordResetRequired) return this.Redirect("~/passwordResetRequired");
|
||||
|
||||
|
|
6
ProjectLighthouse/Pages/Partials/CaptchaPartial.cshtml
Normal file
6
ProjectLighthouse/Pages/Partials/CaptchaPartial.cshtml
Normal file
|
@ -0,0 +1,6 @@
|
|||
@using LBPUnion.ProjectLighthouse.Types.Settings
|
||||
@if (ServerSettings.Instance.HCaptchaEnabled)
|
||||
{
|
||||
<div class="h-captcha" data-sitekey="@ServerSettings.Instance.HCaptchaSiteKey"></div>
|
||||
<script src="https://js.hcaptcha.com/1/api.js" async defer></script>
|
||||
}
|
|
@ -32,7 +32,9 @@
|
|||
@{
|
||||
int size = isMobile ? 50 : 100;
|
||||
}
|
||||
<div class="cardIcon slotCardIcon" style="background-image: url('/gameAssets/@iconHash'); min-width: @(size)px; width: @(size)px; height: @(size)px">
|
||||
<div>
|
||||
<img src="~/assets/slotCardOverlay.png" style="min-width: @(size)px; width: @(size)px; height: @(size)px; pointer-events: none; position: absolute">
|
||||
<img class="cardIcon slotCardIcon" src="/gameAssets/@iconHash" style="min-width: @(size)px; width: @(size)px; height: @(size)px">
|
||||
</div>
|
||||
<div class="cardStats">
|
||||
@if (showLink)
|
||||
|
|
|
@ -10,11 +10,13 @@
|
|||
|
||||
<script>
|
||||
function onSubmit(form) {
|
||||
const password = form['password'];
|
||||
const confirmPassword = form['confirmPassword'];
|
||||
const passwordInput = document.getElementById("password");
|
||||
const confirmPasswordInput = document.getElementById("confirmPassword");
|
||||
const passwordSubmit = document.getElementById("password-submit");
|
||||
const confirmPasswordSubmit = document.getElementById("confirmPassword-submit");
|
||||
|
||||
password.value = sha256(password.value);
|
||||
confirmPassword.value = sha256(confirmPassword.value);
|
||||
passwordSubmit.value = sha256(passwordInput.value);
|
||||
confirmPasswordSubmit.value = sha256(confirmPasswordInput.value);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -35,12 +37,14 @@
|
|||
|
||||
<div class="ui left labeled input">
|
||||
<label for="password" class="ui blue label">Password: </label>
|
||||
<input type="password" name="password" id="password">
|
||||
<input type="password" id="password">
|
||||
<input type="hidden" id="password-submit" name="password">
|
||||
</div><br><br>
|
||||
|
||||
<div class="ui left labeled input">
|
||||
<label for="password" class="ui blue label">Confirm Password: </label>
|
||||
<input type="password" name="confirmPassword" id="confirmPassword">
|
||||
<input type="password" id="confirmPassword">
|
||||
<input type="hidden" id="confirmPassword-submit" name="confirmPassword">
|
||||
</div><br><br><br>
|
||||
|
||||
<input type="submit" value="Reset password and continue" id="submit" class="ui green button"><br>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
@page "/register"
|
||||
@using LBPUnion.ProjectLighthouse.Types.Settings
|
||||
@model LBPUnion.ProjectLighthouse.Pages.RegisterForm
|
||||
|
||||
@{
|
||||
|
@ -60,5 +61,10 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
@if (ServerSettings.Instance.HCaptchaEnabled)
|
||||
{
|
||||
@await Html.PartialAsync("Partials/CaptchaPartial")
|
||||
}
|
||||
|
||||
<input type="submit" value="Register" id="submit" class="ui green button">
|
||||
</form>
|
|
@ -2,6 +2,7 @@ using System.Diagnostics.CodeAnalysis;
|
|||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Helpers.Extensions;
|
||||
using LBPUnion.ProjectLighthouse.Pages.Layouts;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using LBPUnion.ProjectLighthouse.Types.Settings;
|
||||
|
@ -42,13 +43,18 @@ public class RegisterForm : BaseLayout
|
|||
return this.Page();
|
||||
}
|
||||
|
||||
bool userExists = await this.Database.Users.FirstOrDefaultAsync(u => u.Username.ToLower() == username.ToLower()) != null;
|
||||
if (userExists)
|
||||
if (await this.Database.Users.FirstOrDefaultAsync(u => u.Username.ToLower() == username.ToLower()) != null)
|
||||
{
|
||||
this.Error = "The username you've chosen is already taken.";
|
||||
return this.Page();
|
||||
}
|
||||
|
||||
if (!await Request.CheckCaptchaValidity())
|
||||
{
|
||||
this.Error = "You must complete the captcha correctly.";
|
||||
return this.Page();
|
||||
}
|
||||
|
||||
User user = await this.Database.CreateUser(username, HashHelper.BCryptHash(password));
|
||||
|
||||
WebToken webToken = new()
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
@page "/slot/{id:int}"
|
||||
@using System.IO
|
||||
@using System.Web
|
||||
@using LBPUnion.ProjectLighthouse.Helpers.Extensions
|
||||
@using LBPUnion.ProjectLighthouse.Types.Profiles
|
||||
@model LBPUnion.ProjectLighthouse.Pages.SlotPage
|
||||
|
||||
@{
|
||||
|
@ -54,6 +57,36 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui yellow segment">
|
||||
<h1>Comments</h1>
|
||||
@if (Model.Comments.Count == 0)
|
||||
{
|
||||
<p>There are no comments.</p>
|
||||
}
|
||||
|
||||
@foreach (Comment comment in Model.Comments!)
|
||||
{
|
||||
DateTimeOffset timestamp = DateTimeOffset.FromUnixTimeSeconds(comment.Timestamp / 1000);
|
||||
StringWriter messageWriter = new();
|
||||
HttpUtility.HtmlDecode(comment.getComment(), messageWriter);
|
||||
string decodedMessage = messageWriter.ToString();
|
||||
<div>
|
||||
<b><a href="/user/@comment.PosterUserId">@comment.Poster.Username</a>: </b>
|
||||
@if (comment.Deleted)
|
||||
{
|
||||
<i><span>@decodedMessage</span></i>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span>@decodedMessage</span>
|
||||
}
|
||||
<p>
|
||||
<i>@timestamp.ToString("MM/dd/yyyy @ h:mm tt") UTC</i>
|
||||
</p>
|
||||
<div class="ui divider"></div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@if (Model.User != null && Model.User.IsAdmin)
|
||||
{
|
||||
<div class="ui yellow segment">
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
using LBPUnion.ProjectLighthouse.Pages.Layouts;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using LBPUnion.ProjectLighthouse.Types.Levels;
|
||||
using LBPUnion.ProjectLighthouse.Types.Profiles;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
|
@ -10,6 +14,7 @@ namespace LBPUnion.ProjectLighthouse.Pages;
|
|||
|
||||
public class SlotPage : BaseLayout
|
||||
{
|
||||
public List<Comment> Comments;
|
||||
|
||||
public Slot Slot;
|
||||
public SlotPage([NotNull] Database database) : base(database)
|
||||
|
@ -20,6 +25,12 @@ public class SlotPage : BaseLayout
|
|||
Slot? slot = await this.Database.Slots.Include(s => s.Creator).FirstOrDefaultAsync(s => s.SlotId == id);
|
||||
if (slot == null) return this.NotFound();
|
||||
|
||||
this.Comments = await this.Database.Comments.Include(p => p.Poster)
|
||||
.OrderByDescending(p => p.Timestamp)
|
||||
.Where(c => c.TargetId == id && c.Type == CommentType.Level)
|
||||
.Take(50)
|
||||
.ToListAsync();
|
||||
|
||||
this.Slot = slot;
|
||||
|
||||
return this.Page();
|
||||
|
|
|
@ -126,11 +126,19 @@
|
|||
{
|
||||
DateTimeOffset timestamp = DateTimeOffset.FromUnixTimeSeconds(comment.Timestamp / 1000);
|
||||
StringWriter messageWriter = new();
|
||||
HttpUtility.HtmlDecode(comment.Message, messageWriter);
|
||||
HttpUtility.HtmlDecode(comment.getComment(), messageWriter);
|
||||
string decodedMessage = messageWriter.ToString();
|
||||
<div>
|
||||
<b><a href="/user/@comment.PosterUserId">@comment.Poster.Username</a>: </b>
|
||||
<span>@decodedMessage</span>
|
||||
@if (comment.Deleted)
|
||||
{
|
||||
<i><span>@decodedMessage</span></i>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span>@decodedMessage</span>
|
||||
}
|
||||
|
||||
<p>
|
||||
<i>@timestamp.ToString("MM/dd/yyyy @ h:mm tt") UTC</i>
|
||||
</p>
|
||||
|
|
|
@ -30,9 +30,8 @@ public class UserPage : BaseLayout
|
|||
this.Photos = await this.Database.Photos.OrderByDescending(p => p.Timestamp).Where(p => p.CreatorId == userId).Take(6).ToListAsync();
|
||||
this.Comments = await this.Database.Comments.Include
|
||||
(p => p.Poster)
|
||||
.Include(p => p.Target)
|
||||
.OrderByDescending(p => p.Timestamp)
|
||||
.Where(p => p.TargetUserId == userId)
|
||||
.Where(p => p.TargetId == userId && p.Type == CommentType.Profile)
|
||||
.Take(50)
|
||||
.ToListAsync();
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
|
@ -15,18 +14,18 @@
|
|||
<ItemGroup>
|
||||
<PackageReference Include="BCrypt.Net-Next" Version="4.0.2"/>
|
||||
<PackageReference Include="DDSReader" Version="1.0.8-pre"/>
|
||||
<PackageReference Include="Discord.Net.Webhook" Version="3.2.0"/>
|
||||
<PackageReference Include="InfluxDB.Client" Version="3.2.0"/>
|
||||
<PackageReference Include="Discord.Net.Webhook" Version="3.3.0"/>
|
||||
<PackageReference Include="InfluxDB.Client" Version="3.3.0"/>
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2021.3.0"/>
|
||||
<PackageReference Include="Kettu" Version="1.2.1"/>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="6.0.0"/>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="6.0.0"/>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.0"/>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.0">
|
||||
<PackageReference Include="Kettu" Version="1.2.2"/>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="6.0.2"/>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="6.0.2"/>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.2"/>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="6.0.0"/>
|
||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="6.0.1"/>
|
||||
<PackageReference Include="SharpZipLib" Version="1.3.3"/>
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3"/>
|
||||
</ItemGroup>
|
||||
|
@ -48,25 +47,7 @@
|
|||
<EmbeddedResource Include="gitUnpushed.txt">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</EmbeddedResource>
|
||||
<None Remove="StaticFiles\css\themes\default\assets\fonts\outline-icons.woff2"/>
|
||||
<None Remove="StaticFiles\css\themes\default\assets\fonts\outline-icons.woff"/>
|
||||
<None Remove="StaticFiles\css\themes\default\assets\fonts\outline-icons.ttf"/>
|
||||
<None Remove="StaticFiles\css\themes\default\assets\fonts\outline-icons.svg"/>
|
||||
<None Remove="StaticFiles\css\themes\default\assets\fonts\outline-icons.eot"/>
|
||||
<None Remove="StaticFiles\css\themes\default\assets\fonts\icons.woff2"/>
|
||||
<None Remove="StaticFiles\css\themes\default\assets\fonts\icons.woff"/>
|
||||
<None Remove="StaticFiles\css\themes\default\assets\fonts\icons.ttf"/>
|
||||
<None Remove="StaticFiles\css\themes\default\assets\fonts\icons.svg"/>
|
||||
<None Remove="StaticFiles\css\themes\default\assets\fonts\icons.eot"/>
|
||||
<None Remove="StaticFiles\css\themes\default\assets\fonts\brand-icons.woff2"/>
|
||||
<None Remove="StaticFiles\css\themes\default\assets\fonts\brand-icons.woff"/>
|
||||
<None Remove="StaticFiles\css\themes\default\assets\fonts\brand-icons.ttf"/>
|
||||
<None Remove="StaticFiles\css\themes\default\assets\fonts\brand-icons.svg"/>
|
||||
<None Remove="StaticFiles\css\semantic.min.css"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="StaticFiles\css\themes\default\assets\fonts"/>
|
||||
<None Remove="recent-activity.xml"/>
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
|
||||
|
@ -75,5 +56,4 @@
|
|||
<Exec Command="git remote -v > "$(ProjectDir)/gitRemotes.txt""/>
|
||||
<Exec Command="git log --branches --not --remotes --oneline > "$(ProjectDir)/gitUnpushed.txt""/>
|
||||
</Target>
|
||||
|
||||
</Project>
|
||||
|
|
BIN
ProjectLighthouse/StaticFiles/assets/slotCardOverlay.png
Normal file
BIN
ProjectLighthouse/StaticFiles/assets/slotCardOverlay.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
|
@ -12,6 +12,17 @@ div.main {
|
|||
margin-bottom: 1px;
|
||||
}
|
||||
|
||||
canvas.photo-subjects {
|
||||
opacity: 1;
|
||||
transition: opacity 0.3s;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
canvas.hide-subjects {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/*#region Cards*/
|
||||
|
||||
.card {
|
||||
|
@ -24,6 +35,7 @@ div.main {
|
|||
background-size: cover;
|
||||
background-position: center;
|
||||
border-radius: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.cardStats {
|
||||
|
|
7
ProjectLighthouse/Types/CommentType.cs
Normal file
7
ProjectLighthouse/Types/CommentType.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
namespace LBPUnion.ProjectLighthouse.Types;
|
||||
|
||||
public enum CommentType
|
||||
{
|
||||
Profile = 0,
|
||||
Level = 1,
|
||||
}
|
|
@ -8,6 +8,7 @@ public enum LbpFileType
|
|||
FileArchive, // .farc, (ends with FARC)
|
||||
Plan, // PLN, uploaded with levels
|
||||
Voice, // VOP, voice data
|
||||
MotionRecording, // used in LBP2+/V for the motion recorder
|
||||
Painting, // PTG, paintings
|
||||
Jpeg, // JFIF / FIF, used in sticker switches,
|
||||
Png, // used in LBP Vita
|
||||
|
|
|
@ -8,6 +8,7 @@ using LBPUnion.ProjectLighthouse.Helpers;
|
|||
using LBPUnion.ProjectLighthouse.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Types.Profiles;
|
||||
using LBPUnion.ProjectLighthouse.Types.Reviews;
|
||||
using LBPUnion.ProjectLighthouse.Types.Settings;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Types.Levels;
|
||||
|
||||
|
@ -121,6 +122,19 @@ public class Slot
|
|||
}
|
||||
}
|
||||
|
||||
[XmlIgnore]
|
||||
[NotMapped]
|
||||
[JsonIgnore]
|
||||
public int Comments
|
||||
{
|
||||
get
|
||||
{
|
||||
using Database database = new();
|
||||
|
||||
return database.Comments.Count(c => c.Type == CommentType.Level && c.TargetId == this.SlotId);
|
||||
}
|
||||
}
|
||||
|
||||
[XmlIgnore]
|
||||
[NotMapped]
|
||||
public int Plays => this.PlaysLBP1 + this.PlaysLBP2 + this.PlaysLBP3 + this.PlaysLBPVita;
|
||||
|
@ -251,6 +265,7 @@ public class Slot
|
|||
LbpSerializer.StringElement("icon", this.IconHash) +
|
||||
LbpSerializer.StringElement("rootLevel", this.RootLevel) +
|
||||
LbpSerializer.StringElement("authorLabels", this.AuthorLabels) +
|
||||
LbpSerializer.StringElement("labels", this.AuthorLabels) +
|
||||
this.SerializeResources() +
|
||||
LbpSerializer.StringElement("location", this.Location?.Serialize()) +
|
||||
LbpSerializer.StringElement("initiallyLocked", this.InitiallyLocked) +
|
||||
|
@ -266,6 +281,7 @@ public class Slot
|
|||
LbpSerializer.StringElement("mmpick", this.TeamPick) +
|
||||
LbpSerializer.StringElement("heartCount", this.Hearts) +
|
||||
LbpSerializer.StringElement("playCount", this.Plays) +
|
||||
LbpSerializer.StringElement("commentCount", this.Comments) +
|
||||
LbpSerializer.StringElement("uniquePlayCount", this.PlaysLBP2Unique) + // ??? good naming scheme lol
|
||||
LbpSerializer.StringElement("completionCount", this.PlaysComplete) +
|
||||
LbpSerializer.StringElement("lbp1PlayCount", this.PlaysLBP1) +
|
||||
|
@ -277,7 +293,7 @@ public class Slot
|
|||
LbpSerializer.StringElement("lbp3PlayCount", this.PlaysLBP3) +
|
||||
LbpSerializer.StringElement("lbp3CompletionCount", this.PlaysLBP3Complete) +
|
||||
LbpSerializer.StringElement("lbp3UniquePlayCount", this.PlaysLBP3Unique) +
|
||||
LbpSerializer.StringElement("vitaCrossControlRequired", CrossControllerRequired) +
|
||||
LbpSerializer.StringElement("vitaCrossControlRequired", this.CrossControllerRequired) +
|
||||
LbpSerializer.StringElement("thumbsup", this.Thumbsup) +
|
||||
LbpSerializer.StringElement("thumbsdown", this.Thumbsdown) +
|
||||
LbpSerializer.StringElement("averageRating", this.RatingLBP1) +
|
||||
|
@ -290,8 +306,8 @@ public class Slot
|
|||
LbpSerializer.StringElement
|
||||
("yourLBPVitaPlayCount", yourVisitedStats?.PlaysLBPVita) + // i doubt this is the right name but we'll go with it
|
||||
yourReview?.Serialize("yourReview") +
|
||||
LbpSerializer.StringElement("reviewsEnabled", true) +
|
||||
LbpSerializer.StringElement("commentsEnabled", false) +
|
||||
LbpSerializer.StringElement("reviewsEnabled", ServerSettings.Instance.LevelReviewsEnabled) +
|
||||
LbpSerializer.StringElement("commentsEnabled", ServerSettings.Instance.LevelCommentsEnabled) +
|
||||
LbpSerializer.StringElement("reviewCount", this.ReviewCount);
|
||||
|
||||
return LbpSerializer.TaggedStringElement("slot", slotData, "type", "user");
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Linq;
|
||||
using System.Xml.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Serialization;
|
||||
|
||||
|
@ -15,27 +16,58 @@ public class Comment
|
|||
|
||||
public int PosterUserId { get; set; }
|
||||
|
||||
public int TargetUserId { get; set; }
|
||||
public int TargetId { get; set; }
|
||||
|
||||
[ForeignKey(nameof(PosterUserId))]
|
||||
public User Poster { get; set; }
|
||||
|
||||
[ForeignKey(nameof(TargetUserId))]
|
||||
public User Target { get; set; }
|
||||
public bool Deleted { get; set; }
|
||||
|
||||
public string DeletedType { get; set; }
|
||||
|
||||
public string DeletedBy { get; set; }
|
||||
|
||||
public long Timestamp { get; set; }
|
||||
|
||||
[XmlElement("message")]
|
||||
public string Message { get; set; }
|
||||
|
||||
public CommentType Type { get; set; }
|
||||
|
||||
public int ThumbsUp { get; set; }
|
||||
public int ThumbsDown { get; set; }
|
||||
|
||||
public string getComment()
|
||||
{
|
||||
if (!this.Deleted)
|
||||
{
|
||||
return this.Message;
|
||||
}
|
||||
|
||||
if (this.DeletedBy == this.Poster.Username)
|
||||
{
|
||||
return "This comment has been deleted by the author.";
|
||||
}
|
||||
|
||||
using Database database = new();
|
||||
User deletedBy = database.Users.FirstOrDefault(u => u.Username == this.DeletedBy);
|
||||
|
||||
if (deletedBy != null && deletedBy.UserId == this.TargetId)
|
||||
{
|
||||
return "This comment has been deleted by the player.";
|
||||
}
|
||||
|
||||
return "This comment has been deleted.";
|
||||
}
|
||||
|
||||
private string serialize()
|
||||
=> LbpSerializer.StringElement("id", this.CommentId) +
|
||||
LbpSerializer.StringElement("npHandle", this.Poster.Username) +
|
||||
LbpSerializer.StringElement("timestamp", this.Timestamp) +
|
||||
LbpSerializer.StringElement("message", this.Message) +
|
||||
(this.Deleted ? LbpSerializer.StringElement("deleted", true) +
|
||||
LbpSerializer.StringElement("deletedBy", this.DeletedBy) +
|
||||
LbpSerializer.StringElement("deletedType", this.DeletedBy) : "") +
|
||||
LbpSerializer.StringElement("thumbsup", this.ThumbsUp) +
|
||||
LbpSerializer.StringElement("thumbsdown", this.ThumbsDown);
|
||||
|
||||
|
|
16
ProjectLighthouse/Types/Reaction.cs
Normal file
16
ProjectLighthouse/Types/Reaction.cs
Normal file
|
@ -0,0 +1,16 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Types;
|
||||
|
||||
public class Reaction
|
||||
{
|
||||
[Key]
|
||||
public int RatingId { get; set; }
|
||||
|
||||
public int UserId { get; set; }
|
||||
|
||||
public int TargetId { get; set; }
|
||||
|
||||
public int Rating { get; set; }
|
||||
|
||||
}
|
|
@ -5,15 +5,17 @@ using System.Text.Json;
|
|||
using System.Text.Json.Serialization;
|
||||
using JetBrains.Annotations;
|
||||
using Kettu;
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Logging;
|
||||
#if RELEASE
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
#endif
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Types.Settings;
|
||||
|
||||
[Serializable]
|
||||
public class ServerSettings
|
||||
{
|
||||
public const int CurrentConfigVersion = 18; // MUST BE INCREMENTED FOR EVERY CONFIG CHANGE!
|
||||
public const int CurrentConfigVersion = 20; // MUST BE INCREMENTED FOR EVERY CONFIG CHANGE!
|
||||
private static FileSystemWatcher fileWatcher;
|
||||
static ServerSettings()
|
||||
{
|
||||
|
@ -132,6 +134,12 @@ public class ServerSettings
|
|||
|
||||
public int PhotosQuota { get; set; } = 500;
|
||||
|
||||
public bool ProfileCommentsEnabled { get; set; } = true;
|
||||
|
||||
public bool LevelCommentsEnabled { get; set; } = true;
|
||||
|
||||
public bool LevelReviewsEnabled { get; set; } = true;
|
||||
|
||||
public bool GoogleAnalyticsEnabled { get; set; }
|
||||
|
||||
public string GoogleAnalyticsId { get; set; } = "";
|
||||
|
@ -150,6 +158,12 @@ public class ServerSettings
|
|||
|
||||
public string MissingIconHash { get; set; } = "";
|
||||
|
||||
public bool HCaptchaEnabled { get; set; }
|
||||
|
||||
public string HCaptchaSiteKey { get; set; } = "";
|
||||
|
||||
public string HCaptchaSecret { get; set; } = "";
|
||||
|
||||
#region Meta
|
||||
|
||||
[NotNull]
|
||||
|
|
|
@ -61,7 +61,7 @@ public class User
|
|||
public int Comments {
|
||||
get {
|
||||
using Database database = new();
|
||||
return database.Comments.Count(c => c.TargetUserId == this.UserId);
|
||||
return database.Comments.Count(c => c.Type == CommentType.Profile && c.TargetId == this.UserId);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -180,7 +180,7 @@ public class User
|
|||
LbpSerializer.StringElement("commentCount", this.Comments) +
|
||||
LbpSerializer.StringElement("photosByMeCount", this.PhotosByMe) +
|
||||
LbpSerializer.StringElement("photosWithMeCount", this.PhotosWithMe) +
|
||||
LbpSerializer.StringElement("commentsEnabled", "true") +
|
||||
LbpSerializer.StringElement("commentsEnabled", ServerSettings.Instance.ProfileCommentsEnabled) +
|
||||
LbpSerializer.StringElement("location", this.Location.Serialize()) +
|
||||
LbpSerializer.StringElement("favouriteSlotCount", this.HeartedLevels) +
|
||||
LbpSerializer.StringElement("favouriteUserCount", this.HeartedUsers) +
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue