mirror of
https://github.com/LBPUnion/ProjectLighthouse.git
synced 2025-07-29 08:28:39 +00:00
Merge f2cfa6b093
into a3022ff5b4
This commit is contained in:
commit
a8b070fb1e
162 changed files with 5438 additions and 138 deletions
|
@ -1,6 +1,6 @@
|
|||
using LBPUnion.ProjectLighthouse.Database;
|
||||
using LBPUnion.ProjectLighthouse.Filter;
|
||||
using LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
using LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Servers.API.Responses;
|
||||
using LBPUnion.ProjectLighthouse.Types.Users;
|
||||
|
|
|
@ -28,11 +28,7 @@ public class ApiStartup
|
|||
}
|
||||
);
|
||||
|
||||
services.AddDbContext<DatabaseContext>(builder =>
|
||||
{
|
||||
builder.UseMySql(ServerConfiguration.Instance.DbConnectionString,
|
||||
MySqlServerVersion.LatestSupportedServerVersion);
|
||||
});
|
||||
services.AddDbContext<DatabaseContext>(DatabaseContext.ConfigureBuilder());
|
||||
|
||||
services.AddSwaggerGen
|
||||
(
|
||||
|
|
|
@ -0,0 +1,372 @@
|
|||
using System.Linq.Expressions;
|
||||
using LBPUnion.ProjectLighthouse.Database;
|
||||
using LBPUnion.ProjectLighthouse.Extensions;
|
||||
using LBPUnion.ProjectLighthouse.Filter.Filters.Activity;
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Logging;
|
||||
using LBPUnion.ProjectLighthouse.StorableLists.Stores;
|
||||
using LBPUnion.ProjectLighthouse.Types.Activity;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
|
||||
using LBPUnion.ProjectLighthouse.Types.Levels;
|
||||
using LBPUnion.ProjectLighthouse.Types.Logging;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization.Activity;
|
||||
using LBPUnion.ProjectLighthouse.Types.Users;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Authorize]
|
||||
[Route("LITTLEBIGPLANETPS3_XML/stream")]
|
||||
[Produces("text/xml")]
|
||||
public class ActivityController : ControllerBase
|
||||
{
|
||||
private readonly DatabaseContext database;
|
||||
|
||||
public ActivityController(DatabaseContext database)
|
||||
{
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
private class ActivityFilterOptions
|
||||
{
|
||||
public bool ExcludeNews { get; init; }
|
||||
public bool ExcludeMyLevels { get; init; }
|
||||
public bool ExcludeFriends { get; init; }
|
||||
public bool ExcludeFavouriteUsers { get; init; }
|
||||
public bool ExcludeMyself { get; init; }
|
||||
public bool ExcludeMyPlaylists { get; init; } = true;
|
||||
}
|
||||
|
||||
private async Task<IQueryable<ActivityDto>> GetFilters
|
||||
(
|
||||
IQueryable<ActivityDto> dtoQuery,
|
||||
GameTokenEntity token,
|
||||
ActivityFilterOptions options
|
||||
)
|
||||
{
|
||||
dtoQuery = token.GameVersion == GameVersion.LittleBigPlanetVita
|
||||
? dtoQuery.Where(dto => dto.TargetSlotGameVersion == null || dto.TargetSlotGameVersion == token.GameVersion)
|
||||
: dtoQuery.Where(dto => dto.TargetSlotGameVersion == null || dto.TargetSlotGameVersion <= token.GameVersion);
|
||||
|
||||
Expression<Func<ActivityDto, bool>> predicate = PredicateExtensions.False<ActivityDto>();
|
||||
|
||||
List<int> favouriteUsers = await this.database.HeartedProfiles.Where(hp => hp.UserId == token.UserId)
|
||||
.Select(hp => hp.HeartedUserId)
|
||||
.ToListAsync();
|
||||
|
||||
List<int>? friendIds = UserFriendStore.GetUserFriendData(token.UserId)?.FriendIds;
|
||||
friendIds ??= [];
|
||||
|
||||
// This is how lbp3 does its filtering
|
||||
GameStreamFilter? filter = await this.DeserializeBody<GameStreamFilter>();
|
||||
if (filter?.Sources != null)
|
||||
{
|
||||
foreach (GameStreamFilterEventSource filterSource in filter.Sources.Where(filterSource =>
|
||||
filterSource.SourceType != null && filterSource.Types?.Count != 0))
|
||||
{
|
||||
EventType[] types = filterSource.Types?.ToArray() ?? Array.Empty<EventType>();
|
||||
EventTypeFilter eventFilter = new(types);
|
||||
predicate = filterSource.SourceType switch
|
||||
{
|
||||
"MyLevels" => predicate.Or(new MyLevelActivityFilter(token.UserId, eventFilter).GetPredicate()),
|
||||
"FavouriteUsers" => predicate.Or(
|
||||
new IncludeUserIdFilter(favouriteUsers, eventFilter).GetPredicate()),
|
||||
"Friends" => predicate.Or(new IncludeUserIdFilter(friendIds, eventFilter).GetPredicate()),
|
||||
_ => predicate,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Expression<Func<ActivityDto, bool>> newsPredicate = !options.ExcludeNews
|
||||
? new IncludeNewsFilter().GetPredicate()
|
||||
: new ExcludeNewsFilter().GetPredicate();
|
||||
|
||||
predicate = predicate.Or(newsPredicate);
|
||||
|
||||
if (!options.ExcludeMyLevels)
|
||||
{
|
||||
predicate = predicate.Or(dto => dto.TargetSlotCreatorId == token.UserId);
|
||||
}
|
||||
|
||||
List<int> includedUserIds = [];
|
||||
|
||||
if (!options.ExcludeFriends)
|
||||
{
|
||||
includedUserIds.AddRange(friendIds);
|
||||
}
|
||||
|
||||
if (!options.ExcludeFavouriteUsers)
|
||||
{
|
||||
includedUserIds.AddRange(favouriteUsers);
|
||||
}
|
||||
|
||||
if (!options.ExcludeMyself)
|
||||
{
|
||||
includedUserIds.Add(token.UserId);
|
||||
}
|
||||
|
||||
predicate = predicate.Or(dto => includedUserIds.Contains(dto.Activity.UserId));
|
||||
|
||||
if (!options.ExcludeMyPlaylists && !options.ExcludeMyself && token.GameVersion == GameVersion.LittleBigPlanet3)
|
||||
{
|
||||
List<int> creatorPlaylists = await this.database.Playlists.Where(p => p.CreatorId == token.UserId)
|
||||
.Select(p => p.PlaylistId)
|
||||
.ToListAsync();
|
||||
predicate = predicate.Or(new PlaylistActivityFilter(creatorPlaylists).GetPredicate());
|
||||
}
|
||||
else
|
||||
{
|
||||
predicate = predicate.And(dto =>
|
||||
dto.Activity.Type != EventType.CreatePlaylist &&
|
||||
dto.Activity.Type != EventType.HeartPlaylist &&
|
||||
dto.Activity.Type != EventType.AddLevelToPlaylist);
|
||||
}
|
||||
|
||||
dtoQuery = dtoQuery.Where(predicate);
|
||||
|
||||
return dtoQuery;
|
||||
}
|
||||
|
||||
public Task<DateTime> GetMostRecentEventTime(IQueryable<ActivityDto> activity, DateTime upperBound)
|
||||
{
|
||||
return activity.OrderByDescending(a => a.Activity.Timestamp)
|
||||
.Where(a => a.Activity.Timestamp < upperBound)
|
||||
.Select(a => a.Activity.Timestamp)
|
||||
.FirstOrDefaultAsync();
|
||||
}
|
||||
|
||||
private async Task<(DateTime Start, DateTime End)> GetTimeBounds
|
||||
(IQueryable<ActivityDto> activityQuery, long? startTime, long? endTime)
|
||||
{
|
||||
if (startTime is null or 0) startTime = TimeHelper.TimestampMillis;
|
||||
|
||||
DateTime start = DateTimeExtensions.FromUnixTimeMilliseconds(startTime.Value);
|
||||
DateTime end;
|
||||
|
||||
if (endTime == null)
|
||||
{
|
||||
end = await this.GetMostRecentEventTime(activityQuery, start);
|
||||
// If there is no recent event then set it to the the start
|
||||
if (end == DateTime.MinValue) end = start;
|
||||
end = end.Subtract(TimeSpan.FromDays(7));
|
||||
}
|
||||
else
|
||||
{
|
||||
end = DateTimeExtensions.FromUnixTimeMilliseconds(endTime.Value);
|
||||
// Don't allow more than 7 days worth of activity in a single page
|
||||
if (start.Subtract(end).TotalDays > 7)
|
||||
{
|
||||
end = start.Subtract(TimeSpan.FromDays(7));
|
||||
}
|
||||
}
|
||||
|
||||
return (start, end);
|
||||
}
|
||||
|
||||
private static DateTime GetOldestTime
|
||||
(IReadOnlyCollection<IGrouping<ActivityGroup, ActivityDto>> groups, DateTime defaultTimestamp) =>
|
||||
groups.Count != 0
|
||||
? groups.Min(g => g.MinBy(a => a.Activity.Timestamp)?.Activity.Timestamp ?? defaultTimestamp)
|
||||
: defaultTimestamp;
|
||||
|
||||
/// <summary>
|
||||
/// Speeds up serialization because many nested entities need to find Slots by id
|
||||
/// and since they use the Find() method they can benefit from having the entities
|
||||
/// already tracked by the context
|
||||
/// </summary>
|
||||
private async Task CacheEntities(IReadOnlyCollection<OuterActivityGroup> groups)
|
||||
{
|
||||
List<int> slotIds = groups.GetIds(ActivityGroupType.Level);
|
||||
List<int> userIds = groups.GetIds(ActivityGroupType.User);
|
||||
List<int> playlistIds = groups.GetIds(ActivityGroupType.Playlist);
|
||||
List<int> newsIds = groups.GetIds(ActivityGroupType.News);
|
||||
|
||||
// Cache target levels and users within DbContext
|
||||
if (slotIds.Count > 0) await this.database.Slots.Where(s => slotIds.Contains(s.SlotId)).LoadAsync();
|
||||
if (userIds.Count > 0) await this.database.Users.Where(u => userIds.Contains(u.UserId)).LoadAsync();
|
||||
if (playlistIds.Count > 0)
|
||||
await this.database.Playlists.Where(p => playlistIds.Contains(p.PlaylistId)).LoadAsync();
|
||||
if (newsIds.Count > 0)
|
||||
await this.database.WebsiteAnnouncements.Where(a => newsIds.Contains(a.AnnouncementId)).LoadAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// LBP3 uses a different grouping format that wants the actor to be the top level group and the events should be the subgroups
|
||||
/// </summary>
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> GlobalActivityLBP3
|
||||
(long timestamp, bool excludeMyPlaylists, bool excludeNews, bool excludeMyself)
|
||||
{
|
||||
GameTokenEntity token = this.GetToken();
|
||||
|
||||
if (token.GameVersion != GameVersion.LittleBigPlanet3) return this.NotFound();
|
||||
|
||||
IQueryable<ActivityDto> activityEvents = await this.GetFilters(
|
||||
this.database.Activities.ToActivityDto(true, true), token, new ActivityFilterOptions()
|
||||
{
|
||||
ExcludeNews = excludeNews,
|
||||
ExcludeMyLevels = true,
|
||||
ExcludeFriends = true,
|
||||
ExcludeFavouriteUsers = true,
|
||||
ExcludeMyself = excludeMyself,
|
||||
ExcludeMyPlaylists = excludeMyPlaylists,
|
||||
});
|
||||
|
||||
(DateTime Start, DateTime End) times = await this.GetTimeBounds(activityEvents, timestamp, null);
|
||||
|
||||
// LBP3 is grouped by actorThenObject meaning it wants all events by a user grouped together rather than
|
||||
// all user events for a level or profile grouped together
|
||||
List<IGrouping<ActivityGroup, ActivityDto>> groups = await activityEvents
|
||||
.Where(dto => dto.Activity.Timestamp < times.Start && dto.Activity.Timestamp > times.End)
|
||||
.ToActivityGroups(true)
|
||||
.ToListAsync();
|
||||
|
||||
List<OuterActivityGroup> outerGroups = groups.ToOuterActivityGroups(true);
|
||||
|
||||
long oldestTimestamp = GetOldestTime(groups, times.Start).ToUnixTimeMilliseconds();
|
||||
|
||||
return this.Ok(GameStream.CreateFromGroups(token,
|
||||
outerGroups,
|
||||
times.Start.ToUnixTimeMilliseconds(),
|
||||
oldestTimestamp));
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GlobalActivity
|
||||
(
|
||||
long timestamp,
|
||||
long endTimestamp,
|
||||
bool excludeNews,
|
||||
bool excludeMyLevels,
|
||||
bool excludeFriends,
|
||||
bool excludeFavouriteUsers,
|
||||
bool excludeMyself
|
||||
)
|
||||
{
|
||||
GameTokenEntity token = this.GetToken();
|
||||
|
||||
if (token.GameVersion is GameVersion.LittleBigPlanet1 or GameVersion.LittleBigPlanetPSP) return this.NotFound();
|
||||
|
||||
IQueryable<ActivityDto> activityEvents = await this.GetFilters(this.database.Activities.ToActivityDto(true),
|
||||
token,
|
||||
new ActivityFilterOptions
|
||||
{
|
||||
ExcludeNews = excludeNews,
|
||||
ExcludeMyLevels = excludeMyLevels,
|
||||
ExcludeFriends = excludeFriends,
|
||||
ExcludeFavouriteUsers = excludeFavouriteUsers,
|
||||
ExcludeMyself = excludeMyself,
|
||||
});
|
||||
|
||||
(DateTime Start, DateTime End) times = await this.GetTimeBounds(activityEvents, timestamp, endTimestamp);
|
||||
|
||||
List<IGrouping<ActivityGroup, ActivityDto>> groups = await activityEvents
|
||||
.Where(dto => dto.Activity.Timestamp < times.Start && dto.Activity.Timestamp > times.End)
|
||||
.ToActivityGroups()
|
||||
.ToListAsync();
|
||||
|
||||
List<OuterActivityGroup> outerGroups = groups.ToOuterActivityGroups();
|
||||
|
||||
long oldestTimestamp = GetOldestTime(groups, times.Start).ToUnixTimeMilliseconds();
|
||||
|
||||
await this.CacheEntities(outerGroups);
|
||||
|
||||
GameStream? gameStream = GameStream.CreateFromGroups(token,
|
||||
outerGroups,
|
||||
times.Start.ToUnixTimeMilliseconds(),
|
||||
oldestTimestamp);
|
||||
|
||||
return this.Ok(gameStream);
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
private static void PrintOuterGroups(List<OuterActivityGroup> outerGroups)
|
||||
{
|
||||
foreach (OuterActivityGroup outer in outerGroups)
|
||||
{
|
||||
Logger.Debug(@$"Outer group key: {outer.Key}", LogArea.Activity);
|
||||
List<IGrouping<InnerActivityGroup, ActivityDto>> itemGroup = outer.Groups;
|
||||
foreach (IGrouping<InnerActivityGroup, ActivityDto> item in itemGroup)
|
||||
{
|
||||
Logger.Debug(
|
||||
@$" Inner group key: TargetId={item.Key.TargetId}, UserId={item.Key.UserId}, Type={item.Key.Type}",
|
||||
LogArea.Activity);
|
||||
foreach (ActivityDto activity in item)
|
||||
{
|
||||
Logger.Debug(
|
||||
@$" Activity: {activity.GroupType}, Timestamp: {activity.Activity.Timestamp}, UserId: {activity.Activity.UserId}, EventType: {activity.Activity.Type}, TargetId: {activity.TargetId}",
|
||||
LogArea.Activity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
[HttpGet("slot/{slotType}/{slotId:int}")]
|
||||
[HttpGet("user2/{username}")]
|
||||
public async Task<IActionResult> LocalActivity(string? slotType, int slotId, string? username, long? timestamp)
|
||||
{
|
||||
GameTokenEntity token = this.GetToken();
|
||||
|
||||
if (token.GameVersion is GameVersion.LittleBigPlanet1 or GameVersion.LittleBigPlanetPSP) return this.NotFound();
|
||||
|
||||
if ((SlotHelper.IsTypeInvalid(slotType) || slotId == 0) == (username == null)) return this.BadRequest();
|
||||
|
||||
bool isLevelActivity = username == null;
|
||||
bool groupByActor = !isLevelActivity && token.GameVersion == GameVersion.LittleBigPlanet3;
|
||||
|
||||
// User and Level activity will never contain news posts or MM pick events.
|
||||
IQueryable<ActivityDto> activityQuery = this.database.Activities.ToActivityDto()
|
||||
.Where(a => a.Activity.Type != EventType.NewsPost && a.Activity.Type != EventType.MMPickLevel);
|
||||
|
||||
if (token.GameVersion != GameVersion.LittleBigPlanet3)
|
||||
{
|
||||
activityQuery = activityQuery.Where(a =>
|
||||
a.Activity.Type != EventType.CreatePlaylist &&
|
||||
a.Activity.Type != EventType.HeartPlaylist &&
|
||||
a.Activity.Type != EventType.AddLevelToPlaylist);
|
||||
}
|
||||
|
||||
// Slot activity
|
||||
if (isLevelActivity)
|
||||
{
|
||||
if (slotType == "developer")
|
||||
slotId = await SlotHelper.GetPlaceholderSlotId(this.database, slotId, SlotType.Developer);
|
||||
|
||||
if (!await this.database.Slots.AnyAsync(s => s.SlotId == slotId)) return this.NotFound();
|
||||
|
||||
activityQuery = activityQuery.Where(dto => dto.TargetSlotId == slotId);
|
||||
}
|
||||
// User activity
|
||||
else
|
||||
{
|
||||
int userId = await this.database.Users.Where(u => u.Username == username)
|
||||
.Select(u => u.UserId)
|
||||
.FirstOrDefaultAsync();
|
||||
if (userId == 0) return this.NotFound();
|
||||
activityQuery = activityQuery.Where(dto => dto.Activity.UserId == userId);
|
||||
}
|
||||
|
||||
(DateTime Start, DateTime End) times = await this.GetTimeBounds(activityQuery, timestamp, null);
|
||||
|
||||
activityQuery = activityQuery.Where(dto =>
|
||||
dto.Activity.Timestamp < times.Start && dto.Activity.Timestamp > times.End);
|
||||
|
||||
List<IGrouping<ActivityGroup, ActivityDto>> groups = await activityQuery.ToActivityGroups(groupByActor).ToListAsync();
|
||||
|
||||
List<OuterActivityGroup> outerGroups = groups.ToOuterActivityGroups(groupByActor);
|
||||
|
||||
long oldestTimestamp = GetOldestTime(groups, times.Start).ToUnixTimeMilliseconds();
|
||||
|
||||
await this.CacheEntities(outerGroups);
|
||||
|
||||
return this.Ok(GameStream.CreateFromGroups(token,
|
||||
outerGroups,
|
||||
times.Start.ToUnixTimeMilliseconds(),
|
||||
oldestTimestamp,
|
||||
isLevelActivity));
|
||||
}
|
||||
}
|
|
@ -9,7 +9,7 @@ using LBPUnion.ProjectLighthouse.Types.Entities.Token;
|
|||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
using LBPUnion.ProjectLighthouse.Types.Levels;
|
||||
using LBPUnion.ProjectLighthouse.Types.Logging;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization.Comment;
|
||||
using LBPUnion.ProjectLighthouse.Types.Users;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
@ -44,6 +44,20 @@ public class CommentController : ControllerBase
|
|||
return this.Ok();
|
||||
}
|
||||
|
||||
[HttpGet("userComment/{username}")]
|
||||
[HttpGet("comment/{slotType}/{slotId:int}")]
|
||||
public async Task<IActionResult> GetSingleComment(string? username, string? slotType, int? slotId, int commentId)
|
||||
{
|
||||
GameTokenEntity token = this.GetToken();
|
||||
|
||||
if (username == null == (SlotHelper.IsTypeInvalid(slotType) || slotId == null)) return this.BadRequest();
|
||||
|
||||
CommentEntity? comment = await this.database.Comments.FindAsync(commentId);
|
||||
if (comment == null) return this.NotFound();
|
||||
|
||||
return this.Ok(GameComment.CreateFromEntity(comment, token.UserId));
|
||||
}
|
||||
|
||||
[HttpGet("comments/{slotType}/{slotId:int}")]
|
||||
[HttpGet("userComments/{username}")]
|
||||
public async Task<IActionResult> GetComments(string? username, string? slotType, int slotId)
|
||||
|
|
|
@ -5,7 +5,7 @@ using LBPUnion.ProjectLighthouse.Servers.GameServer.Types.Users;
|
|||
using LBPUnion.ProjectLighthouse.StorableLists.Stores;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization.User;
|
||||
using LBPUnion.ProjectLighthouse.Types.Users;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
using LBPUnion.ProjectLighthouse.Database;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Website;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization.News;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Authorize]
|
||||
[Route("LITTLEBIGPLANETPS3_XML/")]
|
||||
[Produces("text/xml")]
|
||||
public class NewsController : ControllerBase
|
||||
{
|
||||
private readonly DatabaseContext database;
|
||||
|
||||
public NewsController(DatabaseContext database)
|
||||
{
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
[HttpGet("news")]
|
||||
public async Task<IActionResult> GetNews()
|
||||
{
|
||||
List<WebsiteAnnouncementEntity> websiteAnnouncements =
|
||||
await this.database.WebsiteAnnouncements.OrderByDescending(a => a.AnnouncementId).ToListAsync();
|
||||
|
||||
return this.Ok(GameNews.CreateFromEntity(websiteAnnouncements));
|
||||
}
|
||||
}
|
|
@ -12,7 +12,7 @@ using LBPUnion.ProjectLighthouse.Types.Entities.Token;
|
|||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
using LBPUnion.ProjectLighthouse.Types.Levels;
|
||||
using LBPUnion.ProjectLighthouse.Types.Logging;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization.Photo;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
|
|
@ -13,6 +13,9 @@ using LBPUnion.ProjectLighthouse.Types.Levels;
|
|||
using LBPUnion.ProjectLighthouse.Types.Logging;
|
||||
using LBPUnion.ProjectLighthouse.Types.Misc;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization.Playlist;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization.Slot;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization.User;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
|
|
@ -9,7 +9,9 @@ using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
|
|||
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
|
||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
using LBPUnion.ProjectLighthouse.Types.Levels;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization.Playlist;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization.Slot;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization.User;
|
||||
using LBPUnion.ProjectLighthouse.Types.Users;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
|
|
@ -4,7 +4,8 @@ using LBPUnion.ProjectLighthouse.Database;
|
|||
using LBPUnion.ProjectLighthouse.Extensions;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization.Playlist;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization.Slot;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
|
|
@ -11,7 +11,7 @@ using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
|
|||
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
|
||||
using LBPUnion.ProjectLighthouse.Types.Logging;
|
||||
using LBPUnion.ProjectLighthouse.Types.Resources;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization.Slot;
|
||||
using LBPUnion.ProjectLighthouse.Types.Users;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
|
|
@ -7,7 +7,7 @@ using LBPUnion.ProjectLighthouse.Types.Entities.Interaction;
|
|||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
|
||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization.Review;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
@ -145,6 +145,22 @@ public class ReviewController : ControllerBase
|
|||
return this.Ok();
|
||||
}
|
||||
|
||||
[HttpGet("review/user/{slotId:int}/{reviewerName}")]
|
||||
public async Task<IActionResult> GetReview(int slotId, string reviewerName)
|
||||
{
|
||||
GameTokenEntity token = this.GetToken();
|
||||
|
||||
int reviewerId = await this.database.Users.Where(u => u.Username == reviewerName)
|
||||
.Select(s => s.UserId)
|
||||
.FirstOrDefaultAsync();
|
||||
if (reviewerId == 0) return this.NotFound();
|
||||
|
||||
ReviewEntity? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.ReviewerId == reviewerId && r.SlotId == slotId);
|
||||
if (review == null) return this.NotFound();
|
||||
|
||||
return this.Ok(GameReview.CreateFromEntity(review, token));
|
||||
}
|
||||
|
||||
[HttpGet("reviewsFor/user/{slotId:int}")]
|
||||
public async Task<IActionResult> ReviewsFor(int slotId)
|
||||
{
|
||||
|
|
|
@ -8,7 +8,7 @@ using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
|||
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
|
||||
using LBPUnion.ProjectLighthouse.Types.Levels;
|
||||
using LBPUnion.ProjectLighthouse.Types.Logging;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization.Score;
|
||||
using LBPUnion.ProjectLighthouse.Types.Users;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
@ -131,7 +131,8 @@ public class ScoreController : ControllerBase
|
|||
|
||||
await this.database.SaveChangesAsync();
|
||||
|
||||
ScoreEntity? existingScore = await this.database.Scores.Where(s => s.SlotId == slot.SlotId)
|
||||
ScoreEntity? existingScore = await this.database.Scores
|
||||
.Where(s => s.SlotId == slot.SlotId)
|
||||
.Where(s => s.ChildSlotId == 0 || s.ChildSlotId == childId)
|
||||
.Where(s => s.UserId == token.UserId)
|
||||
.Where(s => s.Type == score.Type)
|
||||
|
|
|
@ -2,13 +2,13 @@
|
|||
using LBPUnion.ProjectLighthouse.Database;
|
||||
using LBPUnion.ProjectLighthouse.Extensions;
|
||||
using LBPUnion.ProjectLighthouse.Filter;
|
||||
using LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
using LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
using LBPUnion.ProjectLighthouse.Filter.Sorts;
|
||||
using LBPUnion.ProjectLighthouse.Servers.GameServer.Extensions;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
|
||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization.Slot;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
|
|
@ -4,6 +4,7 @@ using LBPUnion.ProjectLighthouse.Database;
|
|||
using LBPUnion.ProjectLighthouse.Extensions;
|
||||
using LBPUnion.ProjectLighthouse.Filter;
|
||||
using LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
using LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
using LBPUnion.ProjectLighthouse.Filter.Sorts;
|
||||
using LBPUnion.ProjectLighthouse.Filter.Sorts.Metadata;
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
|
@ -13,7 +14,7 @@ using LBPUnion.ProjectLighthouse.Types.Entities.Token;
|
|||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
using LBPUnion.ProjectLighthouse.Types.Levels;
|
||||
using LBPUnion.ProjectLighthouse.Types.Misc;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization.Slot;
|
||||
using LBPUnion.ProjectLighthouse.Types.Users;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
|
|
@ -4,9 +4,9 @@ using Microsoft.AspNetCore.Authorization;
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using LBPUnion.ProjectLighthouse.Extensions;
|
||||
using LBPUnion.ProjectLighthouse.Filter;
|
||||
using LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
using LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
using LBPUnion.ProjectLighthouse.Servers.GameServer.Extensions;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization.Slot;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers;
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
|
|||
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
|
||||
using LBPUnion.ProjectLighthouse.Types.Levels;
|
||||
using LBPUnion.ProjectLighthouse.Types.Logging;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization.User;
|
||||
using LBPUnion.ProjectLighthouse.Types.Users;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
using LBPUnion.ProjectLighthouse.Filter;
|
||||
using LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
using LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
|
||||
using LBPUnion.ProjectLighthouse.Types.Levels;
|
||||
using LBPUnion.ProjectLighthouse.Types.Users;
|
||||
|
|
|
@ -7,7 +7,7 @@ using LBPUnion.ProjectLighthouse.Types.Entities.Token;
|
|||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
using LBPUnion.ProjectLighthouse.Types.Filter.Sorts;
|
||||
using LBPUnion.ProjectLighthouse.Types.Misc;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization.Slot;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Extensions;
|
||||
|
|
|
@ -54,11 +54,7 @@ public class GameServerStartup
|
|||
}
|
||||
);
|
||||
|
||||
services.AddDbContext<DatabaseContext>(builder =>
|
||||
{
|
||||
builder.UseMySql(ServerConfiguration.Instance.DbConnectionString,
|
||||
MySqlServerVersion.LatestSupportedServerVersion);
|
||||
});
|
||||
services.AddDbContext<DatabaseContext>(DatabaseContext.ConfigureBuilder());
|
||||
|
||||
IMailService mailService = ServerConfiguration.Instance.Mail.MailEnabled
|
||||
? new MailQueueService(new SmtpMailSender())
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#nullable enable
|
||||
using LBPUnion.ProjectLighthouse.Database;
|
||||
using LBPUnion.ProjectLighthouse.Filter;
|
||||
using LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
using LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
|
||||
|
||||
|
|
|
@ -5,6 +5,8 @@ using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
|||
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
|
||||
using LBPUnion.ProjectLighthouse.Types.Levels;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization.Playlist;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization.Slot;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Types.Categories;
|
||||
|
|
|
@ -5,6 +5,7 @@ using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
|||
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
|
||||
using LBPUnion.ProjectLighthouse.Types.Levels;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization.Slot;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Types.Categories;
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
using LBPUnion.ProjectLighthouse.Database;
|
||||
using LBPUnion.ProjectLighthouse.Extensions;
|
||||
using LBPUnion.ProjectLighthouse.Filter;
|
||||
using LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
using LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
using LBPUnion.ProjectLighthouse.Filter.Sorts;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
|
||||
|
|
|
@ -5,6 +5,8 @@ using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
|
|||
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
|
||||
using LBPUnion.ProjectLighthouse.Types.Levels;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization.Slot;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization.User;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Types.Categories;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
using LBPUnion.ProjectLighthouse.Database;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization.Review;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
@page "/debug/activity"
|
||||
@using System.Globalization
|
||||
@using LBPUnion.ProjectLighthouse.Types.Activity
|
||||
@using LBPUnion.ProjectLighthouse.Types.Entities.Activity
|
||||
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.Debug.ActivityTestPage
|
||||
|
||||
@{
|
||||
Layout = "Layouts/BaseLayout";
|
||||
Model.Title = "Debug - Activity Test";
|
||||
}
|
||||
|
||||
<a href="?groupByActor=false">
|
||||
<div class="ui @(!Model.GroupByActor ? "blue" : "") button">Group By Activity</div>
|
||||
</a>
|
||||
<a href="?groupByActor=true">
|
||||
<div class="ui @(Model.GroupByActor ? "blue" : "") button">Group By Actor</div>
|
||||
</a>
|
||||
<div class="ui divider"></div>
|
||||
|
||||
@foreach (OuterActivityGroup activity in Model.ActivityGroups)
|
||||
{
|
||||
<h4 class="ui top attached header">@activity.Key.GroupType, Timestamp: @activity.Key.Timestamp.ToString(CultureInfo.InvariantCulture)</h4>
|
||||
<div class="ui attached segment">
|
||||
|
||||
@if (activity.Key.UserId != -1)
|
||||
{
|
||||
<p>UserId: @activity.Key.UserId</p>
|
||||
}
|
||||
@if ((activity.Key.TargetNewsId ?? -1) != -1)
|
||||
{
|
||||
<p>TargetNewsId?: @activity.Key.TargetNewsId (targetId=@activity.Key.TargetId)</p>
|
||||
}
|
||||
@if ((activity.Key.TargetPlaylistId ?? -1) != -1)
|
||||
{
|
||||
<p>TargetPlaylistId?: @activity.Key.TargetPlaylistId (targetId=@activity.Key.TargetId)</p>
|
||||
}
|
||||
@if ((activity.Key.TargetSlotId ?? -1) != -1)
|
||||
{
|
||||
<p>TargetSlotId?: @activity.Key.TargetSlotId (targetId=@activity.Key.TargetId)</p>
|
||||
}
|
||||
@if ((activity.Key.TargetTeamPickSlotId ?? -1) != -1)
|
||||
{
|
||||
<p>TargetTeamPickSlot?: @activity.Key.TargetTeamPickSlotId (targetId=@activity.Key.TargetId)</p>
|
||||
}
|
||||
@if ((activity.Key.TargetUserId ?? -1) != -1)
|
||||
{
|
||||
<p>TargetUserId?: @activity.Key.TargetUserId (targetId=@activity.Key.TargetId)</p>
|
||||
}
|
||||
<div class="ui segments">
|
||||
|
||||
@foreach (IGrouping<InnerActivityGroup, ActivityDto>? eventGroup in activity.Groups)
|
||||
{
|
||||
<div class="ui segment">
|
||||
<h5>Nested Group Type: @eventGroup.Key.Type</h5>
|
||||
|
||||
@foreach (ActivityDto gameEvent in eventGroup.ToList())
|
||||
{
|
||||
<h5 class="ui top attached header" style="text-align: start">
|
||||
@gameEvent.Activity.Type, Event Id: @gameEvent.Activity.ActivityId
|
||||
</h5>
|
||||
<div class="ui attached segment">
|
||||
<p>Event Group Type: @gameEvent.GroupType</p>
|
||||
<p>Event Target ID: @gameEvent.TargetId</p>
|
||||
@if (gameEvent.Activity is LevelActivityEntity level)
|
||||
{
|
||||
<p>SlotId: @level.SlotId</p>
|
||||
<p>SlotVersion: @gameEvent.TargetSlotGameVersion</p>
|
||||
}
|
||||
@if (gameEvent.Activity is ScoreActivityEntity score)
|
||||
{
|
||||
<p>ScoreId: @score.ScoreId</p>
|
||||
<p>SlotId: @score.SlotId</p>
|
||||
<p>SlotVersion: @gameEvent.TargetSlotGameVersion</p>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui bottom attached segment">
|
||||
<p>Total events: @activity.Groups.Sum(g => g.ToList().Count)</p>
|
||||
</div>
|
||||
<div class="ui massive divider" style="background-color: #0e91f5"></div>
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
using LBPUnion.ProjectLighthouse.Database;
|
||||
using LBPUnion.ProjectLighthouse.Extensions;
|
||||
using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts;
|
||||
using LBPUnion.ProjectLighthouse.Types.Activity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages.Debug;
|
||||
|
||||
public class ActivityTestPage : BaseLayout
|
||||
{
|
||||
public ActivityTestPage(DatabaseContext database) : base(database)
|
||||
{ }
|
||||
|
||||
public List<OuterActivityGroup> ActivityGroups = [];
|
||||
|
||||
public bool GroupByActor { get; set; }
|
||||
|
||||
public async Task<IActionResult> OnGet(bool groupByActor = false)
|
||||
{
|
||||
Console.WriteLine(groupByActor);
|
||||
List<OuterActivityGroup>? events = (await this.Database.Activities.ToActivityDto(true).ToActivityGroups(groupByActor).ToListAsync())
|
||||
.ToOuterActivityGroups(groupByActor);
|
||||
|
||||
if (events == null) return this.Page();
|
||||
|
||||
this.GroupByActor = groupByActor;
|
||||
|
||||
this.ActivityGroups = events;
|
||||
return this.Page();
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -64,7 +64,7 @@ public class CompleteEmailVerificationPage : BaseLayout
|
|||
webToken.UserToken,
|
||||
new CookieOptions
|
||||
{
|
||||
Expires = DateTimeOffset.Now.AddDays(7),
|
||||
Expires = DateTimeOffset.UtcNow.AddDays(7),
|
||||
});
|
||||
return this.Redirect("/passwordReset");
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
bool isMobile = Request.IsMobile();
|
||||
string language = Model.GetLanguage();
|
||||
string timeZone = Model.GetTimeZone();
|
||||
TimeZoneInfo timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(timeZone);
|
||||
}
|
||||
<h1 class="lighthouse-welcome lighthouse-title">
|
||||
@Model.Translate(LandingPageStrings.Welcome, ServerConfiguration.Instance.Customization.ServerName)
|
||||
|
@ -82,6 +83,7 @@
|
|||
<a style="color: black" href="~/user/@Model.LatestAnnouncement.Publisher.UserId">
|
||||
@Model.LatestAnnouncement.Publisher.Username
|
||||
</a>
|
||||
at @TimeZoneInfo.ConvertTime(Model.LatestAnnouncement.PublishedAt, TimeZoneInfo.Utc, timeZoneInfo).ToString("M/d/yyyy h:mm:ss tt")
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
|
||||
<div class="column">
|
||||
<h2 class="ui black image header centered">
|
||||
<img src="~/@(ServerConfiguration.Instance.WebsiteConfiguration.PrideEventEnabled && DateTime.Now.Month == 6 ? "logo-pride.png" : "logo-color.png")"
|
||||
<img src="~/@(ServerConfiguration.Instance.WebsiteConfiguration.PrideEventEnabled && DateTime.UtcNow.Month == 6 ? "logo-pride.png" : "logo-color.png")"
|
||||
alt="Instance logo"
|
||||
class="image"
|
||||
style="width: 128px;"/>
|
||||
|
|
|
@ -100,7 +100,7 @@ public class LoginForm : BaseLayout
|
|||
webToken.UserToken,
|
||||
new CookieOptions
|
||||
{
|
||||
Expires = DateTimeOffset.Now.AddDays(7),
|
||||
Expires = DateTimeOffset.UtcNow.AddDays(7),
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -2,13 +2,14 @@
|
|||
@using LBPUnion.ProjectLighthouse.Localization.StringLists
|
||||
@using LBPUnion.ProjectLighthouse.Types.Entities.Notifications
|
||||
@using LBPUnion.ProjectLighthouse.Types.Entities.Website
|
||||
@using LBPUnion.ProjectLighthouse.Types.Notifications
|
||||
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.NotificationsPage
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
|
||||
@{
|
||||
Layout = "Layouts/BaseLayout";
|
||||
Model.Title = Model.Translate(GeneralStrings.Notifications);
|
||||
string timeZone = Model.GetTimeZone();
|
||||
TimeZoneInfo timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(timeZone);
|
||||
}
|
||||
|
||||
@if (Model.User != null && Model.User.IsAdmin)
|
||||
|
@ -51,6 +52,7 @@
|
|||
<a style="color: black" href="~/user/@announcement.Publisher.UserId">
|
||||
@announcement.Publisher.Username
|
||||
</a>
|
||||
at @TimeZoneInfo.ConvertTime(announcement.PublishedAt, TimeZoneInfo.Utc, timeZoneInfo).ToString("M/d/yyyy h:mm:ss tt")
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
|
|
@ -54,6 +54,7 @@ public class NotificationsPage : BaseLayout
|
|||
Title = title.Trim(),
|
||||
Content = content.Trim(),
|
||||
PublisherId = user.UserId,
|
||||
PublishedAt = DateTime.UtcNow,
|
||||
};
|
||||
|
||||
this.Database.WebsiteAnnouncements.Add(announcement);
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
@using LBPUnion.ProjectLighthouse.Servers.Website.Extensions
|
||||
@using LBPUnion.ProjectLighthouse.Types.Entities.Profile
|
||||
@using LBPUnion.ProjectLighthouse.Types.Levels
|
||||
@using LBPUnion.ProjectLighthouse.Types.Serialization
|
||||
@using LBPUnion.ProjectLighthouse.Types.Serialization.Photo
|
||||
@model LBPUnion.ProjectLighthouse.Types.Entities.Profile.PhotoEntity
|
||||
|
||||
@{
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
@using LBPUnion.ProjectLighthouse.Files
|
||||
@using LBPUnion.ProjectLighthouse.Helpers
|
||||
@using LBPUnion.ProjectLighthouse.Types.Entities.Level
|
||||
@using LBPUnion.ProjectLighthouse.Types.Serialization
|
||||
@using LBPUnion.ProjectLighthouse.Types.Serialization.Review
|
||||
|
||||
@{
|
||||
bool isMobile = (bool?)ViewData["IsMobile"] ?? false;
|
||||
bool canDelete = (bool?)ViewData["CanDelete"] ?? false;
|
||||
|
|
|
@ -13,7 +13,6 @@ using LBPUnion.ProjectLighthouse.Services;
|
|||
using LBPUnion.ProjectLighthouse.Types.Mail;
|
||||
using Microsoft.AspNetCore.HttpOverrides;
|
||||
using Microsoft.AspNetCore.Localization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
|
||||
#if !DEBUG
|
||||
|
@ -39,11 +38,7 @@ public class WebsiteStartup
|
|||
services.AddControllers();
|
||||
services.AddRazorPages().WithRazorPagesAtContentRoot();
|
||||
|
||||
services.AddDbContext<DatabaseContext>(builder =>
|
||||
{
|
||||
builder.UseMySql(ServerConfiguration.Instance.DbConnectionString,
|
||||
MySqlServerVersion.LatestSupportedServerVersion);
|
||||
});
|
||||
services.AddDbContext<DatabaseContext>(DatabaseContext.ConfigureBuilder());
|
||||
|
||||
IMailService mailService = ServerConfiguration.Instance.Mail.MailEnabled
|
||||
? new MailQueueService(new SmtpMailSender())
|
||||
|
|
|
@ -11,7 +11,7 @@ using LBPUnion.ProjectLighthouse.Tests.Helpers;
|
|||
using LBPUnion.ProjectLighthouse.Tests.Integration;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization.Slot;
|
||||
using LBPUnion.ProjectLighthouse.Types.Users;
|
||||
using Xunit;
|
||||
|
||||
|
|
|
@ -0,0 +1,969 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using LBPUnion.ProjectLighthouse.Database;
|
||||
using LBPUnion.ProjectLighthouse.Tests.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Types.Activity;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Activity;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Interaction;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
|
||||
using Xunit;
|
||||
|
||||
namespace ProjectLighthouse.Tests.GameApiTests.Unit.Activity;
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
public class ActivityEventHandlerTests
|
||||
{
|
||||
#region Entity Inserts
|
||||
[Fact]
|
||||
public async Task Level_Insert_ShouldCreatePublishActivity()
|
||||
{
|
||||
ActivityEntityEventHandler eventHandler = new();
|
||||
DatabaseContext database = await MockHelper.GetTestDatabase(new List<UserEntity>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Username = "test",
|
||||
UserId = 1,
|
||||
},
|
||||
});
|
||||
|
||||
SlotEntity slot = new()
|
||||
{
|
||||
CreatorId = 1,
|
||||
SlotId = 1,
|
||||
};
|
||||
database.Slots.Add(slot);
|
||||
await database.SaveChangesAsync();
|
||||
|
||||
eventHandler.OnEntityInserted(database, slot);
|
||||
|
||||
Assert.NotNull(database.Activities.OfType<LevelActivityEntity>()
|
||||
.FirstOrDefault(a => a.Type == EventType.PublishLevel && a.SlotId == 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task LevelComment_Insert_ShouldCreateCommentActivity()
|
||||
{
|
||||
ActivityEntityEventHandler eventHandler = new();
|
||||
DatabaseContext database = await MockHelper.GetTestDatabase(new List<UserEntity>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Username = "test",
|
||||
UserId = 1,
|
||||
},
|
||||
});
|
||||
|
||||
SlotEntity slot = new()
|
||||
{
|
||||
SlotId = 1,
|
||||
CreatorId = 1,
|
||||
};
|
||||
database.Slots.Add(slot);
|
||||
|
||||
CommentEntity comment = new()
|
||||
{
|
||||
CommentId = 1,
|
||||
PosterUserId = 1,
|
||||
TargetSlotId = 1,
|
||||
Type = CommentType.Level,
|
||||
};
|
||||
database.Comments.Add(comment);
|
||||
await database.SaveChangesAsync();
|
||||
|
||||
eventHandler.OnEntityInserted(database, comment);
|
||||
|
||||
Assert.NotNull(database.Activities.ToList().OfType<CommentActivityEntity>()
|
||||
.FirstOrDefault(a => a.Type == EventType.CommentOnLevel && a.CommentId == 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProfileComment_Insert_ShouldCreateCommentActivity()
|
||||
{
|
||||
ActivityEntityEventHandler eventHandler = new();
|
||||
DatabaseContext database = await MockHelper.GetTestDatabase(new List<UserEntity>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Username = "test",
|
||||
UserId = 1,
|
||||
},
|
||||
});
|
||||
|
||||
CommentEntity comment = new()
|
||||
{
|
||||
CommentId = 1,
|
||||
PosterUserId = 1,
|
||||
TargetUserId = 1,
|
||||
Type = CommentType.Profile,
|
||||
};
|
||||
database.Comments.Add(comment);
|
||||
await database.SaveChangesAsync();
|
||||
|
||||
eventHandler.OnEntityInserted(database, comment);
|
||||
|
||||
Assert.NotNull(database.Activities.ToList().OfType<CommentActivityEntity>()
|
||||
.FirstOrDefault(a => a.Type == EventType.CommentOnUser && a.CommentId == 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Photo_Insert_ShouldCreatePhotoActivity()
|
||||
{
|
||||
ActivityEntityEventHandler eventHandler = new();
|
||||
DatabaseContext database = await MockHelper.GetTestDatabase(new List<UserEntity>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Username = "test",
|
||||
UserId = 1,
|
||||
},
|
||||
},
|
||||
new List<SlotEntity>
|
||||
{
|
||||
new()
|
||||
{
|
||||
SlotId = 1,
|
||||
CreatorId = 1,
|
||||
},
|
||||
});
|
||||
|
||||
PhotoEntity photo = new()
|
||||
{
|
||||
PhotoId = 1,
|
||||
CreatorId = 1,
|
||||
SlotId = 1,
|
||||
};
|
||||
database.Photos.Add(photo);
|
||||
await database.SaveChangesAsync();
|
||||
|
||||
eventHandler.OnEntityInserted(database, photo);
|
||||
|
||||
Assert.NotNull(database.Activities.ToList().OfType<PhotoActivityEntity>()
|
||||
.FirstOrDefault(a => a.Type == EventType.UploadPhoto && a.PhotoId == 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Score_Insert_ShouldCreateScoreActivity()
|
||||
{
|
||||
ActivityEntityEventHandler eventHandler = new();
|
||||
DatabaseContext database = await MockHelper.GetTestDatabase(new List<UserEntity>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Username = "test",
|
||||
UserId = 1,
|
||||
},
|
||||
});
|
||||
|
||||
SlotEntity slot = new()
|
||||
{
|
||||
SlotId = 1,
|
||||
CreatorId = 1,
|
||||
};
|
||||
database.Slots.Add(slot);
|
||||
|
||||
ScoreEntity score = new()
|
||||
{
|
||||
ScoreId = 1,
|
||||
SlotId = 1,
|
||||
UserId = 1,
|
||||
};
|
||||
database.Scores.Add(score);
|
||||
await database.SaveChangesAsync();
|
||||
|
||||
eventHandler.OnEntityInserted(database, score);
|
||||
|
||||
Assert.NotNull(database.Activities.OfType<ScoreActivityEntity>()
|
||||
.FirstOrDefault(a => a.Type == EventType.Score && a.ScoreId == 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HeartedLevel_Insert_ShouldCreateLevelActivity()
|
||||
{
|
||||
ActivityEntityEventHandler eventHandler = new();
|
||||
DatabaseContext database = await MockHelper.GetTestDatabase(new List<UserEntity>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Username = "test",
|
||||
UserId = 1,
|
||||
},
|
||||
});
|
||||
|
||||
SlotEntity slot = new()
|
||||
{
|
||||
SlotId = 1,
|
||||
CreatorId = 1,
|
||||
};
|
||||
database.Slots.Add(slot);
|
||||
await database.SaveChangesAsync();
|
||||
|
||||
HeartedLevelEntity heartedLevel = new()
|
||||
{
|
||||
HeartedLevelId = 1,
|
||||
UserId = 1,
|
||||
SlotId = 1,
|
||||
Slot = slot,
|
||||
};
|
||||
|
||||
eventHandler.OnEntityInserted(database, heartedLevel);
|
||||
|
||||
Assert.NotNull(database.Activities.OfType<LevelActivityEntity>()
|
||||
.FirstOrDefault(a => a.Type == EventType.HeartLevel && a.SlotId == 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HeartedLevel_InsertDuplicate_ShouldRemoveOldActivity()
|
||||
{
|
||||
ActivityEntityEventHandler eventHandler = new();
|
||||
DatabaseContext database = await MockHelper.GetTestDatabase(new List<UserEntity>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Username = "test",
|
||||
UserId = 1,
|
||||
},
|
||||
});
|
||||
|
||||
SlotEntity slot = new()
|
||||
{
|
||||
SlotId = 1,
|
||||
CreatorId = 1,
|
||||
};
|
||||
database.Slots.Add(slot);
|
||||
|
||||
LevelActivityEntity levelActivity = new()
|
||||
{
|
||||
UserId = 1,
|
||||
SlotId = 1,
|
||||
Type = EventType.HeartLevel,
|
||||
Timestamp = DateTime.MinValue,
|
||||
};
|
||||
|
||||
database.Activities.Add(levelActivity);
|
||||
|
||||
await database.SaveChangesAsync();
|
||||
|
||||
HeartedLevelEntity heartedLevel = new()
|
||||
{
|
||||
HeartedLevelId = 1,
|
||||
UserId = 1,
|
||||
SlotId = 1,
|
||||
Slot = slot,
|
||||
};
|
||||
|
||||
Assert.NotNull(database.Activities.OfType<LevelActivityEntity>()
|
||||
.FirstOrDefault(a => a.Type == EventType.HeartLevel && a.SlotId == 1 && a.Timestamp == DateTime.MinValue));
|
||||
|
||||
eventHandler.OnEntityInserted(database, heartedLevel);
|
||||
|
||||
Assert.NotNull(database.Activities.OfType<LevelActivityEntity>()
|
||||
.FirstOrDefault(a => a.Type == EventType.HeartLevel && a.SlotId == 1 && a.Timestamp != DateTime.MinValue));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HeartedProfile_Insert_ShouldCreateUserActivity()
|
||||
{
|
||||
ActivityEntityEventHandler eventHandler = new();
|
||||
DatabaseContext database = await MockHelper.GetTestDatabase(new List<UserEntity>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Username = "test",
|
||||
UserId = 1,
|
||||
},
|
||||
});
|
||||
|
||||
HeartedProfileEntity heartedProfile = new()
|
||||
{
|
||||
HeartedProfileId = 1,
|
||||
UserId = 1,
|
||||
HeartedUserId = 1,
|
||||
};
|
||||
|
||||
eventHandler.OnEntityInserted(database, heartedProfile);
|
||||
|
||||
Assert.NotNull(database.Activities.OfType<UserActivityEntity>()
|
||||
.FirstOrDefault(a => a.Type == EventType.HeartUser && a.TargetUserId == 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HeartedProfile_InsertDuplicate_ShouldRemoveOldActivity()
|
||||
{
|
||||
ActivityEntityEventHandler eventHandler = new();
|
||||
DatabaseContext database = await MockHelper.GetTestDatabase(new List<UserEntity>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Username = "test",
|
||||
UserId = 1,
|
||||
},
|
||||
});
|
||||
|
||||
UserActivityEntity userActivity = new()
|
||||
{
|
||||
UserId = 1,
|
||||
TargetUserId = 1,
|
||||
Type = EventType.HeartUser,
|
||||
Timestamp = DateTime.MinValue,
|
||||
};
|
||||
|
||||
database.Activities.Add(userActivity);
|
||||
await database.SaveChangesAsync();
|
||||
|
||||
Assert.NotNull(database.Activities.OfType<UserActivityEntity>()
|
||||
.FirstOrDefault(a => a.Type == EventType.HeartUser && a.TargetUserId == 1 && a.Timestamp == DateTime.MinValue));
|
||||
|
||||
HeartedProfileEntity heartedProfile = new()
|
||||
{
|
||||
HeartedProfileId = 1,
|
||||
UserId = 1,
|
||||
HeartedUserId = 1,
|
||||
};
|
||||
|
||||
eventHandler.OnEntityInserted(database, heartedProfile);
|
||||
|
||||
Assert.NotNull(database.Activities.OfType<UserActivityEntity>()
|
||||
.FirstOrDefault(a => a.Type == EventType.HeartUser && a.TargetUserId == 1 && a.Timestamp != DateTime.MinValue));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HeartedPlaylist_Insert_ShouldCreatePlaylistActivity()
|
||||
{
|
||||
ActivityEntityEventHandler eventHandler = new();
|
||||
DatabaseContext database = await MockHelper.GetTestDatabase(new List<UserEntity>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Username = "test",
|
||||
UserId = 1,
|
||||
},
|
||||
});
|
||||
|
||||
PlaylistEntity playlist = new()
|
||||
{
|
||||
PlaylistId = 1,
|
||||
CreatorId = 1,
|
||||
};
|
||||
database.Playlists.Add(playlist);
|
||||
await database.SaveChangesAsync();
|
||||
|
||||
HeartedPlaylistEntity heartedPlaylist = new()
|
||||
{
|
||||
HeartedPlaylistId = 1,
|
||||
UserId = 1,
|
||||
PlaylistId = 1,
|
||||
};
|
||||
|
||||
eventHandler.OnEntityInserted(database, heartedPlaylist);
|
||||
|
||||
Assert.NotNull(database.Activities.OfType<PlaylistActivityEntity>()
|
||||
.FirstOrDefault(a => a.Type == EventType.HeartPlaylist && a.PlaylistId == 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HeartedPlaylist_InsertDuplicate_ShouldCreatePlaylistActivity()
|
||||
{
|
||||
ActivityEntityEventHandler eventHandler = new();
|
||||
DatabaseContext database = await MockHelper.GetTestDatabase(new List<UserEntity>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Username = "test",
|
||||
UserId = 1,
|
||||
},
|
||||
});
|
||||
|
||||
PlaylistEntity playlist = new()
|
||||
{
|
||||
PlaylistId = 1,
|
||||
CreatorId = 1,
|
||||
};
|
||||
database.Playlists.Add(playlist);
|
||||
|
||||
PlaylistActivityEntity playlistActivity = new()
|
||||
{
|
||||
UserId = 1,
|
||||
PlaylistId = 1,
|
||||
Type = EventType.HeartPlaylist,
|
||||
Timestamp = DateTime.MinValue,
|
||||
};
|
||||
database.Activities.Add(playlistActivity);
|
||||
|
||||
await database.SaveChangesAsync();
|
||||
|
||||
HeartedPlaylistEntity heartedPlaylist = new()
|
||||
{
|
||||
HeartedPlaylistId = 1,
|
||||
UserId = 1,
|
||||
PlaylistId = 1,
|
||||
};
|
||||
|
||||
Assert.NotNull(database.Activities.OfType<PlaylistActivityEntity>()
|
||||
.FirstOrDefault(a =>
|
||||
a.Type == EventType.HeartPlaylist && a.PlaylistId == 1 && a.Timestamp == DateTime.MinValue));
|
||||
|
||||
eventHandler.OnEntityInserted(database, heartedPlaylist);
|
||||
|
||||
Assert.NotNull(database.Activities.OfType<PlaylistActivityEntity>()
|
||||
.FirstOrDefault(a => a.Type == EventType.HeartPlaylist && a.PlaylistId == 1 && a.Timestamp != DateTime.MinValue));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task VisitedLevel_Insert_ShouldCreateLevelActivity()
|
||||
{
|
||||
ActivityEntityEventHandler eventHandler = new();
|
||||
DatabaseContext database = await MockHelper.GetTestDatabase(new List<UserEntity>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Username = "test",
|
||||
UserId = 1,
|
||||
},
|
||||
});
|
||||
|
||||
SlotEntity slot = new()
|
||||
{
|
||||
SlotId = 1,
|
||||
CreatorId = 1,
|
||||
};
|
||||
database.Slots.Add(slot);
|
||||
await database.SaveChangesAsync();
|
||||
|
||||
VisitedLevelEntity visitedLevel = new()
|
||||
{
|
||||
VisitedLevelId = 1,
|
||||
UserId = 1,
|
||||
SlotId = 1,
|
||||
};
|
||||
|
||||
eventHandler.OnEntityInserted(database, visitedLevel);
|
||||
|
||||
Assert.NotNull(database.Activities.OfType<LevelActivityEntity>()
|
||||
.FirstOrDefault(a => a.Type == EventType.PlayLevel && a.SlotId == 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Review_Insert_ShouldCreateReviewActivity()
|
||||
{
|
||||
ActivityEntityEventHandler eventHandler = new();
|
||||
DatabaseContext database = await MockHelper.GetTestDatabase(new List<UserEntity>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Username = "test",
|
||||
UserId = 1,
|
||||
},
|
||||
});
|
||||
|
||||
SlotEntity slot = new()
|
||||
{
|
||||
SlotId = 1,
|
||||
CreatorId = 1,
|
||||
};
|
||||
database.Slots.Add(slot);
|
||||
|
||||
ReviewEntity review = new()
|
||||
{
|
||||
ReviewId = 1,
|
||||
ReviewerId = 1,
|
||||
SlotId = 1,
|
||||
};
|
||||
database.Reviews.Add(review);
|
||||
await database.SaveChangesAsync();
|
||||
|
||||
eventHandler.OnEntityInserted(database, review);
|
||||
|
||||
Assert.NotNull(database.Activities.OfType<ReviewActivityEntity>()
|
||||
.FirstOrDefault(a => a.Type == EventType.ReviewLevel && a.ReviewId == 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RatedLevel_WithRatingInsert_ShouldCreateLevelActivity()
|
||||
{
|
||||
ActivityEntityEventHandler eventHandler = new();
|
||||
DatabaseContext database = await MockHelper.GetTestDatabase(new List<UserEntity>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Username = "test",
|
||||
UserId = 1,
|
||||
},
|
||||
});
|
||||
|
||||
SlotEntity slot = new()
|
||||
{
|
||||
SlotId = 1,
|
||||
CreatorId = 1,
|
||||
};
|
||||
database.Slots.Add(slot);
|
||||
await database.SaveChangesAsync();
|
||||
|
||||
RatedLevelEntity ratedLevel = new()
|
||||
{
|
||||
RatedLevelId = 1,
|
||||
UserId = 1,
|
||||
SlotId = 1,
|
||||
Rating = 1,
|
||||
};
|
||||
|
||||
eventHandler.OnEntityInserted(database, ratedLevel);
|
||||
|
||||
Assert.NotNull(database.Activities.OfType<LevelActivityEntity>()
|
||||
.FirstOrDefault(a => a.Type == EventType.DpadRateLevel && a.SlotId == 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RatedLevel_WithLBP1RatingInsert_ShouldCreateLevelActivity()
|
||||
{
|
||||
ActivityEntityEventHandler eventHandler = new();
|
||||
DatabaseContext database = await MockHelper.GetTestDatabase(new List<UserEntity>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Username = "test",
|
||||
UserId = 1,
|
||||
},
|
||||
});
|
||||
|
||||
SlotEntity slot = new()
|
||||
{
|
||||
SlotId = 1,
|
||||
CreatorId = 1,
|
||||
};
|
||||
database.Slots.Add(slot);
|
||||
await database.SaveChangesAsync();
|
||||
|
||||
RatedLevelEntity ratedLevel = new()
|
||||
{
|
||||
RatedLevelId = 1,
|
||||
UserId = 1,
|
||||
SlotId = 1,
|
||||
RatingLBP1 = 5,
|
||||
};
|
||||
|
||||
eventHandler.OnEntityInserted(database, ratedLevel);
|
||||
|
||||
Assert.NotNull(database.Activities.OfType<LevelActivityEntity>()
|
||||
.FirstOrDefault(a => a.Type == EventType.RateLevel && a.SlotId == 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Playlist_Insert_ShouldCreateLevelActivity()
|
||||
{
|
||||
ActivityEntityEventHandler eventHandler = new();
|
||||
DatabaseContext database = await MockHelper.GetTestDatabase(new List<UserEntity>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Username = "test",
|
||||
UserId = 1,
|
||||
},
|
||||
});
|
||||
|
||||
PlaylistEntity playlist = new()
|
||||
{
|
||||
PlaylistId = 1,
|
||||
CreatorId = 1,
|
||||
};
|
||||
database.Playlists.Add(playlist);
|
||||
await database.SaveChangesAsync();
|
||||
|
||||
eventHandler.OnEntityInserted(database, playlist);
|
||||
|
||||
Assert.NotNull(database.Activities.OfType<PlaylistActivityEntity>()
|
||||
.FirstOrDefault(a => a.Type == EventType.CreatePlaylist && a.PlaylistId == 1));
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Entity changes
|
||||
[Fact]
|
||||
public async Task VisitedLevel_WithNoChange_ShouldNotCreateLevelActivity()
|
||||
{
|
||||
ActivityEntityEventHandler eventHandler = new();
|
||||
DatabaseContext database = await MockHelper.GetTestDatabase(new List<UserEntity>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Username = "test",
|
||||
UserId = 1,
|
||||
},
|
||||
});
|
||||
|
||||
SlotEntity slot = new()
|
||||
{
|
||||
SlotId = 1,
|
||||
CreatorId = 1,
|
||||
};
|
||||
database.Slots.Add(slot);
|
||||
await database.SaveChangesAsync();
|
||||
|
||||
VisitedLevelEntity visitedLevel = new()
|
||||
{
|
||||
VisitedLevelId = 1,
|
||||
UserId = 1,
|
||||
SlotId = 1,
|
||||
};
|
||||
|
||||
eventHandler.OnEntityChanged(database, visitedLevel, visitedLevel);
|
||||
|
||||
Assert.Null(database.Activities.OfType<LevelActivityEntity>()
|
||||
.FirstOrDefault(a => a.Type == EventType.PlayLevel && a.SlotId == 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task VisitedLevel_WithChange_ShouldCreateLevelActivity()
|
||||
{
|
||||
ActivityEntityEventHandler eventHandler = new();
|
||||
DatabaseContext database = await MockHelper.GetTestDatabase(new List<UserEntity>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Username = "test",
|
||||
UserId = 1,
|
||||
},
|
||||
});
|
||||
|
||||
SlotEntity slot = new()
|
||||
{
|
||||
SlotId = 1,
|
||||
CreatorId = 1,
|
||||
};
|
||||
database.Slots.Add(slot);
|
||||
await database.SaveChangesAsync();
|
||||
|
||||
VisitedLevelEntity oldVisitedLevel = new()
|
||||
{
|
||||
VisitedLevelId = 1,
|
||||
UserId = 1,
|
||||
SlotId = 1,
|
||||
PlaysLBP2 = 1,
|
||||
};
|
||||
|
||||
VisitedLevelEntity newVisitedLevel = new()
|
||||
{
|
||||
VisitedLevelId = 1,
|
||||
UserId = 1,
|
||||
SlotId = 1,
|
||||
PlaysLBP2 = 2,
|
||||
};
|
||||
|
||||
eventHandler.OnEntityChanged(database, oldVisitedLevel, newVisitedLevel);
|
||||
|
||||
Assert.NotNull(database.Activities.OfType<LevelActivityEntity>()
|
||||
.FirstOrDefault(a => a.Type == EventType.PlayLevel && a.SlotId == 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Slot_WithTeamPickChange_ShouldCreateLevelActivity()
|
||||
{
|
||||
ActivityEntityEventHandler eventHandler = new();
|
||||
DatabaseContext database = await MockHelper.GetTestDatabase(new List<UserEntity>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Username = "test",
|
||||
UserId = 1,
|
||||
},
|
||||
});
|
||||
|
||||
SlotEntity oldSlot = new()
|
||||
{
|
||||
SlotId = 1,
|
||||
CreatorId = 1,
|
||||
};
|
||||
database.Slots.Add(oldSlot);
|
||||
await database.SaveChangesAsync();
|
||||
|
||||
SlotEntity newSlot = new()
|
||||
{
|
||||
SlotId = 1,
|
||||
CreatorId = 1,
|
||||
TeamPickTime = 1,
|
||||
};
|
||||
|
||||
eventHandler.OnEntityChanged(database, oldSlot, newSlot);
|
||||
|
||||
Assert.NotNull(database.Activities.ToList().OfType<LevelActivityEntity>()
|
||||
.FirstOrDefault(a => a.Type == EventType.MMPickLevel && a.SlotId == 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Slot_WithRepublish_ShouldCreateLevelActivity()
|
||||
{
|
||||
ActivityEntityEventHandler eventHandler = new();
|
||||
DatabaseContext database = await MockHelper.GetTestDatabase(new List<UserEntity>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Username = "test",
|
||||
UserId = 1,
|
||||
},
|
||||
});
|
||||
|
||||
SlotEntity oldSlot = new()
|
||||
{
|
||||
SlotId = 1,
|
||||
CreatorId = 1,
|
||||
};
|
||||
|
||||
database.Slots.Add(oldSlot);
|
||||
await database.SaveChangesAsync();
|
||||
|
||||
SlotEntity newSlot = new()
|
||||
{
|
||||
SlotId = 1,
|
||||
CreatorId = 1,
|
||||
LastUpdated = 1,
|
||||
};
|
||||
|
||||
eventHandler.OnEntityChanged(database, oldSlot, newSlot);
|
||||
|
||||
Assert.NotNull(database.Activities.ToList().OfType<LevelActivityEntity>()
|
||||
.FirstOrDefault(a => a.Type == EventType.PublishLevel && a.SlotId == 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Comment_WithDeletion_ShouldCreateCommentActivity()
|
||||
{
|
||||
ActivityEntityEventHandler eventHandler = new();
|
||||
DatabaseContext database = await MockHelper.GetTestDatabase(new List<UserEntity>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Username = "test",
|
||||
UserId = 1,
|
||||
},
|
||||
});
|
||||
|
||||
CommentEntity oldComment = new()
|
||||
{
|
||||
CommentId = 1,
|
||||
PosterUserId = 1,
|
||||
Type = CommentType.Level,
|
||||
};
|
||||
|
||||
database.Comments.Add(oldComment);
|
||||
await database.SaveChangesAsync();
|
||||
|
||||
CommentEntity newComment = new()
|
||||
{
|
||||
CommentId = 1,
|
||||
PosterUserId = 1,
|
||||
Type = CommentType.Level,
|
||||
Deleted = true,
|
||||
};
|
||||
|
||||
eventHandler.OnEntityChanged(database, oldComment, newComment);
|
||||
|
||||
Assert.NotNull(database.Activities.ToList()
|
||||
.FirstOrDefault(a => a.Type == EventType.DeleteLevelComment && ((CommentActivityEntity)a).CommentId == 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Playlist_WithSlotsChanged_ShouldCreatePlaylistWithSlotActivity()
|
||||
{
|
||||
ActivityEntityEventHandler eventHandler = new();
|
||||
DatabaseContext database = await MockHelper.GetTestDatabase(new List<UserEntity>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Username = "test",
|
||||
UserId = 1,
|
||||
},
|
||||
});
|
||||
|
||||
SlotEntity slot = new()
|
||||
{
|
||||
SlotId = 1,
|
||||
CreatorId = 1,
|
||||
};
|
||||
database.Slots.Add(slot);
|
||||
|
||||
PlaylistEntity oldPlaylist = new()
|
||||
{
|
||||
PlaylistId = 1,
|
||||
CreatorId = 1,
|
||||
};
|
||||
|
||||
database.Playlists.Add(oldPlaylist);
|
||||
await database.SaveChangesAsync();
|
||||
|
||||
PlaylistEntity newPlaylist = new()
|
||||
{
|
||||
PlaylistId = 1,
|
||||
CreatorId = 1,
|
||||
SlotCollection = "1",
|
||||
};
|
||||
|
||||
eventHandler.OnEntityChanged(database, oldPlaylist, newPlaylist);
|
||||
|
||||
Assert.NotNull(database.Activities.OfType<PlaylistWithSlotActivityEntity>()
|
||||
.FirstOrDefault(a => a.Type == EventType.AddLevelToPlaylist && a.PlaylistId == 1 && a.SlotId == 1));
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Entity deletion
|
||||
[Fact]
|
||||
public async Task HeartedLevel_Delete_ShouldCreateLevelActivity()
|
||||
{
|
||||
ActivityEntityEventHandler eventHandler = new();
|
||||
DatabaseContext database = await MockHelper.GetTestDatabase(new List<UserEntity>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Username = "test",
|
||||
UserId = 1,
|
||||
},
|
||||
});
|
||||
|
||||
SlotEntity slot = new()
|
||||
{
|
||||
SlotId = 1,
|
||||
CreatorId = 1,
|
||||
};
|
||||
database.Slots.Add(slot);
|
||||
|
||||
HeartedLevelEntity heartedLevel = new()
|
||||
{
|
||||
HeartedLevelId = 1,
|
||||
UserId = 1,
|
||||
SlotId = 1,
|
||||
};
|
||||
|
||||
database.HeartedLevels.Add(heartedLevel);
|
||||
await database.SaveChangesAsync();
|
||||
|
||||
eventHandler.OnEntityDeleted(database, heartedLevel);
|
||||
|
||||
Assert.NotNull(database.Activities.OfType<LevelActivityEntity>()
|
||||
.FirstOrDefault(a => a.Type == EventType.UnheartLevel && a.SlotId == 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HeartedLevel_DeleteDuplicate_ShouldRemoveOldActivity()
|
||||
{
|
||||
ActivityEntityEventHandler eventHandler = new();
|
||||
DatabaseContext database = await MockHelper.GetTestDatabase(new List<UserEntity>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Username = "test",
|
||||
UserId = 1,
|
||||
},
|
||||
});
|
||||
|
||||
SlotEntity slot = new()
|
||||
{
|
||||
SlotId = 1,
|
||||
CreatorId = 1,
|
||||
};
|
||||
database.Slots.Add(slot);
|
||||
|
||||
LevelActivityEntity levelActivity = new()
|
||||
{
|
||||
UserId = 1,
|
||||
SlotId = 1,
|
||||
Type = EventType.UnheartLevel,
|
||||
Timestamp = DateTime.MinValue,
|
||||
};
|
||||
|
||||
database.Activities.Add(levelActivity);
|
||||
|
||||
HeartedLevelEntity heartedLevel = new()
|
||||
{
|
||||
HeartedLevelId = 1,
|
||||
UserId = 1,
|
||||
SlotId = 1,
|
||||
};
|
||||
|
||||
database.HeartedLevels.Add(heartedLevel);
|
||||
await database.SaveChangesAsync();
|
||||
|
||||
eventHandler.OnEntityDeleted(database, heartedLevel);
|
||||
|
||||
Assert.NotNull(database.Activities.OfType<LevelActivityEntity>()
|
||||
.FirstOrDefault(a => a.Type == EventType.UnheartLevel && a.SlotId == 1 && a.Timestamp != DateTime.MinValue));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HeartedProfile_Delete_ShouldCreateLevelActivity()
|
||||
{
|
||||
ActivityEntityEventHandler eventHandler = new();
|
||||
DatabaseContext database = await MockHelper.GetTestDatabase(new List<UserEntity>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Username = "test",
|
||||
UserId = 1,
|
||||
},
|
||||
});
|
||||
|
||||
SlotEntity slot = new()
|
||||
{
|
||||
SlotId = 1,
|
||||
CreatorId = 1,
|
||||
};
|
||||
database.Slots.Add(slot);
|
||||
|
||||
HeartedProfileEntity heartedProfile = new()
|
||||
{
|
||||
HeartedProfileId = 1,
|
||||
UserId = 1,
|
||||
HeartedUserId = 1,
|
||||
};
|
||||
|
||||
database.HeartedProfiles.Add(heartedProfile);
|
||||
await database.SaveChangesAsync();
|
||||
|
||||
eventHandler.OnEntityDeleted(database, heartedProfile);
|
||||
|
||||
Assert.NotNull(database.Activities.OfType<UserActivityEntity>()
|
||||
.FirstOrDefault(a => a.Type == EventType.UnheartUser && a.UserId == 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HeartedProfile_DeleteDuplicate_ShouldCreateLevelActivity()
|
||||
{
|
||||
ActivityEntityEventHandler eventHandler = new();
|
||||
DatabaseContext database = await MockHelper.GetTestDatabase(new List<UserEntity>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Username = "test",
|
||||
UserId = 1,
|
||||
},
|
||||
});
|
||||
|
||||
UserActivityEntity userActivity = new()
|
||||
{
|
||||
UserId = 1,
|
||||
TargetUserId = 1,
|
||||
Type = EventType.UnheartUser,
|
||||
Timestamp = DateTime.MinValue,
|
||||
};
|
||||
database.Activities.Add(userActivity);
|
||||
|
||||
HeartedProfileEntity heartedProfile = new()
|
||||
{
|
||||
HeartedProfileId = 1,
|
||||
UserId = 1,
|
||||
HeartedUserId = 1,
|
||||
};
|
||||
|
||||
database.HeartedProfiles.Add(heartedProfile);
|
||||
await database.SaveChangesAsync();
|
||||
|
||||
Assert.NotNull(database.Activities.OfType<UserActivityEntity>()
|
||||
.FirstOrDefault(a => a.Type == EventType.UnheartUser && a.UserId == 1 && a.Timestamp == DateTime.MinValue));
|
||||
|
||||
eventHandler.OnEntityDeleted(database, heartedProfile);
|
||||
|
||||
Assert.NotNull(database.Activities.OfType<UserActivityEntity>()
|
||||
.FirstOrDefault(a => a.Type == EventType.UnheartUser && a.UserId == 1 && a.Timestamp != DateTime.MinValue));
|
||||
}
|
||||
#endregion
|
||||
}
|
|
@ -0,0 +1,318 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using LBPUnion.ProjectLighthouse.Database;
|
||||
using LBPUnion.ProjectLighthouse.Extensions;
|
||||
using LBPUnion.ProjectLighthouse.Tests.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Types.Activity;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Activity;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Website;
|
||||
using LBPUnion.ProjectLighthouse.Types.Users;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Xunit;
|
||||
|
||||
namespace ProjectLighthouse.Tests.GameApiTests.Unit.Activity;
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
public class ActivityGroupingTests
|
||||
{
|
||||
[Fact]
|
||||
public void ToOuterActivityGroups_ShouldCreateGroupPerObject_WhenGroupedBy_ObjectThenActor()
|
||||
{
|
||||
List<ActivityEntity> activities = [
|
||||
new LevelActivityEntity
|
||||
{
|
||||
UserId = 1,
|
||||
SlotId = 1,
|
||||
Slot = new SlotEntity
|
||||
{
|
||||
GameVersion = GameVersion.LittleBigPlanet2,
|
||||
},
|
||||
Timestamp = DateTime.Now,
|
||||
Type = EventType.PlayLevel,
|
||||
},
|
||||
new LevelActivityEntity
|
||||
{
|
||||
UserId = 1,
|
||||
SlotId = 1,
|
||||
Slot = new SlotEntity
|
||||
{
|
||||
GameVersion = GameVersion.LittleBigPlanet2,
|
||||
},
|
||||
Timestamp = DateTime.Now,
|
||||
Type = EventType.ReviewLevel,
|
||||
},
|
||||
new LevelActivityEntity
|
||||
{
|
||||
UserId = 2,
|
||||
SlotId = 1,
|
||||
Slot = new SlotEntity
|
||||
{
|
||||
GameVersion = GameVersion.LittleBigPlanet2,
|
||||
},
|
||||
Timestamp = DateTime.Now,
|
||||
Type = EventType.PlayLevel,
|
||||
},
|
||||
new UserActivityEntity
|
||||
{
|
||||
TargetUserId = 2,
|
||||
UserId = 1,
|
||||
Type = EventType.HeartUser,
|
||||
Timestamp = DateTime.Now,
|
||||
},
|
||||
new UserActivityEntity
|
||||
{
|
||||
TargetUserId = 2,
|
||||
UserId = 1,
|
||||
Type = EventType.CommentOnUser,
|
||||
Timestamp = DateTime.Now,
|
||||
},
|
||||
new UserActivityEntity
|
||||
{
|
||||
TargetUserId = 1,
|
||||
UserId = 2,
|
||||
Type = EventType.HeartUser,
|
||||
Timestamp = DateTime.Now,
|
||||
},
|
||||
new UserActivityEntity
|
||||
{
|
||||
TargetUserId = 1,
|
||||
UserId = 2,
|
||||
Type = EventType.CommentOnUser,
|
||||
Timestamp = DateTime.Now,
|
||||
},
|
||||
];
|
||||
|
||||
List<OuterActivityGroup> groups = activities.ToActivityDto().AsQueryable().ToActivityGroups().ToList().ToOuterActivityGroups();
|
||||
Assert.NotNull(groups);
|
||||
Assert.Equal(3, groups.Count);
|
||||
|
||||
Assert.Equal(ActivityGroupType.User, groups.ElementAt(0).Key.GroupType);
|
||||
Assert.Equal(ActivityGroupType.User, groups.ElementAt(1).Key.GroupType);
|
||||
Assert.Equal(ActivityGroupType.Level, groups.ElementAt(2).Key.GroupType);
|
||||
|
||||
Assert.Equal(1, groups.ElementAt(0).Key.TargetUserId);
|
||||
Assert.Equal(2, groups.ElementAt(1).Key.TargetUserId);
|
||||
Assert.Equal(1, groups.ElementAt(2).Key.TargetSlotId);
|
||||
|
||||
Assert.Single(groups.ElementAt(0).Groups);
|
||||
Assert.Single(groups.ElementAt(1).Groups);
|
||||
Assert.Equal(2, groups.ElementAt(2).Groups.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToOuterActivityGroups_ShouldCreateGroupPerObject_WhenGroupedBy_ActorThenObject()
|
||||
{
|
||||
List<ActivityEntity> activities = [
|
||||
new LevelActivityEntity
|
||||
{
|
||||
UserId = 1,
|
||||
SlotId = 1,
|
||||
Slot = new SlotEntity
|
||||
{
|
||||
GameVersion = GameVersion.LittleBigPlanet2,
|
||||
},
|
||||
Timestamp = DateTime.Now,
|
||||
Type = EventType.PlayLevel,
|
||||
},
|
||||
new LevelActivityEntity
|
||||
{
|
||||
UserId = 1,
|
||||
SlotId = 1,
|
||||
Slot = new SlotEntity
|
||||
{
|
||||
GameVersion = GameVersion.LittleBigPlanet2,
|
||||
},
|
||||
Timestamp = DateTime.Now,
|
||||
Type = EventType.ReviewLevel,
|
||||
},
|
||||
new LevelActivityEntity
|
||||
{
|
||||
UserId = 2,
|
||||
SlotId = 1,
|
||||
Slot = new SlotEntity
|
||||
{
|
||||
GameVersion = GameVersion.LittleBigPlanet2,
|
||||
},
|
||||
Timestamp = DateTime.Now,
|
||||
Type = EventType.PlayLevel,
|
||||
},
|
||||
new UserActivityEntity
|
||||
{
|
||||
TargetUserId = 2,
|
||||
UserId = 1,
|
||||
Type = EventType.HeartUser,
|
||||
Timestamp = DateTime.Now,
|
||||
},
|
||||
new UserActivityEntity
|
||||
{
|
||||
TargetUserId = 2,
|
||||
UserId = 1,
|
||||
Type = EventType.CommentOnUser,
|
||||
Timestamp = DateTime.Now,
|
||||
},
|
||||
new UserActivityEntity
|
||||
{
|
||||
TargetUserId = 1,
|
||||
UserId = 2,
|
||||
Type = EventType.HeartUser,
|
||||
Timestamp = DateTime.Now,
|
||||
},
|
||||
new UserActivityEntity
|
||||
{
|
||||
TargetUserId = 1,
|
||||
UserId = 2,
|
||||
Type = EventType.CommentOnUser,
|
||||
Timestamp = DateTime.Now,
|
||||
},
|
||||
];
|
||||
List<OuterActivityGroup> groups = activities.ToActivityDto()
|
||||
.AsQueryable()
|
||||
.ToActivityGroups(true)
|
||||
.ToList()
|
||||
.ToOuterActivityGroups(true);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.NotNull(groups);
|
||||
Assert.Equal(2, groups.Count);
|
||||
Assert.Equal(1, groups.Count(g => g.Key.UserId == 1));
|
||||
Assert.Equal(1, groups.Count(g => g.Key.UserId == 2));
|
||||
OuterActivityGroup firstUserGroup = groups.FirstOrDefault(g => g.Key.UserId == 1);
|
||||
OuterActivityGroup secondUserGroup = groups.FirstOrDefault(g => g.Key.UserId == 2);
|
||||
Assert.NotNull(firstUserGroup.Groups);
|
||||
Assert.NotNull(secondUserGroup.Groups);
|
||||
|
||||
Assert.Equal(ActivityGroupType.User, firstUserGroup.Key.GroupType);
|
||||
Assert.Equal(ActivityGroupType.User, secondUserGroup.Key.GroupType);
|
||||
|
||||
Assert.True(firstUserGroup.Groups.All(g => g.Key.UserId == 1));
|
||||
Assert.True(secondUserGroup.Groups.All(g => g.Key.UserId == 2));
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ToActivityDtoTest()
|
||||
{
|
||||
DatabaseContext db = await MockHelper.GetTestDatabase();
|
||||
db.Slots.Add(new SlotEntity
|
||||
{
|
||||
SlotId = 1,
|
||||
CreatorId = 1,
|
||||
GameVersion = GameVersion.LittleBigPlanet2,
|
||||
});
|
||||
db.Slots.Add(new SlotEntity
|
||||
{
|
||||
SlotId = 2,
|
||||
CreatorId = 1,
|
||||
GameVersion = GameVersion.LittleBigPlanet2,
|
||||
TeamPickTime = 1,
|
||||
});
|
||||
db.Reviews.Add(new ReviewEntity
|
||||
{
|
||||
Timestamp = DateTime.Now.ToUnixTimeMilliseconds(),
|
||||
SlotId = 1,
|
||||
ReviewerId = 1,
|
||||
ReviewId = 1,
|
||||
});
|
||||
db.Comments.Add(new CommentEntity
|
||||
{
|
||||
TargetSlotId = 1,
|
||||
PosterUserId = 1,
|
||||
Message = "comment on level test",
|
||||
CommentId = 1,
|
||||
});
|
||||
db.Comments.Add(new CommentEntity
|
||||
{
|
||||
TargetUserId = 1,
|
||||
PosterUserId = 1,
|
||||
Message = "comment on user test",
|
||||
CommentId = 2,
|
||||
});
|
||||
db.WebsiteAnnouncements.Add(new WebsiteAnnouncementEntity
|
||||
{
|
||||
PublisherId = 1,
|
||||
AnnouncementId = 1,
|
||||
});
|
||||
db.Playlists.Add(new PlaylistEntity
|
||||
{
|
||||
PlaylistId = 1,
|
||||
CreatorId = 1,
|
||||
});
|
||||
db.Activities.Add(new LevelActivityEntity
|
||||
{
|
||||
Timestamp = DateTime.Now,
|
||||
SlotId = 1,
|
||||
Type = EventType.PlayLevel,
|
||||
UserId = 1,
|
||||
});
|
||||
db.Activities.Add(new ReviewActivityEntity
|
||||
{
|
||||
Timestamp = DateTime.Now,
|
||||
SlotId = 1,
|
||||
Type = EventType.ReviewLevel,
|
||||
ReviewId = 1,
|
||||
UserId = 1,
|
||||
});
|
||||
db.Activities.Add(new UserCommentActivityEntity
|
||||
{
|
||||
Timestamp = DateTime.Now,
|
||||
Type = EventType.CommentOnUser,
|
||||
UserId = 1,
|
||||
TargetUserId = 1,
|
||||
CommentId = 2,
|
||||
});
|
||||
db.Activities.Add(new LevelCommentActivityEntity
|
||||
{
|
||||
Timestamp = DateTime.Now,
|
||||
Type = EventType.CommentOnLevel,
|
||||
UserId = 1,
|
||||
SlotId = 1,
|
||||
CommentId = 1,
|
||||
});
|
||||
db.Activities.Add(new NewsActivityEntity
|
||||
{
|
||||
Type = EventType.NewsPost,
|
||||
NewsId = 1,
|
||||
UserId = 1,
|
||||
});
|
||||
db.Activities.Add(new PlaylistActivityEntity
|
||||
{
|
||||
Type = EventType.CreatePlaylist,
|
||||
PlaylistId = 1,
|
||||
UserId = 1,
|
||||
});
|
||||
db.Activities.Add(new LevelActivityEntity
|
||||
{
|
||||
Type = EventType.MMPickLevel,
|
||||
SlotId = 2,
|
||||
UserId = 1,
|
||||
});
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
List<ActivityDto> resultDto = await db.Activities.ToActivityDto(includeSlotCreator: true, includeTeamPick: true).ToListAsync();
|
||||
|
||||
Assert.Equal(2, resultDto.FirstOrDefault(a => a.Activity.Type == EventType.MMPickLevel)?.TargetTeamPickId);
|
||||
Assert.Equal(2, resultDto.FirstOrDefault(a => a.Activity.Type == EventType.MMPickLevel)?.TargetSlotId);
|
||||
Assert.Equal(1, resultDto.FirstOrDefault(a => a.Activity.Type == EventType.MMPickLevel)?.TargetSlotCreatorId);
|
||||
|
||||
Assert.Equal(1, resultDto.FirstOrDefault(a => a.Activity.Type == EventType.CreatePlaylist)?.TargetPlaylistId);
|
||||
|
||||
Assert.Equal(1, resultDto.FirstOrDefault(a => a.Activity.Type == EventType.NewsPost)?.TargetNewsId);
|
||||
|
||||
Assert.Equal(1, resultDto.FirstOrDefault(a => a.Activity.Type == EventType.CommentOnUser)?.TargetUserId);
|
||||
|
||||
Assert.Null(resultDto.FirstOrDefault(a => a.Activity.Type == EventType.CommentOnLevel)?.TargetTeamPickId);
|
||||
Assert.Equal(1, resultDto.FirstOrDefault(a => a.Activity.Type == EventType.CommentOnLevel)?.TargetSlotId);
|
||||
Assert.Equal(1, resultDto.FirstOrDefault(a => a.Activity.Type == EventType.CommentOnLevel)?.TargetSlotCreatorId);
|
||||
|
||||
Assert.Equal(1, resultDto.FirstOrDefault(a => a.Activity.Type == EventType.ReviewLevel)?.TargetSlotId);
|
||||
Assert.Equal(1, resultDto.FirstOrDefault(a => a.Activity.Type == EventType.ReviewLevel)?.TargetSlotCreatorId);
|
||||
|
||||
Assert.Equal(1, resultDto.FirstOrDefault(a => a.Activity.Type == EventType.PlayLevel)?.TargetSlotId);
|
||||
Assert.Equal(1, resultDto.FirstOrDefault(a => a.Activity.Type == EventType.PlayLevel)?.TargetSlotCreatorId);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
using System.Threading.Tasks;
|
||||
using LBPUnion.ProjectLighthouse.Database;
|
||||
using LBPUnion.ProjectLighthouse.Tests.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Types.Activity;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace ProjectLighthouse.Tests.GameApiTests.Unit.Activity;
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
public class ActivityInterceptorTests
|
||||
{
|
||||
private static async Task<DatabaseContext> GetTestDatabase(IMock<IEntityEventHandler> eventHandlerMock)
|
||||
{
|
||||
DbContextOptionsBuilder<DatabaseContext> optionsBuilder = await MockHelper.GetInMemoryDbOptions();
|
||||
|
||||
optionsBuilder.AddInterceptors(new ActivityInterceptor(eventHandlerMock.Object));
|
||||
DatabaseContext database = new(optionsBuilder.Options);
|
||||
await database.Database.EnsureCreatedAsync();
|
||||
return database;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SaveChangesWithNewEntity_ShouldCallEntityInserted()
|
||||
{
|
||||
Mock<IEntityEventHandler> eventHandlerMock = new();
|
||||
DatabaseContext database = await GetTestDatabase(eventHandlerMock);
|
||||
|
||||
database.Users.Add(new UserEntity
|
||||
{
|
||||
UserId = 1,
|
||||
Username = "test",
|
||||
});
|
||||
await database.SaveChangesAsync();
|
||||
|
||||
eventHandlerMock.Verify(x => x.OnEntityInserted(It.IsAny<DatabaseContext>(), It.Is<object>(user => user is UserEntity)), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SaveChangesWithModifiedEntity_ShouldCallEntityChanged()
|
||||
{
|
||||
Mock<IEntityEventHandler> eventHandlerMock = new();
|
||||
DatabaseContext database = await GetTestDatabase(eventHandlerMock);
|
||||
|
||||
UserEntity user = new()
|
||||
{
|
||||
Username = "test",
|
||||
};
|
||||
|
||||
database.Users.Add(user);
|
||||
await database.SaveChangesAsync();
|
||||
|
||||
user.Username = "test2";
|
||||
await database.SaveChangesAsync();
|
||||
|
||||
eventHandlerMock.Verify(x => x.OnEntityChanged(It.IsAny<DatabaseContext>(),
|
||||
It.Is<object>(u => u is UserEntity && ((UserEntity)u).Username == "test"),
|
||||
It.Is<object>(u => u is UserEntity && ((UserEntity)u).Username == "test2")),
|
||||
Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SaveChangesWithModifiedEntity_ShouldCallEntityDeleted()
|
||||
{
|
||||
Mock<IEntityEventHandler> eventHandlerMock = new();
|
||||
DatabaseContext database = await GetTestDatabase(eventHandlerMock);
|
||||
|
||||
UserEntity user = new()
|
||||
{
|
||||
Username = "test",
|
||||
};
|
||||
|
||||
database.Users.Add(user);
|
||||
await database.SaveChangesAsync();
|
||||
|
||||
database.Users.Remove(user);
|
||||
await database.SaveChangesAsync();
|
||||
|
||||
eventHandlerMock.Verify(x => x.OnEntityDeleted(It.IsAny<DatabaseContext>(), It.Is<object>(u => u is UserEntity)), Times.Once);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
using System.Threading.Tasks;
|
||||
using LBPUnion.ProjectLighthouse.Database;
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers;
|
||||
using LBPUnion.ProjectLighthouse.Tests.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization.Activity;
|
||||
using LBPUnion.ProjectLighthouse.Types.Users;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Xunit;
|
||||
|
||||
namespace ProjectLighthouse.Tests.GameApiTests.Unit.Controllers;
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
public class ActivityControllerTests
|
||||
{
|
||||
private static void SetupToken(ControllerBase controller, GameVersion version)
|
||||
{
|
||||
GameTokenEntity token = MockHelper.GetUnitTestToken();
|
||||
token.GameVersion = version;
|
||||
controller.SetupTestController(token);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task LBP2GlobalActivity_ShouldReturnNothing_WhenEmpty()
|
||||
{
|
||||
DatabaseContext database = await MockHelper.GetTestDatabase();
|
||||
ActivityController activityController = new(database);
|
||||
SetupToken(activityController, GameVersion.LittleBigPlanet2);
|
||||
|
||||
long timestamp = TimeHelper.TimestampMillis;
|
||||
|
||||
IActionResult response = await activityController.GlobalActivity(timestamp, 0, false, false, false, false, false);
|
||||
GameStream stream = response.CastTo<OkObjectResult, GameStream>();
|
||||
Assert.Null(stream.Groups);
|
||||
Assert.Equal(timestamp, stream.StartTimestamp);
|
||||
}
|
||||
//TODO write activity controller tests
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
using LBPUnion.ProjectLighthouse.Filter;
|
||||
using LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
using LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
using LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Slots;
|
||||
using LBPUnion.ProjectLighthouse.Servers.GameServer.Extensions;
|
||||
using LBPUnion.ProjectLighthouse.Tests.Helpers;
|
||||
|
|
|
@ -6,7 +6,7 @@ using LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Slots;
|
|||
using LBPUnion.ProjectLighthouse.Tests.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization.Review;
|
||||
using LBPUnion.ProjectLighthouse.Types.Users;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Xunit;
|
||||
|
|
|
@ -11,7 +11,7 @@ using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
|
|||
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
|
||||
using LBPUnion.ProjectLighthouse.Types.Levels;
|
||||
using LBPUnion.ProjectLighthouse.Types.Matchmaking.Rooms;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization.Slot;
|
||||
using LBPUnion.ProjectLighthouse.Types.Users;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Xunit;
|
||||
|
|
|
@ -4,7 +4,7 @@ using LBPUnion.ProjectLighthouse.Database;
|
|||
using LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers;
|
||||
using LBPUnion.ProjectLighthouse.Tests.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization.Slot;
|
||||
using LBPUnion.ProjectLighthouse.Types.Users;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Xunit;
|
||||
|
|
|
@ -5,7 +5,7 @@ using LBPUnion.ProjectLighthouse.Database;
|
|||
using LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers;
|
||||
using LBPUnion.ProjectLighthouse.Tests.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization.User;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Xunit;
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ public static class MockHelper
|
|||
return finalResult;
|
||||
}
|
||||
|
||||
private static async Task<DbContextOptionsBuilder<DatabaseContext>> GetInMemoryDbOptions()
|
||||
public static async Task<DbContextOptionsBuilder<DatabaseContext>> GetInMemoryDbOptions()
|
||||
{
|
||||
DbConnection connection = new SqliteConnection("DataSource=:memory:");
|
||||
await connection.OpenAsync();
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using LBPUnion.ProjectLighthouse.Filter;
|
||||
using LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
using LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
|
||||
using LBPUnion.ProjectLighthouse.Types.Levels;
|
||||
|
|
|
@ -3,7 +3,7 @@ using System.Xml.Serialization;
|
|||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
|
||||
using LBPUnion.ProjectLighthouse.Types.Misc;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization.Slot;
|
||||
using Xunit;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Tests.Unit;
|
||||
|
|
|
@ -3,7 +3,7 @@ using System.Linq;
|
|||
using LBPUnion.ProjectLighthouse.Configuration;
|
||||
using LBPUnion.ProjectLighthouse.Extensions;
|
||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization.User;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Xunit;
|
||||
|
|
151
ProjectLighthouse/Database/ActivityInterceptor.cs
Normal file
151
ProjectLighthouse/Database/ActivityInterceptor.cs
Normal file
|
@ -0,0 +1,151 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using LBPUnion.ProjectLighthouse.Types.Activity;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Activity;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.ChangeTracking;
|
||||
using Microsoft.EntityFrameworkCore.Diagnostics;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Database;
|
||||
|
||||
public class ActivityInterceptor : SaveChangesInterceptor
|
||||
{
|
||||
private class CustomTrackedEntity
|
||||
{
|
||||
public required EntityState State { get; init; }
|
||||
public required object Entity { get; init; }
|
||||
public required object OldEntity { get; init; }
|
||||
}
|
||||
|
||||
private struct TrackedEntityKey
|
||||
{
|
||||
public Type Type { get; set; }
|
||||
public int HashCode { get; set; }
|
||||
public Guid ContextId { get; set; }
|
||||
}
|
||||
|
||||
private readonly ConcurrentDictionary<TrackedEntityKey, CustomTrackedEntity> unsavedEntities;
|
||||
private readonly IEntityEventHandler eventHandler;
|
||||
|
||||
public ActivityInterceptor(IEntityEventHandler eventHandler)
|
||||
{
|
||||
this.eventHandler = eventHandler;
|
||||
this.unsavedEntities = new ConcurrentDictionary<TrackedEntityKey, CustomTrackedEntity>();
|
||||
}
|
||||
|
||||
#region Hooking stuff
|
||||
|
||||
public override InterceptionResult<int> SavingChanges(DbContextEventData eventData, InterceptionResult<int> result)
|
||||
{
|
||||
this.SaveNewEntities(eventData);
|
||||
return base.SavingChanges(eventData, result);
|
||||
}
|
||||
|
||||
public override ValueTask<InterceptionResult<int>> SavingChangesAsync
|
||||
(DbContextEventData eventData, InterceptionResult<int> result, CancellationToken cancellationToken = new())
|
||||
{
|
||||
this.SaveNewEntities(eventData);
|
||||
return base.SavingChangesAsync(eventData, result, cancellationToken);
|
||||
}
|
||||
|
||||
public override int SavedChanges(SaveChangesCompletedEventData eventData, int result)
|
||||
{
|
||||
this.ParseInsertedEntities(eventData);
|
||||
return base.SavedChanges(eventData, result);
|
||||
}
|
||||
|
||||
public override ValueTask<int> SavedChangesAsync
|
||||
(SaveChangesCompletedEventData eventData, int result, CancellationToken cancellationToken = new())
|
||||
{
|
||||
this.ParseInsertedEntities(eventData);
|
||||
return base.SavedChangesAsync(eventData, result, cancellationToken);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void SaveNewEntities(DbContextEventData eventData)
|
||||
{
|
||||
if (eventData.Context == null) return;
|
||||
|
||||
DbContext context = eventData.Context;
|
||||
|
||||
this.unsavedEntities.Clear();
|
||||
|
||||
foreach (EntityEntry entry in context.ChangeTracker.Entries())
|
||||
{
|
||||
// Ignore activities
|
||||
if (entry.Metadata.BaseType?.ClrType == typeof(ActivityEntity) || entry.Metadata.ClrType == typeof(LastContactEntity)) continue;
|
||||
|
||||
// Ignore tokens
|
||||
if (entry.Metadata.Name.Contains("Token")) continue;
|
||||
|
||||
if (entry.State is not (EntityState.Added or EntityState.Deleted or EntityState.Modified)) continue;
|
||||
this.unsavedEntities.TryAdd(new TrackedEntityKey
|
||||
{
|
||||
ContextId = context.ContextId.InstanceId,
|
||||
Type = entry.Entity.GetType(),
|
||||
HashCode = entry.Entity.GetHashCode(),
|
||||
},
|
||||
new CustomTrackedEntity
|
||||
{
|
||||
State = entry.State,
|
||||
Entity = entry.Entity,
|
||||
OldEntity = entry.OriginalValues.ToObject(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void ParseInsertedEntities(DbContextEventData eventData)
|
||||
{
|
||||
if (eventData.Context is not DatabaseContext context) return;
|
||||
|
||||
HashSet<CustomTrackedEntity> entities = [];
|
||||
|
||||
List<EntityEntry> entries = context.ChangeTracker.Entries().ToList();
|
||||
|
||||
foreach (KeyValuePair<TrackedEntityKey, CustomTrackedEntity> kvp in this.unsavedEntities)
|
||||
{
|
||||
EntityEntry entry = entries.FirstOrDefault(e =>
|
||||
e.Metadata.ClrType == kvp.Key.Type && e.Entity.GetHashCode() == kvp.Key.HashCode);
|
||||
switch (kvp.Value.State)
|
||||
{
|
||||
case EntityState.Added:
|
||||
case EntityState.Modified:
|
||||
if (entry != null) entities.Add(kvp.Value);
|
||||
break;
|
||||
case EntityState.Deleted:
|
||||
if (entry == null) entities.Add(kvp.Value);
|
||||
break;
|
||||
case EntityState.Detached:
|
||||
case EntityState.Unchanged:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (CustomTrackedEntity entity in entities)
|
||||
{
|
||||
switch (entity.State)
|
||||
{
|
||||
case EntityState.Added:
|
||||
this.eventHandler.OnEntityInserted(context, entity.Entity);
|
||||
break;
|
||||
case EntityState.Deleted:
|
||||
this.eventHandler.OnEntityDeleted(context, entity.Entity);
|
||||
break;
|
||||
case EntityState.Modified:
|
||||
this.eventHandler.OnEntityChanged(context, entity.OldEntity, entity.Entity);
|
||||
break;
|
||||
case EntityState.Detached:
|
||||
case EntityState.Unchanged:
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,7 @@
|
|||
using System;
|
||||
using LBPUnion.ProjectLighthouse.Configuration;
|
||||
using LBPUnion.ProjectLighthouse.Types.Activity;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Activity;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Interaction;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Maintenance;
|
||||
|
@ -26,6 +29,10 @@ public partial class DatabaseContext : DbContext
|
|||
public DbSet<WebTokenEntity> WebTokens { get; set; }
|
||||
#endregion
|
||||
|
||||
#region Activity
|
||||
public DbSet<ActivityEntity> Activities { get; set; }
|
||||
#endregion
|
||||
|
||||
#region Users
|
||||
public DbSet<CommentEntity> Comments { get; set; }
|
||||
public DbSet<LastContactEntity> LastContacts { get; set; }
|
||||
|
@ -84,8 +91,36 @@ public partial class DatabaseContext : DbContext
|
|||
public static DatabaseContext CreateNewInstance()
|
||||
{
|
||||
DbContextOptionsBuilder<DatabaseContext> builder = new();
|
||||
builder.UseMySql(ServerConfiguration.Instance.DbConnectionString,
|
||||
MySqlServerVersion.LatestSupportedServerVersion);
|
||||
ConfigureBuilder()(builder);
|
||||
return new DatabaseContext(builder.Options);
|
||||
}
|
||||
|
||||
public static Action<DbContextOptionsBuilder> ConfigureBuilder()
|
||||
{
|
||||
return builder =>
|
||||
{
|
||||
builder.UseMySql(ServerConfiguration.Instance.DbConnectionString,
|
||||
MySqlServerVersion.LatestSupportedServerVersion);
|
||||
builder.AddInterceptors(new ActivityInterceptor(new ActivityEntityEventHandler()));
|
||||
};
|
||||
}
|
||||
|
||||
#region Activity
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<LevelActivityEntity>().UseTphMappingStrategy();
|
||||
modelBuilder.Entity<UserPhotoActivity>().UseTphMappingStrategy();
|
||||
modelBuilder.Entity<LevelPhotoActivity>().UseTphMappingStrategy();
|
||||
modelBuilder.Entity<PlaylistActivityEntity>().UseTphMappingStrategy();
|
||||
modelBuilder.Entity<PlaylistWithSlotActivityEntity>().UseTphMappingStrategy();
|
||||
modelBuilder.Entity<ScoreActivityEntity>().UseTphMappingStrategy();
|
||||
modelBuilder.Entity<UserActivityEntity>().UseTphMappingStrategy();
|
||||
modelBuilder.Entity<NewsActivityEntity>().UseTphMappingStrategy();
|
||||
modelBuilder.Entity<LevelCommentActivityEntity>().UseTphMappingStrategy();
|
||||
modelBuilder.Entity<UserCommentActivityEntity>().UseTphMappingStrategy();
|
||||
modelBuilder.Entity<UserActivityEntity>().UseTphMappingStrategy();
|
||||
modelBuilder.Entity<ReviewActivityEntity>().UseTphMappingStrategy();
|
||||
base.OnModelCreating(modelBuilder);
|
||||
}
|
||||
#endregion
|
||||
}
|
129
ProjectLighthouse/Extensions/ActivityQueryExtensions.cs
Normal file
129
ProjectLighthouse/Extensions/ActivityQueryExtensions.cs
Normal file
|
@ -0,0 +1,129 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using LBPUnion.ProjectLighthouse.Types.Activity;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Activity;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Extensions;
|
||||
|
||||
public static class ActivityQueryExtensions
|
||||
{
|
||||
public static List<int> GetIds(this IReadOnlyCollection<OuterActivityGroup> groups, ActivityGroupType type)
|
||||
{
|
||||
List<int> ids = [];
|
||||
// Add outer group ids
|
||||
ids.AddRange(groups.Where(g => g.Key.GroupType == type)
|
||||
.Where(g => g.Key.TargetId != 0)
|
||||
.Select(g => g.Key.TargetId)
|
||||
.ToList());
|
||||
|
||||
// Add specific event ids
|
||||
ids.AddRange(groups.SelectMany(g =>
|
||||
g.Groups.SelectMany(gr => gr.Where(a => a.GroupType == type).Select(a => a.TargetId))));
|
||||
if (type == ActivityGroupType.User)
|
||||
{
|
||||
ids.AddRange(groups.Where(g => g.Key.GroupType is not ActivityGroupType.News)
|
||||
.SelectMany(g => g.Groups.Select(a => a.Key.UserId)));
|
||||
}
|
||||
|
||||
return ids.Distinct().ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Turns a list of <see cref="ActivityDto"/> into a group based on its timestamp
|
||||
/// </summary>
|
||||
/// <param name="activityQuery">An <see cref="IQueryable{ActivityDto}"/> to group</param>
|
||||
/// <param name="groupByActor">Whether the groups should be created based on the initiator of the event or the target of the event</param>
|
||||
/// <returns>The transformed query containing groups of <see cref="ActivityDto"/></returns>
|
||||
public static IQueryable<IGrouping<ActivityGroup, ActivityDto>> ToActivityGroups
|
||||
(this IQueryable<ActivityDto> activityQuery, bool groupByActor = false) =>
|
||||
groupByActor
|
||||
? activityQuery.GroupBy(dto => new ActivityGroup
|
||||
{
|
||||
Timestamp = dto.Activity.Timestamp.Date,
|
||||
UserId = dto.Activity.UserId,
|
||||
TargetNewsId = dto.TargetNewsId ?? -1,
|
||||
TargetTeamPickSlotId = dto.TargetTeamPickId ?? -1,
|
||||
})
|
||||
: activityQuery.GroupBy(dto => new ActivityGroup
|
||||
{
|
||||
Timestamp = dto.Activity.Timestamp.Date,
|
||||
UserId = -1,
|
||||
TargetUserId = dto.TargetUserId ?? -1,
|
||||
TargetSlotId = dto.TargetSlotId ?? -1,
|
||||
TargetPlaylistId = dto.TargetPlaylistId ?? -1,
|
||||
TargetNewsId = dto.TargetNewsId ?? -1,
|
||||
});
|
||||
|
||||
public static List<OuterActivityGroup> ToOuterActivityGroups
|
||||
(this IEnumerable<IGrouping<ActivityGroup, ActivityDto>> activityGroups, bool groupByActor = false) =>
|
||||
// Pin news posts to the top
|
||||
activityGroups.OrderByDescending(g => g.Key.GroupType == ActivityGroupType.News ? 1 : 0)
|
||||
.ThenByDescending(g => g.MaxBy(a => a.Activity.Timestamp)?.Activity.Timestamp ?? g.Key.Timestamp)
|
||||
.Select(g => new OuterActivityGroup
|
||||
{
|
||||
Key = g.Key,
|
||||
Groups = g.OrderByDescending(a => a.Activity.Timestamp)
|
||||
.GroupBy(gr => new InnerActivityGroup
|
||||
{
|
||||
Type = groupByActor
|
||||
? gr.GroupType
|
||||
: gr.GroupType == ActivityGroupType.News
|
||||
? ActivityGroupType.News
|
||||
: ActivityGroupType.User,
|
||||
UserId = gr.Activity.UserId,
|
||||
TargetId = groupByActor
|
||||
? gr.TargetId
|
||||
: gr.GroupType == ActivityGroupType.News
|
||||
? gr.TargetNewsId ?? 0
|
||||
: gr.Activity.UserId,
|
||||
})
|
||||
.ToList(),
|
||||
})
|
||||
.ToList();
|
||||
|
||||
/// <summary>
|
||||
/// Converts an <see cref="IQueryable"/><<see cref="ActivityEntity"/>> into an <see cref="IQueryable"/><<see cref="ActivityDto"/>> for grouping.
|
||||
/// </summary>
|
||||
/// <param name="activityQuery">The activity query to be converted.</param>
|
||||
/// <param name="includeSlotCreator">Whether the <see cref="ActivityDto.TargetSlotCreatorId"/> field should be included.</param>
|
||||
/// <param name="includeTeamPick">Whether the <see cref="ActivityDto.TargetTeamPickId"/> field should be included.</param>
|
||||
/// <returns>The converted <see cref="IQueryable"/><<see cref="ActivityDto"/>></returns>
|
||||
public static IQueryable<ActivityDto> ToActivityDto
|
||||
(this IQueryable<ActivityEntity> activityQuery, bool includeSlotCreator = false, bool includeTeamPick = false)
|
||||
{
|
||||
return activityQuery.Select(a => new ActivityDto
|
||||
{
|
||||
Activity = a,
|
||||
TargetSlotId = (a as LevelActivityEntity).SlotId,
|
||||
TargetSlotGameVersion = (a as LevelActivityEntity).Slot.GameVersion,
|
||||
TargetSlotCreatorId = includeSlotCreator ? (a as LevelActivityEntity).Slot.CreatorId : null,
|
||||
TargetUserId = (a as UserActivityEntity).TargetUserId,
|
||||
TargetNewsId = (a as NewsActivityEntity).NewsId,
|
||||
TargetPlaylistId = (a as PlaylistActivityEntity).PlaylistId,
|
||||
TargetTeamPickId =
|
||||
includeTeamPick && a.Type == EventType.MMPickLevel ? (a as LevelActivityEntity).SlotId : null, });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an IEnumerable<<see cref="ActivityEntity"/>> into an IEnumerable<<see cref="ActivityDto"/>> for grouping.
|
||||
/// </summary>
|
||||
/// <param name="activityEnumerable">The activity query to be converted.</param>
|
||||
/// <param name="includeSlotCreator">Whether the <see cref="ActivityDto.TargetSlotCreatorId"/> field should be included.</param>
|
||||
/// <param name="includeTeamPick">Whether the <see cref="ActivityDto.TargetTeamPickId"/> field should be included.</param>
|
||||
/// <returns>The converted IEnumerable<<see cref="ActivityDto"/>></returns>
|
||||
public static IEnumerable<ActivityDto> ToActivityDto
|
||||
(this IEnumerable<ActivityEntity> activityEnumerable, bool includeSlotCreator = false, bool includeTeamPick = false)
|
||||
{
|
||||
return activityEnumerable.Select(a => new ActivityDto
|
||||
{
|
||||
Activity = a,
|
||||
TargetSlotId = (a as LevelActivityEntity)?.SlotId,
|
||||
TargetSlotGameVersion = (a as LevelActivityEntity)?.Slot.GameVersion,
|
||||
TargetSlotCreatorId = includeSlotCreator ? (a as LevelActivityEntity)?.Slot.CreatorId : null,
|
||||
TargetUserId = (a as UserActivityEntity)?.TargetUserId,
|
||||
TargetNewsId = (a as NewsActivityEntity)?.NewsId,
|
||||
TargetPlaylistId = (a as PlaylistActivityEntity)?.PlaylistId,
|
||||
TargetTeamPickId =
|
||||
includeTeamPick && a.Type == EventType.MMPickLevel ? (a as LevelActivityEntity)?.SlotId : null, });
|
||||
}
|
||||
}
|
|
@ -44,6 +44,7 @@ public static partial class ControllerExtensions
|
|||
public static async Task<T?> DeserializeBody<T>(this ControllerBase controller, params string[] rootElements)
|
||||
{
|
||||
string bodyString = await controller.ReadBodyAsync();
|
||||
if (bodyString.Length == 0) return default;
|
||||
try
|
||||
{
|
||||
// Prevent unescaped ampersands from causing deserialization to fail
|
||||
|
|
12
ProjectLighthouse/Extensions/DateTimeExtensions.cs
Normal file
12
ProjectLighthouse/Extensions/DateTimeExtensions.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Extensions;
|
||||
|
||||
public static class DateTimeExtensions
|
||||
{
|
||||
public static long ToUnixTimeMilliseconds(this DateTime dateTime) =>
|
||||
((DateTimeOffset)DateTime.SpecifyKind(dateTime, DateTimeKind.Utc)).ToUnixTimeMilliseconds();
|
||||
|
||||
public static DateTime FromUnixTimeMilliseconds(long timestamp) =>
|
||||
DateTimeOffset.FromUnixTimeMilliseconds(timestamp).UtcDateTime;
|
||||
}
|
55
ProjectLighthouse/Filter/ActivityQueryBuilder.cs
Normal file
55
ProjectLighthouse/Filter/ActivityQueryBuilder.cs
Normal file
|
@ -0,0 +1,55 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using LBPUnion.ProjectLighthouse.Extensions;
|
||||
using LBPUnion.ProjectLighthouse.Types.Activity;
|
||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Filter;
|
||||
|
||||
public class ActivityQueryBuilder : IQueryBuilder<ActivityDto>
|
||||
{
|
||||
private readonly List<IActivityFilter> filters;
|
||||
|
||||
public ActivityQueryBuilder()
|
||||
{
|
||||
this.filters = new List<IActivityFilter>();
|
||||
}
|
||||
|
||||
public Expression<Func<ActivityDto, bool>> Build()
|
||||
{
|
||||
Expression<Func<ActivityDto, bool>> predicate = PredicateExtensions.True<ActivityDto>();
|
||||
predicate = this.filters.Aggregate(predicate, (current, filter) => current.And(filter.GetPredicate()));
|
||||
return predicate;
|
||||
}
|
||||
|
||||
public ActivityQueryBuilder RemoveFilter(Type type)
|
||||
{
|
||||
this.filters.RemoveAll(f => f.GetType() == type);
|
||||
return this;
|
||||
}
|
||||
|
||||
#nullable enable
|
||||
public IEnumerable<IActivityFilter> GetFilters(Type type) => this.filters.Where(f => f.GetType() == type).ToList();
|
||||
#nullable disable
|
||||
|
||||
public ActivityQueryBuilder AddFilter(int index, IActivityFilter filter)
|
||||
{
|
||||
this.filters.Insert(index, filter);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ActivityQueryBuilder Clone()
|
||||
{
|
||||
ActivityQueryBuilder clone = new();
|
||||
clone.filters.AddRange(this.filters);
|
||||
return clone;
|
||||
}
|
||||
|
||||
public ActivityQueryBuilder AddFilter(IActivityFilter filter)
|
||||
{
|
||||
this.filters.Add(filter);
|
||||
return this;
|
||||
}
|
||||
}
|
27
ProjectLighthouse/Filter/Filters/Activity/EventTypeFilter.cs
Normal file
27
ProjectLighthouse/Filter/Filters/Activity/EventTypeFilter.cs
Normal file
|
@ -0,0 +1,27 @@
|
|||
#nullable enable
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using LBPUnion.ProjectLighthouse.Extensions;
|
||||
using LBPUnion.ProjectLighthouse.Types.Activity;
|
||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Activity;
|
||||
|
||||
public class EventTypeFilter : IActivityFilter
|
||||
{
|
||||
private readonly EventType[] events;
|
||||
|
||||
public EventTypeFilter(params EventType[] events)
|
||||
{
|
||||
this.events = events;
|
||||
}
|
||||
|
||||
public Expression<Func<ActivityDto, bool>> GetPredicate()
|
||||
{
|
||||
Expression<Func<ActivityDto, bool>> predicate = PredicateExtensions.False<ActivityDto>();
|
||||
predicate = this.events.Aggregate(predicate,
|
||||
(current, eventType) => current.Or(a => a.Activity.Type == eventType));
|
||||
return predicate;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
using System.Linq.Expressions;
|
||||
using LBPUnion.ProjectLighthouse.Types.Activity;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Activity;
|
||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Activity;
|
||||
|
||||
public class ExcludeNewsFilter : IActivityFilter
|
||||
{
|
||||
public Expression<Func<ActivityDto, bool>> GetPredicate() => a => a.Activity is NewsActivityEntity && a.Activity.Type != EventType.NewsPost;
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
using System;
|
||||
using System.Linq.Expressions;
|
||||
using LBPUnion.ProjectLighthouse.Types.Activity;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Activity;
|
||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Activity;
|
||||
|
||||
public class IncludeNewsFilter : IActivityFilter
|
||||
{
|
||||
public Expression<Func<ActivityDto, bool>> GetPredicate() =>
|
||||
a => (a.Activity is NewsActivityEntity && a.Activity.Type == EventType.NewsPost) ||
|
||||
a.Activity.Type == EventType.MMPickLevel;
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using LBPUnion.ProjectLighthouse.Extensions;
|
||||
using LBPUnion.ProjectLighthouse.Types.Activity;
|
||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Activity;
|
||||
|
||||
public class IncludeUserIdFilter : IActivityFilter
|
||||
{
|
||||
private readonly IEnumerable<int> userIds;
|
||||
private readonly EventTypeFilter eventFilter;
|
||||
|
||||
public IncludeUserIdFilter(IEnumerable<int> userIds, EventTypeFilter eventFilter = null)
|
||||
{
|
||||
this.userIds = userIds;
|
||||
this.eventFilter = eventFilter;
|
||||
}
|
||||
|
||||
public Expression<Func<ActivityDto, bool>> GetPredicate()
|
||||
{
|
||||
Expression<Func<ActivityDto, bool>> predicate = PredicateExtensions.False<ActivityDto>();
|
||||
predicate = this.userIds.Aggregate(predicate, (current, friendId) => current.Or(a => a.Activity.UserId == friendId));
|
||||
if (this.eventFilter != null) predicate = predicate.And(this.eventFilter.GetPredicate());
|
||||
return predicate;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
using System;
|
||||
using System.Linq.Expressions;
|
||||
using LBPUnion.ProjectLighthouse.Extensions;
|
||||
using LBPUnion.ProjectLighthouse.Types.Activity;
|
||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Activity;
|
||||
|
||||
public class MyLevelActivityFilter : IActivityFilter
|
||||
{
|
||||
private readonly int userId;
|
||||
private readonly EventTypeFilter eventFilter;
|
||||
|
||||
public MyLevelActivityFilter(int userId, EventTypeFilter eventFilter = null)
|
||||
{
|
||||
this.userId = userId;
|
||||
this.eventFilter = eventFilter;
|
||||
}
|
||||
|
||||
public Expression<Func<ActivityDto, bool>> GetPredicate()
|
||||
{
|
||||
Expression<Func<ActivityDto, bool>> predicate = PredicateExtensions.False<ActivityDto>();
|
||||
predicate = predicate.Or(a => a.TargetSlotCreatorId == this.userId);
|
||||
if (this.eventFilter != null) predicate = predicate.And(this.eventFilter.GetPredicate());
|
||||
return predicate;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using LBPUnion.ProjectLighthouse.Extensions;
|
||||
using LBPUnion.ProjectLighthouse.Types.Activity;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Activity;
|
||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Activity;
|
||||
|
||||
public class PlaylistActivityFilter : IActivityFilter
|
||||
{
|
||||
private readonly List<int> playlistIds;
|
||||
private readonly EventTypeFilter eventFilter;
|
||||
|
||||
public PlaylistActivityFilter(List<int> playlistIds, EventTypeFilter eventFilter = null)
|
||||
{
|
||||
this.playlistIds = playlistIds;
|
||||
this.eventFilter = eventFilter;
|
||||
}
|
||||
|
||||
public Expression<Func<ActivityDto, bool>> GetPredicate()
|
||||
{
|
||||
Expression<Func<ActivityDto, bool>> predicate = PredicateExtensions.False<ActivityDto>();
|
||||
predicate = this.playlistIds.Aggregate(predicate, (current, playlistId) => current.Or(a => (a.Activity is PlaylistActivityEntity || a.Activity is PlaylistWithSlotActivityEntity) && a.TargetPlaylistId == playlistId));
|
||||
if (this.eventFilter != null) predicate = predicate.And(this.eventFilter.GetPredicate());
|
||||
return predicate;
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@ using System.Linq.Expressions;
|
|||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
|
||||
public class AdventureFilter : ISlotFilter
|
||||
{
|
|
@ -5,7 +5,7 @@ using LBPUnion.ProjectLighthouse.Extensions;
|
|||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
|
||||
public class AuthorLabelFilter : ISlotFilter
|
||||
{
|
||||
|
@ -20,7 +20,7 @@ public class AuthorLabelFilter : ISlotFilter
|
|||
{
|
||||
Expression<Func<SlotEntity, bool>> predicate = PredicateExtensions.True<SlotEntity>();
|
||||
predicate = this.labels.Aggregate(predicate,
|
||||
(current, label) => current.And(s => s.AuthorLabels.Contains(label)));
|
||||
(current, label) => PredicateExtensions.And<SlotEntity>(current, s => s.AuthorLabels.Contains(label)));
|
||||
return predicate;
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@ using System.Linq.Expressions;
|
|||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
|
||||
public class CreatorFilter : ISlotFilter
|
||||
{
|
|
@ -3,7 +3,7 @@ using System.Linq.Expressions;
|
|||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
|
||||
public class CrossControlFilter : ISlotFilter
|
||||
{
|
|
@ -3,7 +3,7 @@ using System.Linq.Expressions;
|
|||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
|
||||
public class ExcludeAdventureFilter : ISlotFilter
|
||||
{
|
|
@ -3,7 +3,7 @@ using System.Linq.Expressions;
|
|||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
|
||||
public class ExcludeCrossControlFilter : ISlotFilter
|
||||
{
|
|
@ -4,7 +4,7 @@ using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
|||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
using LBPUnion.ProjectLighthouse.Types.Users;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
|
||||
public class ExcludeLBP1OnlyFilter : ISlotFilter
|
||||
{
|
|
@ -3,7 +3,7 @@ using System.Linq.Expressions;
|
|||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
|
||||
public class ExcludeMovePackFilter : ISlotFilter
|
||||
{
|
|
@ -4,7 +4,7 @@ using LBPUnion.ProjectLighthouse.Extensions;
|
|||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
|
||||
public class FirstUploadedFilter : ISlotFilter
|
||||
{
|
|
@ -4,7 +4,7 @@ using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
|||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
using LBPUnion.ProjectLighthouse.Types.Users;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
|
||||
public class GameVersionFilter : ISlotFilter
|
||||
{
|
|
@ -6,7 +6,7 @@ using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
|||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
using LBPUnion.ProjectLighthouse.Types.Users;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
|
||||
public class GameVersionListFilter : ISlotFilter
|
||||
{
|
||||
|
@ -19,5 +19,5 @@ public class GameVersionListFilter : ISlotFilter
|
|||
|
||||
public Expression<Func<SlotEntity, bool>> GetPredicate() =>
|
||||
this.versions.Aggregate(PredicateExtensions.False<SlotEntity>(),
|
||||
(current, version) => current.Or(s => s.GameVersion == version));
|
||||
(current, version) => PredicateExtensions.Or<SlotEntity>(current, s => s.GameVersion == version));
|
||||
}
|
|
@ -3,7 +3,7 @@ using System.Linq.Expressions;
|
|||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
|
||||
public class HiddenSlotFilter : ISlotFilter
|
||||
{
|
|
@ -3,7 +3,7 @@ using System.Linq.Expressions;
|
|||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
|
||||
public class MovePackFilter : ISlotFilter
|
||||
{
|
|
@ -4,7 +4,7 @@ using LBPUnion.ProjectLighthouse.Extensions;
|
|||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
|
||||
public class PlayerCountFilter : ISlotFilter
|
||||
{
|
|
@ -4,7 +4,7 @@ using System.Linq.Expressions;
|
|||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
|
||||
public class ResultTypeFilter : ISlotFilter
|
||||
{
|
|
@ -6,7 +6,7 @@ using LBPUnion.ProjectLighthouse.Extensions;
|
|||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
|
||||
public class SlotIdFilter : ISlotFilter
|
||||
{
|
||||
|
@ -20,7 +20,7 @@ public class SlotIdFilter : ISlotFilter
|
|||
public Expression<Func<SlotEntity, bool>> GetPredicate()
|
||||
{
|
||||
Expression<Func<SlotEntity, bool>> predicate = PredicateExtensions.False<SlotEntity>();
|
||||
predicate = this.slotIds.Aggregate(predicate, (current, slotId) => current.Or(s => s.SlotId == slotId));
|
||||
predicate = this.slotIds.Aggregate(predicate, (current, slotId) => PredicateExtensions.Or<SlotEntity>(current, s => s.SlotId == slotId));
|
||||
return predicate;
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
|||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
using LBPUnion.ProjectLighthouse.Types.Levels;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
|
||||
public class SlotTypeFilter : ISlotFilter
|
||||
{
|
|
@ -3,7 +3,7 @@ using System.Linq.Expressions;
|
|||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
|
||||
public class SubLevelFilter : ISlotFilter
|
||||
{
|
|
@ -3,7 +3,7 @@ using System.Linq.Expressions;
|
|||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
|
||||
public class TeamPickFilter : ISlotFilter
|
||||
{
|
|
@ -4,7 +4,7 @@ using LBPUnion.ProjectLighthouse.Extensions;
|
|||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
|
||||
public class TextFilter : ISlotFilter
|
||||
{
|
692
ProjectLighthouse/Migrations/20240514032512_AddRecentActivity.cs
Normal file
692
ProjectLighthouse/Migrations/20240514032512_AddRecentActivity.cs
Normal file
|
@ -0,0 +1,692 @@
|
|||
using System;
|
||||
using LBPUnion.ProjectLighthouse.Database;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Migrations
|
||||
{
|
||||
[DbContext(typeof(DatabaseContext))]
|
||||
[Migration("20240514032512_AddRecentActivity")]
|
||||
public partial class AddRecentActivity : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "TokenId",
|
||||
table: "WebTokens",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "AnnouncementId",
|
||||
table: "WebsiteAnnouncements",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "VisitedLevelId",
|
||||
table: "VisitedLevels",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "UserId",
|
||||
table: "Users",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "SlotId",
|
||||
table: "Slots",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "ScoreId",
|
||||
table: "Scores",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "ReviewId",
|
||||
table: "Reviews",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "ReportId",
|
||||
table: "Reports",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "TokenId",
|
||||
table: "RegistrationTokens",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "RatedReviewId",
|
||||
table: "RatedReviews",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "RatedLevelId",
|
||||
table: "RatedLevels",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "RatingId",
|
||||
table: "RatedComments",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "QueuedLevelId",
|
||||
table: "QueuedLevels",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "PlaylistId",
|
||||
table: "Playlists",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "PlatformLinkAttemptId",
|
||||
table: "PlatformLinkAttempts",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "PhotoSubjectId",
|
||||
table: "PhotoSubjects",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "PhotoId",
|
||||
table: "Photos",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "TokenId",
|
||||
table: "PasswordResetTokens",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "Id",
|
||||
table: "Notifications",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "HeartedProfileId",
|
||||
table: "HeartedProfiles",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "HeartedPlaylistId",
|
||||
table: "HeartedPlaylists",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "HeartedLevelId",
|
||||
table: "HeartedLevels",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "TokenId",
|
||||
table: "GameTokens",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "EmailVerificationTokenId",
|
||||
table: "EmailVerificationTokens",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "EmailSetTokenId",
|
||||
table: "EmailSetTokens",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "CategoryId",
|
||||
table: "CustomCategories",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "CommentId",
|
||||
table: "Comments",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "CaseId",
|
||||
table: "Cases",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "BlockedProfileId",
|
||||
table: "BlockedProfiles",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "Id",
|
||||
table: "APIKeys",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Activities",
|
||||
columns: table => new
|
||||
{
|
||||
ActivityId = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||
Timestamp = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
UserId = table.Column<int>(type: "int", nullable: false),
|
||||
Type = table.Column<int>(type: "int", nullable: false),
|
||||
Discriminator = table.Column<string>(type: "varchar(34)", maxLength: 34, nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
SlotId = table.Column<int>(type: "int", nullable: true),
|
||||
CommentId = table.Column<int>(type: "int", nullable: true),
|
||||
PhotoId = table.Column<int>(type: "int", nullable: true),
|
||||
NewsId = table.Column<int>(type: "int", nullable: true),
|
||||
PlaylistId = table.Column<int>(type: "int", nullable: true),
|
||||
ReviewId = table.Column<int>(type: "int", nullable: true),
|
||||
ScoreId = table.Column<int>(type: "int", nullable: true),
|
||||
Points = table.Column<int>(type: "int", nullable: true),
|
||||
TargetUserId = table.Column<int>(type: "int", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Activities", x => x.ActivityId);
|
||||
table.ForeignKey(
|
||||
name: "FK_Activities_Comments_CommentId",
|
||||
column: x => x.CommentId,
|
||||
principalTable: "Comments",
|
||||
principalColumn: "CommentId",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_Activities_Photos_PhotoId",
|
||||
column: x => x.PhotoId,
|
||||
principalTable: "Photos",
|
||||
principalColumn: "PhotoId",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_Activities_Playlists_PlaylistId",
|
||||
column: x => x.PlaylistId,
|
||||
principalTable: "Playlists",
|
||||
principalColumn: "PlaylistId",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_Activities_Reviews_ReviewId",
|
||||
column: x => x.ReviewId,
|
||||
principalTable: "Reviews",
|
||||
principalColumn: "ReviewId",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_Activities_Scores_ScoreId",
|
||||
column: x => x.ScoreId,
|
||||
principalTable: "Scores",
|
||||
principalColumn: "ScoreId",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_Activities_Slots_SlotId",
|
||||
column: x => x.SlotId,
|
||||
principalTable: "Slots",
|
||||
principalColumn: "SlotId",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_Activities_Users_TargetUserId",
|
||||
column: x => x.TargetUserId,
|
||||
principalTable: "Users",
|
||||
principalColumn: "UserId",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_Activities_Users_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "Users",
|
||||
principalColumn: "UserId",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_Activities_WebsiteAnnouncements_NewsId",
|
||||
column: x => x.NewsId,
|
||||
principalTable: "WebsiteAnnouncements",
|
||||
principalColumn: "AnnouncementId",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Activities_CommentId",
|
||||
table: "Activities",
|
||||
column: "CommentId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Activities_NewsId",
|
||||
table: "Activities",
|
||||
column: "NewsId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Activities_PhotoId",
|
||||
table: "Activities",
|
||||
column: "PhotoId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Activities_PlaylistId",
|
||||
table: "Activities",
|
||||
column: "PlaylistId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Activities_ReviewId",
|
||||
table: "Activities",
|
||||
column: "ReviewId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Activities_ScoreId",
|
||||
table: "Activities",
|
||||
column: "ScoreId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Activities_SlotId",
|
||||
table: "Activities",
|
||||
column: "SlotId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Activities_TargetUserId",
|
||||
table: "Activities",
|
||||
column: "TargetUserId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Activities_UserId",
|
||||
table: "Activities",
|
||||
column: "UserId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "Activities");
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "TokenId",
|
||||
table: "WebTokens",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "AnnouncementId",
|
||||
table: "WebsiteAnnouncements",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "VisitedLevelId",
|
||||
table: "VisitedLevels",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "UserId",
|
||||
table: "Users",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "SlotId",
|
||||
table: "Slots",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "ScoreId",
|
||||
table: "Scores",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "ReviewId",
|
||||
table: "Reviews",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "ReportId",
|
||||
table: "Reports",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "TokenId",
|
||||
table: "RegistrationTokens",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "RatedReviewId",
|
||||
table: "RatedReviews",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "RatedLevelId",
|
||||
table: "RatedLevels",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "RatingId",
|
||||
table: "RatedComments",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "QueuedLevelId",
|
||||
table: "QueuedLevels",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "PlaylistId",
|
||||
table: "Playlists",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "PlatformLinkAttemptId",
|
||||
table: "PlatformLinkAttempts",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "PhotoSubjectId",
|
||||
table: "PhotoSubjects",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "PhotoId",
|
||||
table: "Photos",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "TokenId",
|
||||
table: "PasswordResetTokens",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "Id",
|
||||
table: "Notifications",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "HeartedProfileId",
|
||||
table: "HeartedProfiles",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "HeartedPlaylistId",
|
||||
table: "HeartedPlaylists",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "HeartedLevelId",
|
||||
table: "HeartedLevels",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "TokenId",
|
||||
table: "GameTokens",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "EmailVerificationTokenId",
|
||||
table: "EmailVerificationTokens",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "EmailSetTokenId",
|
||||
table: "EmailSetTokens",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "CategoryId",
|
||||
table: "CustomCategories",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "CommentId",
|
||||
table: "Comments",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "CaseId",
|
||||
table: "Cases",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "BlockedProfileId",
|
||||
table: "BlockedProfiles",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "Id",
|
||||
table: "APIKeys",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
using System;
|
||||
using LBPUnion.ProjectLighthouse.Database;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Migrations
|
||||
{
|
||||
[DbContext(typeof(DatabaseContext))]
|
||||
[Migration("20240514032620_AddPublishedAtToWebAnnouncement")]
|
||||
public partial class AddPublishedAtToWebAnnouncement : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "PublishedAt",
|
||||
table: "WebsiteAnnouncements",
|
||||
type: "datetime(6)",
|
||||
nullable: false,
|
||||
defaultValue: DateTime.UtcNow);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "PublishedAt",
|
||||
table: "WebsiteAnnouncements");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using LBPUnion.ProjectLighthouse.Configuration;
|
||||
using LBPUnion.ProjectLighthouse.Types.Matchmaking.Rooms;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.StorableLists.Stores;
|
||||
|
@ -10,7 +11,7 @@ public static class RoomStore
|
|||
|
||||
public static StorableList<Room> GetRooms()
|
||||
{
|
||||
if (RedisDatabase.Initialized)
|
||||
if (!ServerStatics.IsUnitTesting && RedisDatabase.Initialized)
|
||||
{
|
||||
return new RedisStorableList<Room>(RedisDatabase.GetRooms());
|
||||
}
|
||||
|
|
35
ProjectLighthouse/Types/Activity/ActivityDto.cs
Normal file
35
ProjectLighthouse/Types/Activity/ActivityDto.cs
Normal file
|
@ -0,0 +1,35 @@
|
|||
using LBPUnion.ProjectLighthouse.Types.Entities.Activity;
|
||||
using LBPUnion.ProjectLighthouse.Types.Users;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Types.Activity;
|
||||
|
||||
public class ActivityDto
|
||||
{
|
||||
public required ActivityEntity Activity { get; set; }
|
||||
public int? TargetSlotId { get; set; }
|
||||
public int? TargetSlotCreatorId { get; set; }
|
||||
public GameVersion? TargetSlotGameVersion { get; set; }
|
||||
public int? TargetUserId { get; set; }
|
||||
public int? TargetPlaylistId { get; set; }
|
||||
public int? TargetNewsId { get; set; }
|
||||
public int? TargetTeamPickId { get; set; }
|
||||
|
||||
public int TargetId =>
|
||||
this.GroupType switch
|
||||
{
|
||||
ActivityGroupType.User => this.TargetUserId ?? -1,
|
||||
ActivityGroupType.Level => this.TargetSlotId ?? -1,
|
||||
ActivityGroupType.Playlist => this.TargetPlaylistId ?? -1,
|
||||
ActivityGroupType.News => this.TargetNewsId ?? -1,
|
||||
_ => this.Activity.UserId,
|
||||
};
|
||||
|
||||
public ActivityGroupType GroupType =>
|
||||
this.TargetPlaylistId != null
|
||||
? ActivityGroupType.Playlist
|
||||
: this.TargetNewsId != null
|
||||
? ActivityGroupType.News
|
||||
: this.TargetSlotId != null
|
||||
? ActivityGroupType.Level
|
||||
: ActivityGroupType.User;
|
||||
}
|
385
ProjectLighthouse/Types/Activity/ActivityEntityEventHandler.cs
Normal file
385
ProjectLighthouse/Types/Activity/ActivityEntityEventHandler.cs
Normal file
|
@ -0,0 +1,385 @@
|
|||
#nullable enable
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using LBPUnion.ProjectLighthouse.Configuration;
|
||||
using LBPUnion.ProjectLighthouse.Database;
|
||||
using LBPUnion.ProjectLighthouse.Extensions;
|
||||
using LBPUnion.ProjectLighthouse.Logging;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Activity;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Interaction;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Website;
|
||||
using LBPUnion.ProjectLighthouse.Types.Levels;
|
||||
using LBPUnion.ProjectLighthouse.Types.Logging;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Types.Activity;
|
||||
|
||||
public class ActivityEntityEventHandler : IEntityEventHandler
|
||||
{
|
||||
public void OnEntityInserted<T>(DatabaseContext database, T entity) where T : class
|
||||
{
|
||||
ActivityEntity? activity = entity switch
|
||||
{
|
||||
SlotEntity slot => slot.Type switch
|
||||
{
|
||||
SlotType.User => new LevelActivityEntity
|
||||
{
|
||||
Type = EventType.PublishLevel,
|
||||
SlotId = slot.SlotId,
|
||||
UserId = slot.CreatorId,
|
||||
},
|
||||
_ => null,
|
||||
},
|
||||
CommentEntity comment => comment.Type switch
|
||||
{
|
||||
CommentType.Level => database.Slots.Where(s => s.SlotId == comment.TargetSlotId)
|
||||
.Select(s => s.Type)
|
||||
.FirstOrDefault() switch
|
||||
{
|
||||
SlotType.User => new LevelCommentActivityEntity
|
||||
{
|
||||
Type = EventType.CommentOnLevel,
|
||||
CommentId = comment.CommentId,
|
||||
UserId = comment.PosterUserId,
|
||||
SlotId = comment.TargetSlotId ?? throw new NullReferenceException("SlotId in Level comment is null, this shouldn't happen."),
|
||||
},
|
||||
_ => null,
|
||||
},
|
||||
CommentType.Profile => new UserCommentActivityEntity
|
||||
{
|
||||
Type = EventType.CommentOnUser,
|
||||
CommentId = comment.CommentId,
|
||||
UserId = comment.PosterUserId,
|
||||
TargetUserId = comment.TargetUserId ?? throw new NullReferenceException("TargetUserId in User comment is null, this shouldn't happen."),
|
||||
},
|
||||
_ => null,
|
||||
},
|
||||
PhotoEntity photo => database.Slots.Where(s => s.SlotId == photo.SlotId)
|
||||
.Select(s => s.Type)
|
||||
.FirstOrDefault() switch
|
||||
{
|
||||
SlotType.User => new LevelPhotoActivity
|
||||
{
|
||||
Type = EventType.UploadPhoto,
|
||||
PhotoId = photo.PhotoId,
|
||||
UserId = photo.CreatorId,
|
||||
SlotId = photo.SlotId ?? throw new NullReferenceException("SlotId in Photo is null"),
|
||||
},
|
||||
// All other photos (story, moon, pod, etc.)
|
||||
_ => null,
|
||||
},
|
||||
ScoreEntity score => database.Slots.Where(s => s.SlotId == score.SlotId)
|
||||
.Select(s => s.Type)
|
||||
.FirstOrDefault() switch
|
||||
{
|
||||
// Don't add story scores or versus scores
|
||||
SlotType.User when score.Type != 7 => new ScoreActivityEntity
|
||||
{
|
||||
Type = EventType.Score,
|
||||
ScoreId = score.ScoreId,
|
||||
UserId = score.UserId,
|
||||
SlotId = score.SlotId,
|
||||
Points = score.Points,
|
||||
},
|
||||
_ => null,
|
||||
},
|
||||
HeartedLevelEntity heartedLevel => database.Slots.Where(s => s.SlotId == heartedLevel.SlotId)
|
||||
.Select(s => s.Type)
|
||||
.FirstOrDefault() switch
|
||||
{
|
||||
SlotType.User => new LevelActivityEntity
|
||||
{
|
||||
Type = EventType.HeartLevel,
|
||||
SlotId = heartedLevel.SlotId,
|
||||
UserId = heartedLevel.UserId,
|
||||
},
|
||||
_ => null,
|
||||
},
|
||||
HeartedProfileEntity heartedProfile => new UserActivityEntity
|
||||
{
|
||||
Type = EventType.HeartUser,
|
||||
TargetUserId = heartedProfile.HeartedUserId,
|
||||
UserId = heartedProfile.UserId,
|
||||
},
|
||||
HeartedPlaylistEntity heartedPlaylist => new PlaylistActivityEntity
|
||||
{
|
||||
Type = EventType.HeartPlaylist,
|
||||
PlaylistId = heartedPlaylist.PlaylistId,
|
||||
UserId = heartedPlaylist.UserId,
|
||||
},
|
||||
VisitedLevelEntity visitedLevel => new LevelActivityEntity
|
||||
{
|
||||
Type = EventType.PlayLevel,
|
||||
SlotId = visitedLevel.SlotId,
|
||||
UserId = visitedLevel.UserId,
|
||||
},
|
||||
ReviewEntity review => new ReviewActivityEntity
|
||||
{
|
||||
Type = EventType.ReviewLevel,
|
||||
ReviewId = review.ReviewId,
|
||||
UserId = review.ReviewerId,
|
||||
SlotId = review.SlotId,
|
||||
},
|
||||
RatedLevelEntity ratedLevel => new LevelActivityEntity
|
||||
{
|
||||
Type = ratedLevel.Rating != 0 ? EventType.DpadRateLevel : EventType.RateLevel,
|
||||
SlotId = ratedLevel.SlotId,
|
||||
UserId = ratedLevel.UserId,
|
||||
},
|
||||
PlaylistEntity playlist => new PlaylistActivityEntity
|
||||
{
|
||||
Type = EventType.CreatePlaylist,
|
||||
PlaylistId = playlist.PlaylistId,
|
||||
UserId = playlist.CreatorId,
|
||||
},
|
||||
WebsiteAnnouncementEntity announcement => new NewsActivityEntity
|
||||
{
|
||||
Type = EventType.NewsPost,
|
||||
UserId = announcement.PublisherId ?? 0,
|
||||
NewsId = announcement.AnnouncementId,
|
||||
},
|
||||
_ => null,
|
||||
};
|
||||
InsertActivity(database, activity);
|
||||
}
|
||||
|
||||
private static void RemoveDuplicateEvents(DatabaseContext database, ActivityEntity activity)
|
||||
{
|
||||
// ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault
|
||||
switch (activity.Type)
|
||||
{
|
||||
case EventType.HeartLevel:
|
||||
case EventType.UnheartLevel:
|
||||
{
|
||||
if (activity is not LevelActivityEntity levelActivity) break;
|
||||
|
||||
DeleteActivity(a => a.TargetSlotId == levelActivity.SlotId);
|
||||
break;
|
||||
}
|
||||
case EventType.HeartUser:
|
||||
case EventType.UnheartUser:
|
||||
{
|
||||
if (activity is not UserActivityEntity userActivity) break;
|
||||
|
||||
DeleteActivity(a => a.TargetUserId == userActivity.TargetUserId);
|
||||
break;
|
||||
}
|
||||
case EventType.HeartPlaylist:
|
||||
{
|
||||
if (activity is not PlaylistActivityEntity playlistActivity) break;
|
||||
|
||||
DeleteActivity(a => a.TargetPlaylistId == playlistActivity.PlaylistId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
void DeleteActivity(Expression<Func<ActivityDto, bool>> predicate)
|
||||
{
|
||||
database.Activities.ToActivityDto()
|
||||
.Where(a => a.Activity.UserId == activity.UserId)
|
||||
.Where(a => a.Activity.Type == activity.Type)
|
||||
.Where(predicate)
|
||||
.Select(a => a.Activity)
|
||||
.ExecuteDelete();
|
||||
}
|
||||
}
|
||||
|
||||
private static void InsertActivity(DatabaseContext database, ActivityEntity? activity)
|
||||
{
|
||||
if (activity == null) return;
|
||||
|
||||
if (ServerConfiguration.Instance.UserGeneratedContentLimits.ReadOnlyMode) return;
|
||||
|
||||
Logger.Debug("Inserting activity: " + activity.GetType().Name, LogArea.Activity);
|
||||
|
||||
RemoveDuplicateEvents(database, activity);
|
||||
|
||||
activity.Timestamp = DateTime.UtcNow;
|
||||
database.Activities.Add(activity);
|
||||
database.SaveChanges();
|
||||
}
|
||||
|
||||
public void OnEntityChanged<T>(DatabaseContext database, T origEntity, T currentEntity) where T : class
|
||||
{
|
||||
ActivityEntity? activity = null;
|
||||
switch (currentEntity)
|
||||
{
|
||||
case VisitedLevelEntity visitedLevel:
|
||||
{
|
||||
if (origEntity is not VisitedLevelEntity oldVisitedLevel) break;
|
||||
|
||||
if (Plays(oldVisitedLevel) >= Plays(visitedLevel)) break;
|
||||
|
||||
activity = new LevelActivityEntity
|
||||
{
|
||||
Type = EventType.PlayLevel,
|
||||
SlotId = visitedLevel.SlotId,
|
||||
UserId = visitedLevel.UserId,
|
||||
};
|
||||
break;
|
||||
|
||||
int Plays(VisitedLevelEntity entity) => entity.PlaysLBP1 + entity.PlaysLBP2 + entity.PlaysLBP3;
|
||||
}
|
||||
case ScoreEntity score:
|
||||
{
|
||||
if (origEntity is not ScoreEntity oldScore) break;
|
||||
|
||||
// don't track versus levels
|
||||
if (oldScore.Type == 7) break;
|
||||
|
||||
SlotType slotType = database.Slots.Where(s => s.SlotId == score.SlotId)
|
||||
.Select(s => s.Type)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (slotType != SlotType.User) break;
|
||||
|
||||
if (oldScore.Points > score.Points) break;
|
||||
|
||||
activity = new ScoreActivityEntity
|
||||
{
|
||||
Type = EventType.Score,
|
||||
ScoreId = score.ScoreId,
|
||||
SlotId = score.SlotId,
|
||||
UserId = score.UserId,
|
||||
Points = score.Points,
|
||||
};
|
||||
|
||||
break;
|
||||
}
|
||||
case SlotEntity slotEntity:
|
||||
{
|
||||
if (origEntity is not SlotEntity oldSlotEntity) break;
|
||||
|
||||
bool oldIsTeamPick = oldSlotEntity.TeamPickTime != 0;
|
||||
bool newIsTeamPick = slotEntity.TeamPickTime != 0;
|
||||
|
||||
switch (oldIsTeamPick)
|
||||
{
|
||||
// When a level is team picked
|
||||
case false when newIsTeamPick:
|
||||
activity = new LevelActivityEntity
|
||||
{
|
||||
Type = EventType.MMPickLevel,
|
||||
SlotId = slotEntity.SlotId,
|
||||
UserId = slotEntity.CreatorId,
|
||||
};
|
||||
break;
|
||||
// When a level has its team pick removed then remove the corresponding activity
|
||||
case true when !newIsTeamPick:
|
||||
database.Activities.OfType<LevelActivityEntity>()
|
||||
.Where(a => a.Type == EventType.MMPickLevel)
|
||||
.Where(a => a.SlotId == slotEntity.SlotId)
|
||||
.ExecuteDelete();
|
||||
break;
|
||||
default:
|
||||
{
|
||||
if (oldSlotEntity.SlotId == slotEntity.SlotId &&
|
||||
slotEntity.Type == SlotType.User &&
|
||||
oldSlotEntity.LastUpdated != slotEntity.LastUpdated)
|
||||
{
|
||||
activity = new LevelActivityEntity
|
||||
{
|
||||
Type = EventType.PublishLevel,
|
||||
SlotId = slotEntity.SlotId,
|
||||
UserId = slotEntity.CreatorId,
|
||||
};
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CommentEntity comment:
|
||||
{
|
||||
if (origEntity is not CommentEntity oldComment) break;
|
||||
|
||||
if (comment.TargetSlotId != null)
|
||||
{
|
||||
SlotType slotType = database.Slots.Where(s => s.SlotId == comment.TargetSlotId)
|
||||
.Select(s => s.Type)
|
||||
.FirstOrDefault();
|
||||
if (slotType != SlotType.User) break;
|
||||
}
|
||||
|
||||
if (oldComment.Deleted || !comment.Deleted) break;
|
||||
|
||||
if (comment.Type != CommentType.Level) break;
|
||||
|
||||
activity = new CommentActivityEntity
|
||||
{
|
||||
Type = EventType.DeleteLevelComment,
|
||||
CommentId = comment.CommentId,
|
||||
UserId = comment.PosterUserId,
|
||||
};
|
||||
break;
|
||||
}
|
||||
case PlaylistEntity playlist:
|
||||
{
|
||||
if (origEntity is not PlaylistEntity oldPlaylist) break;
|
||||
|
||||
int[] newSlots = playlist.SlotIds;
|
||||
int[] oldSlots = oldPlaylist.SlotIds;
|
||||
Logger.Debug($@"Old playlist slots: {string.Join(",", oldSlots)}", LogArea.Activity);
|
||||
Logger.Debug($@"New playlist slots: {string.Join(",", newSlots)}", LogArea.Activity);
|
||||
|
||||
int[] addedSlots = newSlots.Except(oldSlots).ToArray();
|
||||
|
||||
Logger.Debug($@"Added playlist slots: {string.Join(",", addedSlots)}", LogArea.Activity);
|
||||
|
||||
// If no new level have been added
|
||||
if (addedSlots.Length == 0) break;
|
||||
|
||||
// Normally events only need 1 resulting ActivityEntity but here
|
||||
// we need multiple, so we have to do the inserting ourselves.
|
||||
foreach (int slotId in addedSlots)
|
||||
{
|
||||
ActivityEntity entity = new PlaylistWithSlotActivityEntity
|
||||
{
|
||||
Type = EventType.AddLevelToPlaylist,
|
||||
PlaylistId = playlist.PlaylistId,
|
||||
SlotId = slotId,
|
||||
UserId = playlist.CreatorId,
|
||||
};
|
||||
InsertActivity(database, entity);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
InsertActivity(database, activity);
|
||||
}
|
||||
|
||||
public void OnEntityDeleted<T>(DatabaseContext database, T entity) where T : class
|
||||
{
|
||||
ActivityEntity? activity = entity switch
|
||||
{
|
||||
HeartedLevelEntity heartedLevel => database.Slots.Where(s => s.SlotId == heartedLevel.SlotId)
|
||||
.Select(s => s.Type)
|
||||
.FirstOrDefault() switch
|
||||
{
|
||||
SlotType.User => new LevelActivityEntity
|
||||
{
|
||||
Type = EventType.UnheartLevel,
|
||||
SlotId = heartedLevel.SlotId,
|
||||
UserId = heartedLevel.UserId,
|
||||
},
|
||||
_ => null,
|
||||
},
|
||||
HeartedProfileEntity heartedProfile => new UserActivityEntity
|
||||
{
|
||||
Type = EventType.UnheartUser,
|
||||
TargetUserId = heartedProfile.HeartedUserId,
|
||||
UserId = heartedProfile.UserId,
|
||||
},
|
||||
_ => null,
|
||||
};
|
||||
InsertActivity(database, activity);
|
||||
}
|
||||
}
|
73
ProjectLighthouse/Types/Activity/ActivityGroup.cs
Normal file
73
ProjectLighthouse/Types/Activity/ActivityGroup.cs
Normal file
|
@ -0,0 +1,73 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Types.Activity;
|
||||
|
||||
public struct ActivityGroup
|
||||
{
|
||||
public DateTime Timestamp { get; set; }
|
||||
public int UserId { get; set; }
|
||||
public int? TargetSlotId { get; set; }
|
||||
public int? TargetUserId { get; set; }
|
||||
public int? TargetPlaylistId { get; set; }
|
||||
public int? TargetNewsId { get; set; }
|
||||
public int? TargetTeamPickSlotId { get; set; }
|
||||
|
||||
public int TargetId =>
|
||||
this.GroupType switch
|
||||
{
|
||||
ActivityGroupType.User => this.TargetUserId ?? this.UserId,
|
||||
ActivityGroupType.Level => this.TargetSlotId ?? -1,
|
||||
ActivityGroupType.TeamPick => this.TargetTeamPickSlotId ?? -1,
|
||||
ActivityGroupType.Playlist => this.TargetPlaylistId ?? -1,
|
||||
ActivityGroupType.News => this.TargetNewsId ?? -1,
|
||||
_ => this.UserId,
|
||||
};
|
||||
|
||||
public ActivityGroupType GroupType =>
|
||||
(this.TargetPlaylistId ?? -1) != -1
|
||||
? ActivityGroupType.Playlist
|
||||
: (this.TargetNewsId ?? -1) != -1
|
||||
? ActivityGroupType.News
|
||||
: (this.TargetTeamPickSlotId ?? -1) != -1
|
||||
? ActivityGroupType.TeamPick
|
||||
: (this.TargetSlotId ?? -1) != -1
|
||||
? ActivityGroupType.Level
|
||||
: ActivityGroupType.User;
|
||||
|
||||
public override string ToString() =>
|
||||
$@"{this.GroupType} Group: Timestamp: {this.Timestamp}, UserId: {this.UserId}, TargetId: {this.TargetId}";
|
||||
}
|
||||
|
||||
public struct OuterActivityGroup
|
||||
{
|
||||
public ActivityGroup Key { get; set; }
|
||||
public List<IGrouping<InnerActivityGroup, ActivityDto>> Groups { get; set; }
|
||||
}
|
||||
|
||||
public struct InnerActivityGroup
|
||||
{
|
||||
public ActivityGroupType Type { get; set; }
|
||||
public int UserId { get; set; }
|
||||
public int TargetId { get; set; }
|
||||
}
|
||||
|
||||
public enum ActivityGroupType
|
||||
{
|
||||
[XmlEnum("user")]
|
||||
User,
|
||||
|
||||
[XmlEnum("slot")]
|
||||
Level,
|
||||
|
||||
[XmlEnum("playlist")]
|
||||
Playlist,
|
||||
|
||||
[XmlEnum("news")]
|
||||
News,
|
||||
|
||||
[XmlEnum("slot")]
|
||||
TeamPick,
|
||||
}
|
86
ProjectLighthouse/Types/Activity/EventType.cs
Normal file
86
ProjectLighthouse/Types/Activity/EventType.cs
Normal file
|
@ -0,0 +1,86 @@
|
|||
using System.Xml.Serialization;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Types.Activity;
|
||||
|
||||
/// <summary>
|
||||
/// An enum of all possible event types that LBP recognizes in Recent Activity
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// <see cref="UnheartLevel"/>, <see cref="UnheartUser"/>, <see cref="DeleteLevelComment"/>, <see cref="UnpublishLevel"/> are ignored by the game
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public enum EventType
|
||||
{
|
||||
[XmlEnum("heart_level")]
|
||||
HeartLevel = 0,
|
||||
|
||||
[XmlEnum("unheart_level")]
|
||||
UnheartLevel = 1,
|
||||
|
||||
[XmlEnum("heart_user")]
|
||||
HeartUser = 2,
|
||||
|
||||
[XmlEnum("unheart_user")]
|
||||
UnheartUser = 3,
|
||||
|
||||
[XmlEnum("play_level")]
|
||||
PlayLevel = 4,
|
||||
|
||||
[XmlEnum("rate_level")]
|
||||
RateLevel = 5,
|
||||
|
||||
[XmlEnum("tag_level")]
|
||||
TagLevel = 6,
|
||||
|
||||
[XmlEnum("comment_on_level")]
|
||||
CommentOnLevel = 7,
|
||||
|
||||
[XmlEnum("delete_level_comment")]
|
||||
DeleteLevelComment = 8,
|
||||
|
||||
[XmlEnum("upload_photo")]
|
||||
UploadPhoto = 9,
|
||||
|
||||
[XmlEnum("publish_level")]
|
||||
PublishLevel = 10,
|
||||
|
||||
[XmlEnum("unpublish_level")]
|
||||
UnpublishLevel = 11,
|
||||
|
||||
[XmlEnum("score")]
|
||||
Score = 12,
|
||||
|
||||
[XmlEnum("news_post")]
|
||||
NewsPost = 13,
|
||||
|
||||
[XmlEnum("mm_pick_level")]
|
||||
MMPickLevel = 14,
|
||||
|
||||
[XmlEnum("dpad_rate_level")]
|
||||
DpadRateLevel = 15,
|
||||
|
||||
[XmlEnum("review_level")]
|
||||
ReviewLevel = 16,
|
||||
|
||||
[XmlEnum("comment_on_user")]
|
||||
CommentOnUser = 17,
|
||||
|
||||
/// <remarks>
|
||||
/// This event is only used in LBP3
|
||||
/// </remarks>>
|
||||
[XmlEnum("create_playlist")]
|
||||
CreatePlaylist = 18,
|
||||
|
||||
/// <remarks>
|
||||
/// This event is only used in LBP3
|
||||
/// </remarks>>
|
||||
[XmlEnum("heart_playlist")]
|
||||
HeartPlaylist = 19,
|
||||
|
||||
/// <remarks>
|
||||
/// This event is only used in LBP3
|
||||
/// </remarks>>
|
||||
[XmlEnum("add_level_to_playlist")]
|
||||
AddLevelToPlaylist = 20,
|
||||
}
|
10
ProjectLighthouse/Types/Activity/IEntityEventHandler.cs
Normal file
10
ProjectLighthouse/Types/Activity/IEntityEventHandler.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
using LBPUnion.ProjectLighthouse.Database;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Types.Activity;
|
||||
|
||||
public interface IEntityEventHandler
|
||||
{
|
||||
public void OnEntityInserted<T>(DatabaseContext database, T entity) where T : class;
|
||||
public void OnEntityChanged<T>(DatabaseContext database, T origEntity, T currentEntity) where T : class;
|
||||
public void OnEntityDeleted<T>(DatabaseContext database, T entity) where T : class;
|
||||
}
|
31
ProjectLighthouse/Types/Entities/Activity/ActivityEntity.cs
Normal file
31
ProjectLighthouse/Types/Entities/Activity/ActivityEntity.cs
Normal file
|
@ -0,0 +1,31 @@
|
|||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using LBPUnion.ProjectLighthouse.Types.Activity;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Types.Entities.Activity;
|
||||
|
||||
public class ActivityEntity
|
||||
{
|
||||
[Key]
|
||||
public int ActivityId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The time that this event took place.
|
||||
/// </summary>
|
||||
public DateTime Timestamp { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="UserEntity.UserId"/> of the <see cref="UserEntity"/> that triggered this event.
|
||||
/// </summary>
|
||||
public int UserId { get; set; }
|
||||
|
||||
[ForeignKey(nameof(UserId))]
|
||||
public UserEntity User { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The type of this event.
|
||||
/// </summary>
|
||||
public EventType Type { get; set; }
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using LBPUnion.ProjectLighthouse.Types.Activity;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Types.Entities.Activity;
|
||||
|
||||
/// <summary>
|
||||
/// Supported event types: <see cref="EventType.CommentOnUser"/>, <see cref="EventType.CommentOnLevel"/>, and <see cref="EventType.DeleteLevelComment"/>.
|
||||
/// </summary>
|
||||
public class CommentActivityEntity : ActivityEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="CommentEntity.CommentId"/> of the <see cref="CommentEntity"/> that this event refers to.
|
||||
/// </summary>
|
||||
public int CommentId { get; set; }
|
||||
|
||||
[ForeignKey(nameof(CommentId))]
|
||||
public CommentEntity Comment { get; set; }
|
||||
}
|
||||
|
||||
public class LevelCommentActivityEntity : CommentActivityEntity
|
||||
{
|
||||
[Column("SlotId")]
|
||||
public int SlotId { get; set; }
|
||||
|
||||
[ForeignKey(nameof(SlotId))]
|
||||
public SlotEntity Slot { get; set; }
|
||||
}
|
||||
|
||||
public class UserCommentActivityEntity : CommentActivityEntity
|
||||
{
|
||||
[Column("TargetUserId")]
|
||||
public int TargetUserId { get; set; }
|
||||
|
||||
[ForeignKey(nameof(TargetUserId))]
|
||||
public UserEntity TargetUser { get; set; }
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using LBPUnion.ProjectLighthouse.Types.Activity;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Types.Entities.Activity;
|
||||
|
||||
/// <summary>
|
||||
/// Supported event types: <see cref="EventType.PlayLevel"/>, <see cref="EventType.HeartLevel"/>, <see cref="EventType.PublishLevel"/>,
|
||||
/// <see cref="EventType.UnheartLevel"/>, and <see cref="EventType.MMPickLevel"/>.
|
||||
/// </summary>
|
||||
public class LevelActivityEntity : ActivityEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="SlotEntity.SlotId"/> of the <see cref="SlotEntity"/> that this event refers to.
|
||||
/// </summary>
|
||||
[Column("SlotId")]
|
||||
public int SlotId { get; set; }
|
||||
|
||||
[ForeignKey(nameof(SlotId))]
|
||||
public SlotEntity Slot { get; set; }
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using LBPUnion.ProjectLighthouse.Types.Activity;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Website;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Types.Entities.Activity;
|
||||
|
||||
/// <summary>
|
||||
/// Supported event types: <see cref="EventType.NewsPost"/>.
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This event type can only be grouped with other <see cref="NewsActivityEntity"/>.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public class NewsActivityEntity : ActivityEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="WebsiteAnnouncementEntity.AnnouncementId"/> of the <see cref="WebsiteAnnouncementEntity"/> that this event refers to.
|
||||
/// </summary>
|
||||
public int NewsId { get; set; }
|
||||
|
||||
[ForeignKey(nameof(NewsId))]
|
||||
public WebsiteAnnouncementEntity News { get; set; }
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using LBPUnion.ProjectLighthouse.Types.Activity;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Types.Entities.Activity;
|
||||
|
||||
/// <summary>
|
||||
/// Supported event types: <see cref="EventType.UploadPhoto"/>.
|
||||
/// </summary>
|
||||
public class PhotoActivityEntity : ActivityEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="PhotoEntity.PhotoId"/> of the <see cref="PhotoEntity"/> that this event refers to.
|
||||
/// </summary>
|
||||
public int PhotoId { get; set; }
|
||||
|
||||
[ForeignKey(nameof(PhotoId))]
|
||||
public PhotoEntity Photo { get; set; }
|
||||
}
|
||||
|
||||
public class LevelPhotoActivity : PhotoActivityEntity
|
||||
{
|
||||
[Column("SlotId")]
|
||||
public int SlotId { get; set; }
|
||||
|
||||
[ForeignKey(nameof(SlotId))]
|
||||
public SlotEntity Slot { get; set; }
|
||||
}
|
||||
|
||||
public class UserPhotoActivity : PhotoActivityEntity
|
||||
{
|
||||
[Column("TargetUserId")]
|
||||
public int TargetUserId { get; set; }
|
||||
|
||||
[ForeignKey(nameof(TargetUserId))]
|
||||
public UserEntity TargetUser { get; set; }
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using LBPUnion.ProjectLighthouse.Types.Activity;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Types.Entities.Activity;
|
||||
|
||||
/// <summary>
|
||||
/// Supported event types: <see cref="EventType.CreatePlaylist"/> and <see cref="EventType.HeartPlaylist"/>.
|
||||
/// </summary>
|
||||
public class PlaylistActivityEntity : ActivityEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="PlaylistEntity.PlaylistId"/> of the <see cref="PlaylistEntity"/> that this event refers to.
|
||||
/// </summary>
|
||||
[Column("PlaylistId")]
|
||||
public int PlaylistId { get; set; }
|
||||
|
||||
[ForeignKey(nameof(PlaylistId))]
|
||||
public PlaylistEntity Playlist { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Supported event types: <see cref="EventType.AddLevelToPlaylist"/>.
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The relationship between <see cref="PlaylistActivityEntity"/> and <see cref="PlaylistWithSlotActivityEntity"/>
|
||||
/// is slightly hacky but it allows us to reuse columns that would normally only be user with other <see cref="ActivityEntity"/> types.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public class PlaylistWithSlotActivityEntity : ActivityEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="PlaylistEntity.PlaylistId"/> of the <see cref="PlaylistEntity"/> that this event refers to.
|
||||
/// </summary>
|
||||
[Column("PlaylistId")]
|
||||
public int PlaylistId { get; set; }
|
||||
|
||||
[ForeignKey(nameof(PlaylistId))]
|
||||
public PlaylistEntity Playlist { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// This reuses the SlotId column of <see cref="LevelActivityEntity"/> but has no ForeignKey definition so that it can be null
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// It effectively serves as extra storage for PlaylistActivityEntity to use for the AddLevelToPlaylistEvent
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
[Column("SlotId")]
|
||||
public int SlotId { get; set; }
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using LBPUnion.ProjectLighthouse.Types.Activity;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Types.Entities.Activity;
|
||||
|
||||
/// <summary>
|
||||
/// Supported event types: <see cref="EventType.DpadRateLevel"/>, <see cref="EventType.ReviewLevel"/>, <see cref="EventType.RateLevel"/>, and <see cref="EventType.TagLevel"/>.
|
||||
/// </summary>
|
||||
public class ReviewActivityEntity : ActivityEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="ReviewEntity.ReviewId"/> of the <see cref="ReviewEntity"/> that this event refers to.
|
||||
/// </summary>
|
||||
public int ReviewId { get; set; }
|
||||
|
||||
[ForeignKey(nameof(ReviewId))]
|
||||
public ReviewEntity Review { get; set; }
|
||||
|
||||
[Column("SlotId")]
|
||||
public int SlotId { get; set; }
|
||||
|
||||
[ForeignKey(nameof(SlotId))]
|
||||
public SlotEntity Slot { get; set; }
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using LBPUnion.ProjectLighthouse.Types.Activity;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Types.Entities.Activity;
|
||||
|
||||
/// <summary>
|
||||
/// Supported event types: <see cref="EventType.Score"/>.
|
||||
/// </summary>
|
||||
public class ScoreActivityEntity : ActivityEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="ScoreEntity.ScoreId"/> of the <see cref="ScoreEntity"/> that this event refers to.
|
||||
/// </summary>
|
||||
public int ScoreId { get; set; }
|
||||
|
||||
[ForeignKey(nameof(ScoreId))]
|
||||
public ScoreEntity Score { get; set; }
|
||||
|
||||
[Column("SlotId")]
|
||||
public int SlotId { get; set; }
|
||||
|
||||
[ForeignKey(nameof(SlotId))]
|
||||
public SlotEntity Slot { get; set; }
|
||||
|
||||
public int Points { get; set; }
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using LBPUnion.ProjectLighthouse.Types.Activity;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Types.Entities.Activity;
|
||||
|
||||
/// <summary>
|
||||
/// Supported event types: <see cref="EventType.HeartUser"/> and <see cref="EventType.UnheartUser"/>.
|
||||
/// </summary>
|
||||
public class UserActivityEntity : ActivityEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="UserEntity.UserId"/> of the <see cref="UserEntity"/> that this event refers to.
|
||||
/// </summary>
|
||||
[Column("TargetUserId")]
|
||||
public int TargetUserId { get; set; }
|
||||
|
||||
[ForeignKey(nameof(TargetUserId))]
|
||||
public UserEntity TargetUser { get; set; }
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue