Profile Blocking (#662)

* Added blocked user DB object

* Added user blocking functions

* Fixed DB Migration

* Updated DB Functions

* Added blocked user support to website

* Fixed DB Migration

* I forgot to save 🫠

* More migration pain

* Fixed Unblock label

* Update ProjectLighthouse.Servers.Website/Pages/UserPage.cshtml

sounds cool

Co-authored-by: koko <68549366+sudokoko@users.noreply.github.com>

* Removed unnecessary imports in database

* Removed unnecessary  imports in UserPage.cshtml.cs

* Made comments in-game respect blocked users

* Update ProjectLighthouse/Database.cs

Co-authored-by: Josh <josh@slendy.pw>

* Update ProjectLighthouse/Database.cs

Co-authored-by: Josh <josh@slendy.pw>

* DB Code cleanup

* Cleaned up userPage block detection code

* Get only the creator id in lieu of the whole object

* Fixed null condition when not logged in

* Fixed null condition when not logged in

* Potential DB Optimisation

* Apply suggestions from code review

Co-authored-by: Josh <josh@slendy.pw>

* Fix errors and null warning

* Use explicit type in lieu of var

* changed block icons

* Optimize blocked user check and save changes when unblocking

---------

Co-authored-by: koko <68549366+sudokoko@users.noreply.github.com>
Co-authored-by: Josh <josh@slendy.pw>
This commit is contained in:
Zaprit 2023-02-11 08:25:06 +00:00 committed by GitHub
parent b4326d4798
commit 3fcfaaf5cc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 227 additions and 8 deletions

View file

@ -71,9 +71,15 @@ public class CommentController : ControllerBase
if (targetId == 0) return this.NotFound(); if (targetId == 0) return this.NotFound();
List<Comment> comments = await this.database.Comments.Include List<int> blockedUsers = await (
(c => c.Poster) from blockedProfile in this.database.BlockedProfiles
.Where(c => c.TargetId == targetId && c.Type == type) where blockedProfile.UserId == token.UserId
select blockedProfile.BlockedUserId
).ToListAsync();
List<Comment> comments = await this.database.Comments.Include(c => c.Poster)
.Where(c => c.TargetId == targetId && c.Type == type && !c.Poster.IsBanned)
.Where(c => !blockedUsers.Contains(c.PosterUserId))
.OrderByDescending(c => c.Timestamp) .OrderByDescending(c => c.Timestamp)
.Skip(Math.Max(0, pageStart - 1)) .Skip(Math.Max(0, pageStart - 1))
.Take(Math.Min(pageSize, 30)) .Take(Math.Min(pageSize, 30))

View file

@ -87,4 +87,32 @@ public class UserPageController : ControllerBase
return this.Redirect("~/user/" + id); return this.Redirect("~/user/" + id);
} }
[HttpGet("block")]
public async Task<IActionResult> BlockUser([FromRoute] int id)
{
WebToken? token = this.database.WebTokenFromRequest(this.Request);
if (token == null) return this.Redirect("~/login");
User? blockedUser = await this.database.Users.FirstOrDefaultAsync(u => u.UserId == id);
if (blockedUser == null) return this.NotFound();
await this.database.BlockUser(token.UserId, blockedUser);
return this.Redirect("~/user/" + id);
}
[HttpGet("unblock")]
public async Task<IActionResult> UnblockUser([FromRoute] int id)
{
WebToken? token = this.database.WebTokenFromRequest(this.Request);
if (token == null) return this.Redirect("~/login");
User? blockedUser = await this.database.Users.FirstOrDefaultAsync(u => u.UserId == id);
if (blockedUser == null) return this.NotFound();
await this.database.UnblockUser(token.UserId, blockedUser);
return this.Redirect("~/user/" + id);
}
} }

View file

@ -61,6 +61,20 @@
<br> <br>
@if (Model.ProfileUser != Model.User && Model.User != null) @if (Model.ProfileUser != Model.User && Model.User != null)
{ {
if (!Model.IsProfileUserBlocked)
{
<a class="ui red button" href="/user/@Model.ProfileUser.UserId/block">
<i class="user alternate slash icon"></i>
<span>Block</span>
</a>
}
else
{
<a class="ui black button" href="/user/@Model.ProfileUser.UserId/unblock">
<i class="user alternate icon"></i>
<span>Unblock</span>
</a>
}
if (!Model.IsProfileUserHearted) if (!Model.IsProfileUserHearted)
{ {
<a class="ui pink button" href="/user/@Model.ProfileUser.UserId/heart"> <a class="ui pink button" href="/user/@Model.ProfileUser.UserId/heart">

View file

@ -18,6 +18,8 @@ public class UserPage : BaseLayout
public bool IsProfileUserHearted; public bool IsProfileUserHearted;
public bool IsProfileUserBlocked;
public List<Photo>? Photos; public List<Photo>? Photos;
public List<Slot>? Slots; public List<Slot>? Slots;
@ -86,11 +88,18 @@ public class UserPage : BaseLayout
} }
this.CommentsEnabled = ServerConfiguration.Instance.UserGeneratedContentLimits.LevelCommentsEnabled && this.ProfileUser.CommentsEnabled; this.CommentsEnabled = ServerConfiguration.Instance.UserGeneratedContentLimits.LevelCommentsEnabled && this.ProfileUser.CommentsEnabled;
if (this.CommentsEnabled) if (this.CommentsEnabled)
{ {
List<int> blockedUsers = this.User == null ? new List<int>() : await
(from blockedProfile in this.Database.BlockedProfiles
where blockedProfile.UserId == this.User.UserId
select blockedProfile.BlockedUserId).ToListAsync();
this.Comments = await this.Database.Comments.Include(p => p.Poster) this.Comments = await this.Database.Comments.Include(p => p.Poster)
.OrderByDescending(p => p.Timestamp) .OrderByDescending(p => p.Timestamp)
.Where(p => p.TargetId == userId && p.Type == CommentType.Profile) .Where(p => p.TargetId == userId && p.Type == CommentType.Profile)
.Where(p => !blockedUsers.Contains(p.PosterUserId))
.Take(50) .Take(50)
.ToListAsync(); .ToListAsync();
} }
@ -114,6 +123,8 @@ public class UserPage : BaseLayout
.Where(h => h.UserId == this.User.UserId) .Where(h => h.UserId == this.User.UserId)
.AnyAsync(); .AnyAsync();
this.IsProfileUserBlocked = await this.Database.IsUserBlockedBy(this.ProfileUser.UserId, this.User.UserId);
return this.Page(); return this.Page();
} }
} }

View file

@ -53,6 +53,7 @@ public class Database : DbContext
public DbSet<APIKey> APIKeys { get; set; } public DbSet<APIKey> APIKeys { get; set; }
public DbSet<Playlist> Playlists { get; set; } public DbSet<Playlist> Playlists { get; set; }
public DbSet<PlatformLinkAttempt> PlatformLinkAttempts { get; set; } public DbSet<PlatformLinkAttempt> PlatformLinkAttempts { get; set; }
public DbSet<BlockedProfile> BlockedProfiles { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder options) protected override void OnConfiguring(DbContextOptionsBuilder options)
=> options.UseMySql(ServerConfiguration.Instance.DbConnectionString, MySqlServerVersion.LatestSupportedServerVersion); => options.UseMySql(ServerConfiguration.Instance.DbConnectionString, MySqlServerVersion.LatestSupportedServerVersion);
@ -131,9 +132,10 @@ public class Database : DbContext
Comment? comment = await this.Comments.FirstOrDefaultAsync(c => commentId == c.CommentId); Comment? comment = await this.Comments.FirstOrDefaultAsync(c => commentId == c.CommentId);
if (comment == null) return false; if (comment == null) return false;
if (comment.PosterUserId == userId) return false; if (comment.PosterUserId == userId) return false;
if (await this.IsUserBlockedBy(userId, comment.PosterUserId)) return false;
Reaction? reaction = await this.Reactions.FirstOrDefaultAsync(r => r.UserId == userId && r.TargetId == commentId); Reaction? reaction = await this.Reactions.FirstOrDefaultAsync(r => r.UserId == userId && r.TargetId == commentId);
if (reaction == null) if (reaction == null)
{ {
@ -189,14 +191,17 @@ public class Database : DbContext
.Select(u => u.UserId) .Select(u => u.UserId)
.FirstOrDefaultAsync(); .FirstOrDefaultAsync();
if (targetUserId == 0) return false; if (targetUserId == 0) return false;
if (await this.IsUserBlockedBy(userId, targetUserId)) return false;
} }
else else
{ {
int targetSlotId = await this.Slots.Where(s => s.SlotId == targetId) int creatorId = await this.Slots.Where(s => s.SlotId == targetId)
.Where(s => s.CommentsEnabled && !s.Hidden) .Where(s => s.CommentsEnabled && !s.Hidden)
.Select(s => s.SlotId) .Select(s => s.CreatorId)
.FirstOrDefaultAsync(); .FirstOrDefaultAsync();
if (targetSlotId == 0) return false; if (creatorId == 0) return false;
if (await this.IsUserBlockedBy(userId, creatorId)) return false;
} }
this.Comments.Add this.Comments.Add
@ -312,6 +317,39 @@ public class Database : DbContext
await this.SaveChangesAsync(); await this.SaveChangesAsync();
} }
public async Task BlockUser(int userId, User blockedUser)
{
if (userId == blockedUser.UserId) return;
User? user = await this.Users.FindAsync(userId);
BlockedProfile blockedProfile = new()
{
User = user,
BlockedUser = blockedUser,
};
await this.BlockedProfiles.AddAsync(blockedProfile);
await this.SaveChangesAsync();
}
public async Task UnblockUser(int userId, User blockedUser)
{
if (userId == blockedUser.UserId) return;
this.BlockedProfiles.RemoveWhere(bp => bp.BlockedUserId == blockedUser.UserId && bp.UserId == userId);
await this.SaveChangesAsync();
}
public async Task<bool> IsUserBlockedBy(int userId, int targetId)
{
if (targetId == userId) return false;
return await this.BlockedProfiles.Has(bp => bp.BlockedUserId == userId && bp.UserId == targetId);
}
#endregion #endregion
#region User Helper Methods #region User Helper Methods

View file

@ -0,0 +1,62 @@
using LBPUnion.ProjectLighthouse;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ProjectLighthouse.Migrations
{
[DbContext(typeof(Database))]
[Migration("20230208165011_AddedUserBlocking")]
public partial class AddedUserBlocking : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "BlockedProfiles",
columns: table => new
{
BlockedProfileId = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
UserId = table.Column<int>(type: "int", nullable: false),
BlockedUserId = table.Column<int>(type: "int", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_BlockedProfiles", x => x.BlockedProfileId);
table.ForeignKey(
name: "FK_BlockedProfiles_Users_BlockedUserId",
column: x => x.BlockedUserId,
principalTable: "Users",
principalColumn: "UserId",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_BlockedProfiles_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "UserId",
onDelete: ReferentialAction.Cascade);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateIndex(
name: "IX_BlockedProfiles_BlockedUserId",
table: "BlockedProfiles",
column: "BlockedUserId");
migrationBuilder.CreateIndex(
name: "IX_BlockedProfiles_UserId",
table: "BlockedProfiles",
column: "UserId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "BlockedProfiles");
}
}
}

View file

@ -0,0 +1,20 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace LBPUnion.ProjectLighthouse.PlayerData.Profiles;
public class BlockedProfile
{
[Key]
public int BlockedProfileId { get; set; }
public int UserId { get; set; }
[ForeignKey(nameof(UserId))]
public User User { get; set; }
public int BlockedUserId { get; set; }
[ForeignKey(nameof(BlockedUserId))]
public User BlockedUser { get; set; }
}

View file

@ -609,6 +609,27 @@ namespace ProjectLighthouse.Migrations
b.ToTable("PlatformLinkAttempts"); b.ToTable("PlatformLinkAttempts");
}); });
modelBuilder.Entity("LBPUnion.ProjectLighthouse.PlayerData.Profiles.BlockedProfile", b =>
{
b.Property<int>("BlockedProfileId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("BlockedUserId")
.HasColumnType("int");
b.Property<int>("UserId")
.HasColumnType("int");
b.HasKey("BlockedProfileId");
b.HasIndex("BlockedUserId");
b.HasIndex("UserId");
b.ToTable("BlockedProfiles");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.PlayerData.Profiles.Comment", b => modelBuilder.Entity("LBPUnion.ProjectLighthouse.PlayerData.Profiles.Comment", b =>
{ {
b.Property<int>("CommentId") b.Property<int>("CommentId")
@ -1225,6 +1246,25 @@ namespace ProjectLighthouse.Migrations
b.Navigation("User"); b.Navigation("User");
}); });
modelBuilder.Entity("LBPUnion.ProjectLighthouse.PlayerData.Profiles.BlockedProfile", b =>
{
b.HasOne("LBPUnion.ProjectLighthouse.PlayerData.Profiles.User", "BlockedUser")
.WithMany()
.HasForeignKey("BlockedUserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("LBPUnion.ProjectLighthouse.PlayerData.Profiles.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("BlockedUser");
b.Navigation("User");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.PlayerData.Profiles.Comment", b => modelBuilder.Entity("LBPUnion.ProjectLighthouse.PlayerData.Profiles.Comment", b =>
{ {
b.HasOne("LBPUnion.ProjectLighthouse.PlayerData.Profiles.User", "Poster") b.HasOne("LBPUnion.ProjectLighthouse.PlayerData.Profiles.User", "Poster")