mirror of
https://github.com/LBPUnion/ProjectLighthouse.git
synced 2025-08-03 18:48:40 +00:00
Finish most of Recent Activity
This commit is contained in:
parent
1737a16f38
commit
60d851fb15
77 changed files with 2725 additions and 443 deletions
|
@ -1,6 +1,6 @@
|
||||||
using LBPUnion.ProjectLighthouse.Database;
|
using LBPUnion.ProjectLighthouse.Database;
|
||||||
using LBPUnion.ProjectLighthouse.Filter;
|
using LBPUnion.ProjectLighthouse.Filter;
|
||||||
using LBPUnion.ProjectLighthouse.Filter.Filters;
|
using LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||||
using LBPUnion.ProjectLighthouse.Helpers;
|
using LBPUnion.ProjectLighthouse.Helpers;
|
||||||
using LBPUnion.ProjectLighthouse.Servers.API.Responses;
|
using LBPUnion.ProjectLighthouse.Servers.API.Responses;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Users;
|
using LBPUnion.ProjectLighthouse.Types.Users;
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
using LBPUnion.ProjectLighthouse.Database;
|
using LBPUnion.ProjectLighthouse.Database;
|
||||||
using LBPUnion.ProjectLighthouse.Extensions;
|
using LBPUnion.ProjectLighthouse.Extensions;
|
||||||
|
using LBPUnion.ProjectLighthouse.Filter.Filters.Activity;
|
||||||
using LBPUnion.ProjectLighthouse.Helpers;
|
using LBPUnion.ProjectLighthouse.Helpers;
|
||||||
using LBPUnion.ProjectLighthouse.StorableLists.Stores;
|
using LBPUnion.ProjectLighthouse.StorableLists.Stores;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Activity;
|
using LBPUnion.ProjectLighthouse.Types.Activity;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Entities.Activity;
|
|
||||||
using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
|
|
||||||
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
|
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Levels;
|
using LBPUnion.ProjectLighthouse.Types.Levels;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Serialization.Activity;
|
using LBPUnion.ProjectLighthouse.Types.Serialization.Activity;
|
||||||
|
@ -29,143 +28,210 @@ public class ActivityController : ControllerBase
|
||||||
this.database = database;
|
this.database = database;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ActivityDto
|
/// <summary>
|
||||||
{
|
/// This method is only used for LBP2 so we exclude playlists
|
||||||
public required ActivityEntity Activity { get; set; }
|
/// </summary>
|
||||||
public int? TargetSlotId { get; set; }
|
private async Task<IQueryable<ActivityDto>> GetFilters
|
||||||
public int? TargetUserId { get; set; }
|
|
||||||
public int? TargetPlaylistId { get; set; }
|
|
||||||
public int? SlotCreatorId { get; set; }
|
|
||||||
}
|
|
||||||
//TODO refactor this mess into a separate db file or something
|
|
||||||
|
|
||||||
private static Expression<Func<ActivityEntity, ActivityDto>> ActivityToDto()
|
|
||||||
{
|
|
||||||
return a => new ActivityDto
|
|
||||||
{
|
|
||||||
Activity = a,
|
|
||||||
TargetSlotId = a is LevelActivityEntity
|
|
||||||
? ((LevelActivityEntity)a).SlotId
|
|
||||||
: a is PhotoActivityEntity && ((PhotoActivityEntity)a).Photo.PhotoId != 0
|
|
||||||
? ((PhotoActivityEntity)a).Photo.SlotId
|
|
||||||
: a is CommentActivityEntity && ((CommentActivityEntity)a).Comment.Type == CommentType.Level
|
|
||||||
? ((CommentActivityEntity)a).Comment.TargetId
|
|
||||||
: a is ScoreActivityEntity
|
|
||||||
? ((ScoreActivityEntity)a).Score.SlotId
|
|
||||||
: 0,
|
|
||||||
|
|
||||||
TargetUserId = a is UserActivityEntity
|
|
||||||
? ((UserActivityEntity)a).TargetUserId
|
|
||||||
: a is CommentActivityEntity && ((CommentActivityEntity)a).Comment.Type == CommentType.Profile
|
|
||||||
? ((CommentActivityEntity)a).Comment.TargetId
|
|
||||||
: a is PhotoActivityEntity && ((PhotoActivityEntity)a).Photo.SlotId != 0
|
|
||||||
? ((PhotoActivityEntity)a).Photo.CreatorId
|
|
||||||
: 0,
|
|
||||||
TargetPlaylistId = a is PlaylistActivityEntity ? ((PlaylistActivityEntity)a).PlaylistId : 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IQueryable<IGrouping<ActivityGroup, ActivityEntity>> GroupActivities
|
|
||||||
(IQueryable<ActivityEntity> activityQuery)
|
|
||||||
{
|
|
||||||
return activityQuery.Select(ActivityToDto())
|
|
||||||
.GroupBy(dto => new ActivityGroup
|
|
||||||
{
|
|
||||||
Timestamp = dto.Activity.Timestamp.Date,
|
|
||||||
UserId = dto.Activity.UserId,
|
|
||||||
TargetUserId = dto.TargetUserId,
|
|
||||||
TargetSlotId = dto.TargetSlotId,
|
|
||||||
TargetPlaylistId = dto.TargetPlaylistId,
|
|
||||||
},
|
|
||||||
dto => dto.Activity);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IQueryable<IGrouping<ActivityGroup, ActivityEntity>> GroupActivities
|
|
||||||
(IQueryable<ActivityDto> activityQuery)
|
|
||||||
{
|
|
||||||
return activityQuery.GroupBy(dto => new ActivityGroup
|
|
||||||
{
|
|
||||||
Timestamp = dto.Activity.Timestamp.Date,
|
|
||||||
UserId = dto.Activity.UserId,
|
|
||||||
TargetUserId = dto.TargetUserId,
|
|
||||||
TargetSlotId = dto.TargetSlotId,
|
|
||||||
TargetPlaylistId = dto.TargetPlaylistId,
|
|
||||||
},
|
|
||||||
dto => dto.Activity);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO this is kinda ass, can maybe improve once comment migration is merged
|
|
||||||
private async Task<IQueryable<ActivityEntity>> GetFilters
|
|
||||||
(
|
(
|
||||||
|
IQueryable<ActivityDto> dtoQuery,
|
||||||
GameTokenEntity token,
|
GameTokenEntity token,
|
||||||
bool excludeNews,
|
bool excludeNews,
|
||||||
bool excludeMyLevels,
|
bool excludeMyLevels,
|
||||||
bool excludeFriends,
|
bool excludeFriends,
|
||||||
bool excludeFavouriteUsers,
|
bool excludeFavouriteUsers,
|
||||||
bool excludeMyself
|
bool excludeMyself,
|
||||||
|
bool excludeMyPlaylists = true
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
IQueryable<ActivityEntity> query = this.database.Activities.AsQueryable();
|
|
||||||
if (excludeNews) query = query.Where(a => a.Type != EventType.NewsPost);
|
|
||||||
|
|
||||||
IQueryable<ActivityDto> dtoQuery = query.Select(a => new ActivityDto
|
|
||||||
{
|
|
||||||
Activity = a,
|
|
||||||
SlotCreatorId = a is LevelActivityEntity
|
|
||||||
? ((LevelActivityEntity)a).Slot.CreatorId
|
|
||||||
: a is PhotoActivityEntity && ((PhotoActivityEntity)a).Photo.SlotId != 0
|
|
||||||
? ((PhotoActivityEntity)a).Photo.Slot!.CreatorId
|
|
||||||
: a is CommentActivityEntity && ((CommentActivityEntity)a).Comment.Type == CommentType.Level
|
|
||||||
? ((CommentActivityEntity)a).Comment.TargetId
|
|
||||||
: a is ScoreActivityEntity
|
|
||||||
? ((ScoreActivityEntity)a).Score.Slot.CreatorId
|
|
||||||
: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
Expression<Func<ActivityDto, bool>> predicate = PredicateExtensions.False<ActivityDto>();
|
Expression<Func<ActivityDto, bool>> predicate = PredicateExtensions.False<ActivityDto>();
|
||||||
|
|
||||||
predicate = predicate.Or(a => a.SlotCreatorId == 0 || excludeMyLevels
|
|
||||||
? a.SlotCreatorId != token.UserId
|
|
||||||
: a.SlotCreatorId == token.UserId);
|
|
||||||
|
|
||||||
List<int>? friendIds = UserFriendStore.GetUserFriendData(token.UserId)?.FriendIds;
|
|
||||||
if (friendIds != null)
|
|
||||||
{
|
|
||||||
predicate = excludeFriends
|
|
||||||
? predicate.Or(a => !friendIds.Contains(a.Activity.UserId))
|
|
||||||
: predicate.Or(a => friendIds.Contains(a.Activity.UserId));
|
|
||||||
}
|
|
||||||
|
|
||||||
List<int> favouriteUsers = await this.database.HeartedProfiles.Where(hp => hp.UserId == token.UserId)
|
List<int> favouriteUsers = await this.database.HeartedProfiles.Where(hp => hp.UserId == token.UserId)
|
||||||
.Select(hp => hp.HeartedUserId)
|
.Select(hp => hp.HeartedUserId)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
predicate = excludeFavouriteUsers
|
List<int>? friendIds = UserFriendStore.GetUserFriendData(token.UserId)?.FriendIds;
|
||||||
? predicate.Or(a => !favouriteUsers.Contains(a.Activity.UserId))
|
friendIds ??= new List<int>();
|
||||||
: predicate.Or(a => favouriteUsers.Contains(a.Activity.UserId));
|
|
||||||
|
|
||||||
predicate = excludeMyself
|
// This is how lbp3 does its filtering
|
||||||
? predicate.Or(a => a.Activity.UserId != token.UserId)
|
GameStreamFilter? filter = await this.DeserializeBody<GameStreamFilter>();
|
||||||
: predicate.Or(a => a.Activity.UserId == token.UserId);
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
query = dtoQuery.Where(predicate).Select(dto => dto.Activity);
|
Expression<Func<ActivityDto, bool>> newsPredicate = !excludeNews
|
||||||
|
? new IncludeNewsFilter().GetPredicate()
|
||||||
|
: new ExcludeNewsFilter().GetPredicate();
|
||||||
|
|
||||||
return query.OrderByDescending(a => a.Timestamp);
|
predicate = predicate.Or(newsPredicate);
|
||||||
|
|
||||||
|
if (!excludeMyLevels)
|
||||||
|
{
|
||||||
|
predicate = predicate.Or(dto => dto.TargetSlotCreatorId == token.UserId);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<int> includedUserIds = new();
|
||||||
|
|
||||||
|
if (!excludeFriends)
|
||||||
|
{
|
||||||
|
includedUserIds.AddRange(friendIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!excludeFavouriteUsers)
|
||||||
|
{
|
||||||
|
includedUserIds.AddRange(favouriteUsers);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!excludeMyself)
|
||||||
|
{
|
||||||
|
includedUserIds.Add(token.UserId);
|
||||||
|
}
|
||||||
|
|
||||||
|
predicate = predicate.Or(dto => includedUserIds.Contains(dto.Activity.UserId));
|
||||||
|
|
||||||
|
if (!excludeMyPlaylists)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine(predicate);
|
||||||
|
|
||||||
|
dtoQuery = dtoQuery.Where(predicate);
|
||||||
|
|
||||||
|
return dtoQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<DateTime> GetMostRecentEventTime(GameTokenEntity token, DateTime upperBound)
|
public Task<DateTime> GetMostRecentEventTime(IQueryable<ActivityDto> activity, DateTime upperBound)
|
||||||
{
|
{
|
||||||
return this.database.Activities.Where(a => a.UserId == token.UserId)
|
return activity.OrderByDescending(a => a.Activity.Timestamp)
|
||||||
.Where(a => a.Timestamp < upperBound)
|
.Where(a => a.Activity.Timestamp < upperBound)
|
||||||
.OrderByDescending(a => a.Timestamp)
|
.Select(a => a.Activity.Timestamp)
|
||||||
.Select(a => a.Timestamp)
|
|
||||||
.FirstOrDefaultAsync();
|
.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.Any()
|
||||||
|
? 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,
|
||||||
|
excludeNews,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
excludeMyself,
|
||||||
|
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]
|
[HttpGet]
|
||||||
public async Task<IActionResult> GlobalActivity
|
public async Task<IActionResult> GlobalActivity
|
||||||
(
|
(
|
||||||
long timestamp,
|
long timestamp,
|
||||||
|
long endTimestamp,
|
||||||
bool excludeNews,
|
bool excludeNews,
|
||||||
bool excludeMyLevels,
|
bool excludeMyLevels,
|
||||||
bool excludeFriends,
|
bool excludeFriends,
|
||||||
|
@ -175,112 +241,109 @@ public class ActivityController : ControllerBase
|
||||||
{
|
{
|
||||||
GameTokenEntity token = this.GetToken();
|
GameTokenEntity token = this.GetToken();
|
||||||
|
|
||||||
if (token.GameVersion == GameVersion.LittleBigPlanet1) return this.BadRequest();
|
if (token.GameVersion == GameVersion.LittleBigPlanet1) return this.NotFound();
|
||||||
|
|
||||||
if (timestamp > TimeHelper.TimestampMillis || timestamp <= 0) timestamp = TimeHelper.TimestampMillis;
|
IQueryable<ActivityDto> activityEvents = await this.GetFilters(this.database.Activities.ToActivityDto(true),
|
||||||
|
token,
|
||||||
DateTime start = DateTimeExtensions.FromUnixTimeMilliseconds(timestamp);
|
|
||||||
|
|
||||||
DateTime soonestTime = await this.GetMostRecentEventTime(token, start);
|
|
||||||
Console.WriteLine(@"Most recent event occurred at " + soonestTime);
|
|
||||||
soonestTime = soonestTime.Subtract(TimeSpan.FromDays(1));
|
|
||||||
|
|
||||||
long soonestTimestamp = soonestTime.ToUnixTimeMilliseconds();
|
|
||||||
|
|
||||||
long endTimestamp = soonestTimestamp - 86_400_000;
|
|
||||||
|
|
||||||
Console.WriteLine(@$"soonestTime: {soonestTimestamp}, endTime: {endTimestamp}");
|
|
||||||
|
|
||||||
IQueryable<ActivityEntity> activityEvents = await this.GetFilters(token,
|
|
||||||
excludeNews,
|
excludeNews,
|
||||||
excludeMyLevels,
|
excludeMyLevels,
|
||||||
excludeFriends,
|
excludeFriends,
|
||||||
excludeFavouriteUsers,
|
excludeFavouriteUsers,
|
||||||
excludeMyself);
|
excludeMyself);
|
||||||
|
|
||||||
DateTime end = DateTimeExtensions.FromUnixTimeMilliseconds(endTimestamp);
|
(DateTime Start, DateTime End) times = await this.GetTimeBounds(activityEvents, timestamp, endTimestamp);
|
||||||
|
|
||||||
activityEvents = activityEvents.Where(a => a.Timestamp < start && a.Timestamp > end);
|
List<IGrouping<ActivityGroup, ActivityDto>> groups = await activityEvents
|
||||||
|
.Where(dto => dto.Activity.Timestamp < times.Start && dto.Activity.Timestamp > times.End)
|
||||||
|
.ToActivityGroups()
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
Console.WriteLine($@"start: {start}, end: {end}");
|
List<OuterActivityGroup> outerGroups = groups.ToOuterActivityGroups();
|
||||||
|
|
||||||
List<IGrouping<ActivityGroup, ActivityEntity>> groups = await GroupActivities(activityEvents).ToListAsync();
|
long oldestTimestamp = GetOldestTime(groups, times.Start).ToUnixTimeMilliseconds();
|
||||||
|
|
||||||
foreach (IGrouping<ActivityGroup, ActivityEntity> group in groups)
|
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)
|
||||||
{
|
{
|
||||||
ActivityGroup key = group.Key;
|
Console.WriteLine(@$"Outer group key: {outer.Key}");
|
||||||
Console.WriteLine(
|
List<IGrouping<InnerActivityGroup, ActivityDto>> itemGroup = outer.Groups;
|
||||||
$@"{key.GroupType}: Timestamp: {key.Timestamp}, UserId: {key.UserId}, TargetSlotId: {key.TargetSlotId}, " +
|
foreach (IGrouping<InnerActivityGroup, ActivityDto> item in itemGroup)
|
||||||
@$"TargetUserId: {key.TargetUserId}, TargetPlaylistId: {key.TargetPlaylistId}");
|
|
||||||
foreach (ActivityEntity activity in group)
|
|
||||||
{
|
{
|
||||||
Console.WriteLine($@" {activity.Type}: Timestamp: {activity.Timestamp}");
|
Console.WriteLine(
|
||||||
|
@$" Inner group key: TargetId={item.Key.TargetId}, UserId={item.Key.UserId}, Type={item.Key.Type}");
|
||||||
|
foreach (ActivityDto activity in item)
|
||||||
|
{
|
||||||
|
Console.WriteLine(
|
||||||
|
@$" Activity: {activity.GroupType}, Timestamp: {activity.Activity.Timestamp}, UserId: {activity.Activity.UserId}, EventType: {activity.Activity.Type}, TargetId: {activity.TargetId}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DateTime oldestTime = groups.Any() ? groups.Min(g => g.Any() ? g.Min(a => a.Timestamp) : end) : end;
|
|
||||||
long oldestTimestamp = oldestTime.ToUnixTimeMilliseconds();
|
|
||||||
|
|
||||||
return this.Ok(await GameStream.CreateFromEntityResult(this.database, token, groups, timestamp, oldestTimestamp));
|
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
[HttpGet("slot/{slotType}/{slotId:int}")]
|
[HttpGet("slot/{slotType}/{slotId:int}")]
|
||||||
public async Task<IActionResult> SlotActivity(string slotType, int slotId, long timestamp)
|
[HttpGet("user2/{username}")]
|
||||||
|
public async Task<IActionResult> SlotActivity(string? slotType, int slotId, string? username, long? timestamp)
|
||||||
{
|
{
|
||||||
GameTokenEntity token = this.GetToken();
|
GameTokenEntity token = this.GetToken();
|
||||||
|
|
||||||
if (token.GameVersion == GameVersion.LittleBigPlanet1) return this.BadRequest();
|
if (token.GameVersion == GameVersion.LittleBigPlanet1) return this.NotFound();
|
||||||
|
|
||||||
if (timestamp > TimeHelper.TimestampMillis || timestamp <= 0) timestamp = TimeHelper.TimestampMillis;
|
if ((SlotHelper.IsTypeInvalid(slotType) || slotId == 0) == (username == null)) return this.BadRequest();
|
||||||
|
|
||||||
long endTimestamp = timestamp - 864_000;
|
IQueryable<ActivityDto> activityQuery = this.database.Activities.ToActivityDto()
|
||||||
|
.Where(a => a.Activity.Type != EventType.NewsPost && a.Activity.Type != EventType.MMPickLevel);
|
||||||
|
|
||||||
if (slotType is not ("developer" or "user")) return this.BadRequest();
|
bool isLevelActivity = username == null;
|
||||||
|
|
||||||
if (slotType == "developer")
|
// Slot activity
|
||||||
slotId = await SlotHelper.GetPlaceholderSlotId(this.database, slotId, SlotType.Developer);
|
if (isLevelActivity)
|
||||||
|
{
|
||||||
|
if (slotType == "developer")
|
||||||
|
slotId = await SlotHelper.GetPlaceholderSlotId(this.database, slotId, SlotType.Developer);
|
||||||
|
|
||||||
IQueryable<ActivityDto> slotActivity = this.database.Activities.Select(ActivityToDto())
|
if (!await this.database.Slots.AnyAsync(s => s.SlotId == slotId)) return this.NotFound();
|
||||||
.Where(a => a.TargetSlotId == slotId);
|
|
||||||
|
|
||||||
DateTime start = DateTimeOffset.FromUnixTimeMilliseconds(timestamp).DateTime;
|
activityQuery = activityQuery.Where(dto => dto.TargetSlotId == slotId);
|
||||||
DateTime end = DateTimeOffset.FromUnixTimeMilliseconds(endTimestamp).DateTime;
|
}
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
slotActivity = slotActivity.Where(a => a.Activity.Timestamp < start && a.Activity.Timestamp > end);
|
(DateTime Start, DateTime End) times = await this.GetTimeBounds(activityQuery, timestamp, null);
|
||||||
|
|
||||||
List<IGrouping<ActivityGroup, ActivityEntity>> groups = await GroupActivities(slotActivity).ToListAsync();
|
activityQuery = activityQuery.Where(dto =>
|
||||||
|
dto.Activity.Timestamp < times.Start && dto.Activity.Timestamp > times.End);
|
||||||
|
|
||||||
DateTime oldestTime = groups.Max(g => g.Max(a => a.Timestamp));
|
List<IGrouping<ActivityGroup, ActivityDto>> groups = await activityQuery.ToActivityGroups().ToListAsync();
|
||||||
long oldestTimestamp = new DateTimeOffset(oldestTime).ToUnixTimeMilliseconds();
|
|
||||||
|
|
||||||
return this.Ok(await GameStream.CreateFromEntityResult(this.database, token, groups, timestamp, oldestTimestamp));
|
List<OuterActivityGroup> outerGroups = groups.ToOuterActivityGroups();
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("user2/{userId:int}/")]
|
long oldestTimestamp = GetOldestTime(groups, times.Start).ToUnixTimeMilliseconds();
|
||||||
public async Task<IActionResult> UserActivity(int userId, long timestamp)
|
|
||||||
{
|
|
||||||
GameTokenEntity token = this.GetToken();
|
|
||||||
|
|
||||||
if (token.GameVersion == GameVersion.LittleBigPlanet1) return this.BadRequest();
|
await this.CacheEntities(outerGroups);
|
||||||
|
|
||||||
if (timestamp > TimeHelper.TimestampMillis || timestamp <= 0) timestamp = TimeHelper.TimestampMillis;
|
return this.Ok(GameStream.CreateFromGroups(token,
|
||||||
|
outerGroups,
|
||||||
long endTimestamp = timestamp - 864_000;
|
times.Start.ToUnixTimeMilliseconds(),
|
||||||
|
oldestTimestamp));
|
||||||
IQueryable<ActivityDto> userActivity = this.database.Activities.Select(ActivityToDto())
|
|
||||||
.Where(a => a.TargetUserId == userId);
|
|
||||||
|
|
||||||
DateTime start = DateTimeOffset.FromUnixTimeMilliseconds(timestamp).DateTime;
|
|
||||||
DateTime end = DateTimeOffset.FromUnixTimeMilliseconds(endTimestamp).DateTime;
|
|
||||||
|
|
||||||
userActivity = userActivity.Where(a => a.Activity.Timestamp < start && a.Activity.Timestamp > end);
|
|
||||||
|
|
||||||
List<IGrouping<ActivityGroup, ActivityEntity>> groups = await GroupActivities(userActivity).ToListAsync();
|
|
||||||
|
|
||||||
DateTime oldestTime = groups.Max(g => g.Max(a => a.Timestamp));
|
|
||||||
long oldestTimestamp = new DateTimeOffset(oldestTime).ToUnixTimeMilliseconds();
|
|
||||||
|
|
||||||
return this.Ok(
|
|
||||||
await GameStream.CreateFromEntityResult(this.database, token, groups, timestamp, oldestTimestamp));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
|
@ -141,6 +141,22 @@ public class ReviewController : ControllerBase
|
||||||
return this.Ok();
|
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}")]
|
[HttpGet("reviewsFor/user/{slotId:int}")]
|
||||||
public async Task<IActionResult> ReviewsFor(int slotId)
|
public async Task<IActionResult> ReviewsFor(int slotId)
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
using LBPUnion.ProjectLighthouse.Database;
|
using LBPUnion.ProjectLighthouse.Database;
|
||||||
using LBPUnion.ProjectLighthouse.Extensions;
|
using LBPUnion.ProjectLighthouse.Extensions;
|
||||||
using LBPUnion.ProjectLighthouse.Filter;
|
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;
|
||||||
using LBPUnion.ProjectLighthouse.Servers.GameServer.Extensions;
|
using LBPUnion.ProjectLighthouse.Servers.GameServer.Extensions;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||||
|
|
|
@ -3,7 +3,7 @@ using System.Linq.Expressions;
|
||||||
using LBPUnion.ProjectLighthouse.Database;
|
using LBPUnion.ProjectLighthouse.Database;
|
||||||
using LBPUnion.ProjectLighthouse.Extensions;
|
using LBPUnion.ProjectLighthouse.Extensions;
|
||||||
using LBPUnion.ProjectLighthouse.Filter;
|
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;
|
||||||
using LBPUnion.ProjectLighthouse.Filter.Sorts.Metadata;
|
using LBPUnion.ProjectLighthouse.Filter.Sorts.Metadata;
|
||||||
using LBPUnion.ProjectLighthouse.Helpers;
|
using LBPUnion.ProjectLighthouse.Helpers;
|
||||||
|
|
|
@ -4,7 +4,7 @@ using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using LBPUnion.ProjectLighthouse.Extensions;
|
using LBPUnion.ProjectLighthouse.Extensions;
|
||||||
using LBPUnion.ProjectLighthouse.Filter;
|
using LBPUnion.ProjectLighthouse.Filter;
|
||||||
using LBPUnion.ProjectLighthouse.Filter.Filters;
|
using LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||||
using LBPUnion.ProjectLighthouse.Servers.GameServer.Extensions;
|
using LBPUnion.ProjectLighthouse.Servers.GameServer.Extensions;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Serialization.Slot;
|
using LBPUnion.ProjectLighthouse.Types.Serialization.Slot;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
using LBPUnion.ProjectLighthouse.Filter;
|
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.Entities.Token;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Levels;
|
using LBPUnion.ProjectLighthouse.Types.Levels;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Users;
|
using LBPUnion.ProjectLighthouse.Types.Users;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#nullable enable
|
#nullable enable
|
||||||
using LBPUnion.ProjectLighthouse.Database;
|
using LBPUnion.ProjectLighthouse.Database;
|
||||||
using LBPUnion.ProjectLighthouse.Filter;
|
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.Level;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
|
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
using LBPUnion.ProjectLighthouse.Database;
|
using LBPUnion.ProjectLighthouse.Database;
|
||||||
using LBPUnion.ProjectLighthouse.Extensions;
|
using LBPUnion.ProjectLighthouse.Extensions;
|
||||||
using LBPUnion.ProjectLighthouse.Filter;
|
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;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
|
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
|
||||||
|
|
|
@ -0,0 +1,727 @@
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
CommentEntity comment = new()
|
||||||
|
{
|
||||||
|
CommentId = 1,
|
||||||
|
PosterUserId = 1,
|
||||||
|
TargetId = 1,
|
||||||
|
Type = CommentType.Level,
|
||||||
|
};
|
||||||
|
database.Comments.Add(comment);
|
||||||
|
await database.SaveChangesAsync();
|
||||||
|
|
||||||
|
eventHandler.OnEntityInserted(database, comment);
|
||||||
|
|
||||||
|
Assert.NotNull(database.Activities.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,
|
||||||
|
TargetId = 1,
|
||||||
|
Type = CommentType.Profile,
|
||||||
|
};
|
||||||
|
database.Comments.Add(comment);
|
||||||
|
await database.SaveChangesAsync();
|
||||||
|
|
||||||
|
eventHandler.OnEntityInserted(database, comment);
|
||||||
|
|
||||||
|
Assert.NotNull(database.Activities.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,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
PhotoEntity photo = new()
|
||||||
|
{
|
||||||
|
PhotoId = 1,
|
||||||
|
CreatorId = 1,
|
||||||
|
};
|
||||||
|
database.Photos.Add(photo);
|
||||||
|
await database.SaveChangesAsync();
|
||||||
|
|
||||||
|
eventHandler.OnEntityInserted(database, photo);
|
||||||
|
|
||||||
|
Assert.NotNull(database.Activities.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,
|
||||||
|
PlayerIdCollection = "test",
|
||||||
|
};
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
|
||||||
|
eventHandler.OnEntityInserted(database, heartedLevel);
|
||||||
|
|
||||||
|
Assert.NotNull(database.Activities.OfType<LevelActivityEntity>()
|
||||||
|
.FirstOrDefault(a => a.Type == EventType.HeartLevel && a.SlotId == 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[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 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 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,
|
||||||
|
TeamPick = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
eventHandler.OnEntityChanged(database, oldSlot, newSlot);
|
||||||
|
|
||||||
|
Assert.NotNull(database.Activities.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.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.OfType<CommentActivityEntity>()
|
||||||
|
.FirstOrDefault(a => a.Type == EventType.DeleteLevelComment && 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 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));
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
namespace ProjectLighthouse.Tests.GameApiTests.Unit.Controllers;
|
||||||
|
|
||||||
|
public class ActivityControllerTests
|
||||||
|
{
|
||||||
|
//TODO write activity controller tests
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
using LBPUnion.ProjectLighthouse.Filter;
|
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.Controllers.Slots;
|
||||||
using LBPUnion.ProjectLighthouse.Servers.GameServer.Extensions;
|
using LBPUnion.ProjectLighthouse.Servers.GameServer.Extensions;
|
||||||
using LBPUnion.ProjectLighthouse.Tests.Helpers;
|
using LBPUnion.ProjectLighthouse.Tests.Helpers;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using LBPUnion.ProjectLighthouse.Filter;
|
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.Level;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
|
using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Levels;
|
using LBPUnion.ProjectLighthouse.Types.Levels;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
using System;
|
||||||
using LBPUnion.ProjectLighthouse.Configuration;
|
using LBPUnion.ProjectLighthouse.Configuration;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Activity;
|
using LBPUnion.ProjectLighthouse.Types.Activity;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Entities.Activity;
|
using LBPUnion.ProjectLighthouse.Types.Entities.Activity;
|
||||||
|
@ -90,30 +91,34 @@ public partial class DatabaseContext : DbContext
|
||||||
public static DatabaseContext CreateNewInstance()
|
public static DatabaseContext CreateNewInstance()
|
||||||
{
|
{
|
||||||
DbContextOptionsBuilder<DatabaseContext> builder = new();
|
DbContextOptionsBuilder<DatabaseContext> builder = new();
|
||||||
builder.UseMySql(ServerConfiguration.Instance.DbConnectionString,
|
ConfigureBuilder()(builder);
|
||||||
MySqlServerVersion.LatestSupportedServerVersion);
|
|
||||||
return new DatabaseContext(builder.Options);
|
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
|
#region Activity
|
||||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||||
{
|
{
|
||||||
//TODO implement reviews
|
|
||||||
modelBuilder.Entity<LevelActivityEntity>().UseTphMappingStrategy();
|
modelBuilder.Entity<LevelActivityEntity>().UseTphMappingStrategy();
|
||||||
modelBuilder.Entity<PhotoActivityEntity>().UseTphMappingStrategy();
|
modelBuilder.Entity<PhotoActivityEntity>().UseTphMappingStrategy();
|
||||||
modelBuilder.Entity<PlaylistActivityEntity>().UseTphMappingStrategy();
|
modelBuilder.Entity<PlaylistActivityEntity>().UseTphMappingStrategy();
|
||||||
|
modelBuilder.Entity<PlaylistWithSlotActivityEntity>().UseTphMappingStrategy();
|
||||||
modelBuilder.Entity<ScoreActivityEntity>().UseTphMappingStrategy();
|
modelBuilder.Entity<ScoreActivityEntity>().UseTphMappingStrategy();
|
||||||
modelBuilder.Entity<UserActivityEntity>().UseTphMappingStrategy();
|
modelBuilder.Entity<UserActivityEntity>().UseTphMappingStrategy();
|
||||||
modelBuilder.Entity<NewsActivityEntity>().UseTphMappingStrategy();
|
modelBuilder.Entity<NewsActivityEntity>().UseTphMappingStrategy();
|
||||||
modelBuilder.Entity<CommentActivityEntity>().UseTphMappingStrategy();
|
modelBuilder.Entity<CommentActivityEntity>().UseTphMappingStrategy();
|
||||||
modelBuilder.Entity<UserActivityEntity>().UseTphMappingStrategy();
|
modelBuilder.Entity<UserActivityEntity>().UseTphMappingStrategy();
|
||||||
|
modelBuilder.Entity<ReviewActivityEntity>().UseTphMappingStrategy();
|
||||||
base.OnModelCreating(modelBuilder);
|
base.OnModelCreating(modelBuilder);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
|
||||||
{
|
|
||||||
optionsBuilder.AddInterceptors(new ActivityInterceptor(new ActivityEntityEventHandler()));
|
|
||||||
base.OnConfiguring(optionsBuilder);
|
|
||||||
}
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
121
ProjectLighthouse/Extensions/ActivityQueryExtensions.cs
Normal file
121
ProjectLighthouse/Extensions/ActivityQueryExtensions.cs
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using LBPUnion.ProjectLighthouse.Types.Activity;
|
||||||
|
using LBPUnion.ProjectLighthouse.Types.Entities.Activity;
|
||||||
|
using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
|
||||||
|
|
||||||
|
namespace LBPUnion.ProjectLighthouse.Extensions;
|
||||||
|
|
||||||
|
public static class ActivityQueryExtensions
|
||||||
|
{
|
||||||
|
public static List<int> GetIds(this IReadOnlyCollection<OuterActivityGroup> groups, ActivityGroupType type)
|
||||||
|
{
|
||||||
|
List<int> ids = new();
|
||||||
|
// 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
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 ?? 0,
|
||||||
|
TargetTeamPickSlotId = dto.TargetTeamPickId ?? 0,
|
||||||
|
})
|
||||||
|
: activityQuery.GroupBy(dto => new ActivityGroup
|
||||||
|
{
|
||||||
|
Timestamp = dto.Activity.Timestamp.Date,
|
||||||
|
TargetUserId = dto.TargetUserId ?? 0,
|
||||||
|
TargetSlotId = dto.TargetSlotId ?? 0,
|
||||||
|
TargetPlaylistId = dto.TargetPlaylistId ?? 0,
|
||||||
|
TargetNewsId = dto.TargetNewsId ?? 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
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.User : ActivityGroupType.News,
|
||||||
|
UserId = gr.Activity.UserId,
|
||||||
|
TargetId = groupByActor ? gr.TargetId : gr.Activity.UserId,
|
||||||
|
})
|
||||||
|
.ToList(),
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// WARNING - To the next person who tries to improve this code: As of writing this, it's not possible
|
||||||
|
// to build a pattern matching switch statement with expression trees. so the only other option
|
||||||
|
// is to basically rewrite this nested ternary mess with expression trees which isn't much better
|
||||||
|
// The resulting SQL generated by EntityFramework uses a CASE statement which is probably fine
|
||||||
|
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 is LevelActivityEntity
|
||||||
|
? ((LevelActivityEntity)a).SlotId
|
||||||
|
: a is PhotoActivityEntity && ((PhotoActivityEntity)a).Photo.PhotoId != 0
|
||||||
|
? ((PhotoActivityEntity)a).Photo.SlotId
|
||||||
|
: a is CommentActivityEntity && ((CommentActivityEntity)a).Comment.Type == CommentType.Level
|
||||||
|
? ((CommentActivityEntity)a).Comment.TargetId
|
||||||
|
: a is ScoreActivityEntity
|
||||||
|
? ((ScoreActivityEntity)a).Score.SlotId
|
||||||
|
: a is ReviewActivityEntity
|
||||||
|
? ((ReviewActivityEntity)a).Review.SlotId
|
||||||
|
: 0,
|
||||||
|
|
||||||
|
TargetUserId = a is UserActivityEntity
|
||||||
|
? ((UserActivityEntity)a).TargetUserId
|
||||||
|
: a is CommentActivityEntity && ((CommentActivityEntity)a).Comment.Type == CommentType.Profile
|
||||||
|
? ((CommentActivityEntity)a).Comment.TargetId
|
||||||
|
: a is PhotoActivityEntity && ((PhotoActivityEntity)a).Photo.SlotId != 0
|
||||||
|
? ((PhotoActivityEntity)a).Photo.CreatorId
|
||||||
|
: 0,
|
||||||
|
TargetPlaylistId = a is PlaylistActivityEntity || a is PlaylistWithSlotActivityEntity
|
||||||
|
? ((PlaylistActivityEntity)a).PlaylistId
|
||||||
|
: 0,
|
||||||
|
TargetNewsId = a is NewsActivityEntity ? ((NewsActivityEntity)a).NewsId : 0,
|
||||||
|
TargetTeamPickId = includeTeamPick
|
||||||
|
? a.Type == EventType.MMPickLevel && a is LevelActivityEntity ? ((LevelActivityEntity)a).SlotId : 0
|
||||||
|
: 0,
|
||||||
|
TargetSlotCreatorId = includeSlotCreator
|
||||||
|
? a is LevelActivityEntity
|
||||||
|
? ((LevelActivityEntity)a).Slot.CreatorId
|
||||||
|
: a is PhotoActivityEntity && ((PhotoActivityEntity)a).Photo.SlotId != 0
|
||||||
|
? ((PhotoActivityEntity)a).Photo.Slot!.CreatorId
|
||||||
|
: a is CommentActivityEntity && ((CommentActivityEntity)a).Comment.Type == CommentType.Level
|
||||||
|
? ((CommentActivityEntity)a).Comment.TargetId
|
||||||
|
: a is ScoreActivityEntity
|
||||||
|
? ((ScoreActivityEntity)a).Score.Slot.CreatorId
|
||||||
|
: a is ReviewActivityEntity
|
||||||
|
? ((ReviewActivityEntity)a).Review.Slot!.CreatorId
|
||||||
|
: 0
|
||||||
|
: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -44,6 +44,7 @@ public static partial class ControllerExtensions
|
||||||
public static async Task<T?> DeserializeBody<T>(this ControllerBase controller, params string[] rootElements)
|
public static async Task<T?> DeserializeBody<T>(this ControllerBase controller, params string[] rootElements)
|
||||||
{
|
{
|
||||||
string bodyString = await controller.ReadBodyAsync();
|
string bodyString = await controller.ReadBodyAsync();
|
||||||
|
if (bodyString.Length == 0) return default;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Prevent unescaped ampersands from causing deserialization to fail
|
// Prevent unescaped ampersands from causing deserialization to fail
|
||||||
|
|
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.Entities.Level;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||||
|
|
||||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||||
|
|
||||||
public class AdventureFilter : ISlotFilter
|
public class AdventureFilter : ISlotFilter
|
||||||
{
|
{
|
|
@ -5,7 +5,7 @@ using LBPUnion.ProjectLighthouse.Extensions;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||||
|
|
||||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||||
|
|
||||||
public class AuthorLabelFilter : ISlotFilter
|
public class AuthorLabelFilter : ISlotFilter
|
||||||
{
|
{
|
||||||
|
@ -20,7 +20,7 @@ public class AuthorLabelFilter : ISlotFilter
|
||||||
{
|
{
|
||||||
Expression<Func<SlotEntity, bool>> predicate = PredicateExtensions.True<SlotEntity>();
|
Expression<Func<SlotEntity, bool>> predicate = PredicateExtensions.True<SlotEntity>();
|
||||||
predicate = this.labels.Aggregate(predicate,
|
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;
|
return predicate;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -3,7 +3,7 @@ using System.Linq.Expressions;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||||
|
|
||||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||||
|
|
||||||
public class CreatorFilter : ISlotFilter
|
public class CreatorFilter : ISlotFilter
|
||||||
{
|
{
|
|
@ -3,7 +3,7 @@ using System.Linq.Expressions;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||||
|
|
||||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||||
|
|
||||||
public class CrossControlFilter : ISlotFilter
|
public class CrossControlFilter : ISlotFilter
|
||||||
{
|
{
|
|
@ -3,7 +3,7 @@ using System.Linq.Expressions;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||||
|
|
||||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||||
|
|
||||||
public class ExcludeAdventureFilter : ISlotFilter
|
public class ExcludeAdventureFilter : ISlotFilter
|
||||||
{
|
{
|
|
@ -3,7 +3,7 @@ using System.Linq.Expressions;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||||
|
|
||||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||||
|
|
||||||
public class ExcludeCrossControlFilter : ISlotFilter
|
public class ExcludeCrossControlFilter : ISlotFilter
|
||||||
{
|
{
|
|
@ -4,7 +4,7 @@ using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Users;
|
using LBPUnion.ProjectLighthouse.Types.Users;
|
||||||
|
|
||||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||||
|
|
||||||
public class ExcludeLBP1OnlyFilter : ISlotFilter
|
public class ExcludeLBP1OnlyFilter : ISlotFilter
|
||||||
{
|
{
|
|
@ -3,7 +3,7 @@ using System.Linq.Expressions;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||||
|
|
||||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||||
|
|
||||||
public class ExcludeMovePackFilter : ISlotFilter
|
public class ExcludeMovePackFilter : ISlotFilter
|
||||||
{
|
{
|
|
@ -4,7 +4,7 @@ using LBPUnion.ProjectLighthouse.Extensions;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||||
|
|
||||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||||
|
|
||||||
public class FirstUploadedFilter : ISlotFilter
|
public class FirstUploadedFilter : ISlotFilter
|
||||||
{
|
{
|
|
@ -4,7 +4,7 @@ using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Users;
|
using LBPUnion.ProjectLighthouse.Types.Users;
|
||||||
|
|
||||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||||
|
|
||||||
public class GameVersionFilter : ISlotFilter
|
public class GameVersionFilter : ISlotFilter
|
||||||
{
|
{
|
|
@ -6,7 +6,7 @@ using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Users;
|
using LBPUnion.ProjectLighthouse.Types.Users;
|
||||||
|
|
||||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||||
|
|
||||||
public class GameVersionListFilter : ISlotFilter
|
public class GameVersionListFilter : ISlotFilter
|
||||||
{
|
{
|
||||||
|
@ -19,5 +19,5 @@ public class GameVersionListFilter : ISlotFilter
|
||||||
|
|
||||||
public Expression<Func<SlotEntity, bool>> GetPredicate() =>
|
public Expression<Func<SlotEntity, bool>> GetPredicate() =>
|
||||||
this.versions.Aggregate(PredicateExtensions.False<SlotEntity>(),
|
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.Entities.Level;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||||
|
|
||||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||||
|
|
||||||
public class HiddenSlotFilter : ISlotFilter
|
public class HiddenSlotFilter : ISlotFilter
|
||||||
{
|
{
|
|
@ -3,7 +3,7 @@ using System.Linq.Expressions;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||||
|
|
||||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||||
|
|
||||||
public class MovePackFilter : ISlotFilter
|
public class MovePackFilter : ISlotFilter
|
||||||
{
|
{
|
|
@ -4,7 +4,7 @@ using LBPUnion.ProjectLighthouse.Extensions;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||||
|
|
||||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||||
|
|
||||||
public class PlayerCountFilter : ISlotFilter
|
public class PlayerCountFilter : ISlotFilter
|
||||||
{
|
{
|
|
@ -4,7 +4,7 @@ using System.Linq.Expressions;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||||
|
|
||||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||||
|
|
||||||
public class ResultTypeFilter : ISlotFilter
|
public class ResultTypeFilter : ISlotFilter
|
||||||
{
|
{
|
|
@ -6,7 +6,7 @@ using LBPUnion.ProjectLighthouse.Extensions;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||||
|
|
||||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||||
|
|
||||||
public class SlotIdFilter : ISlotFilter
|
public class SlotIdFilter : ISlotFilter
|
||||||
{
|
{
|
||||||
|
@ -20,7 +20,7 @@ public class SlotIdFilter : ISlotFilter
|
||||||
public Expression<Func<SlotEntity, bool>> GetPredicate()
|
public Expression<Func<SlotEntity, bool>> GetPredicate()
|
||||||
{
|
{
|
||||||
Expression<Func<SlotEntity, bool>> predicate = PredicateExtensions.False<SlotEntity>();
|
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;
|
return predicate;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -4,7 +4,7 @@ using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Levels;
|
using LBPUnion.ProjectLighthouse.Types.Levels;
|
||||||
|
|
||||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||||
|
|
||||||
public class SlotTypeFilter : ISlotFilter
|
public class SlotTypeFilter : ISlotFilter
|
||||||
{
|
{
|
|
@ -3,7 +3,7 @@ using System.Linq.Expressions;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||||
|
|
||||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||||
|
|
||||||
public class SubLevelFilter : ISlotFilter
|
public class SubLevelFilter : ISlotFilter
|
||||||
{
|
{
|
|
@ -3,7 +3,7 @@ using System.Linq.Expressions;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||||
|
|
||||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||||
|
|
||||||
public class TeamPickFilter : ISlotFilter
|
public class TeamPickFilter : ISlotFilter
|
||||||
{
|
{
|
|
@ -4,7 +4,7 @@ using LBPUnion.ProjectLighthouse.Extensions;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||||
|
|
||||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||||
|
|
||||||
public class TextFilter : ISlotFilter
|
public class TextFilter : ISlotFilter
|
||||||
{
|
{
|
149
ProjectLighthouse/Migrations/20230725013522_InitialActivity.cs
Normal file
149
ProjectLighthouse/Migrations/20230725013522_InitialActivity.cs
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
using System;
|
||||||
|
using LBPUnion.ProjectLighthouse.Database;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace ProjectLighthouse.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(DatabaseContext))]
|
||||||
|
[Migration("20230725013522_InitialActivity")]
|
||||||
|
public partial class InitialActivity : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
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: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
CommentId = table.Column<int>(type: "int", nullable: true),
|
||||||
|
SlotId = table.Column<int>(type: "int", nullable: true),
|
||||||
|
NewsId = table.Column<int>(type: "int", nullable: true),
|
||||||
|
PhotoId = 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),
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Activities");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,6 +19,36 @@ namespace ProjectLighthouse.Migrations
|
||||||
.HasAnnotation("ProductVersion", "8.0.2")
|
.HasAnnotation("ProductVersion", "8.0.2")
|
||||||
.HasAnnotation("Relational:MaxIdentifierLength", 64);
|
.HasAnnotation("Relational:MaxIdentifierLength", 64);
|
||||||
|
|
||||||
|
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Entities.Activity.ActivityEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("ActivityId")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Discriminator")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Timestamp")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<int>("Type")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int>("UserId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.HasKey("ActivityId");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("Activities");
|
||||||
|
|
||||||
|
b.HasDiscriminator<string>("Discriminator").HasValue("ActivityEntity");
|
||||||
|
|
||||||
|
b.UseTphMappingStrategy();
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Entities.Interaction.HeartedLevelEntity", b =>
|
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Entities.Interaction.HeartedLevelEntity", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("HeartedLevelId")
|
b.Property<int>("HeartedLevelId")
|
||||||
|
@ -1090,6 +1120,136 @@ namespace ProjectLighthouse.Migrations
|
||||||
b.ToTable("WebsiteAnnouncements");
|
b.ToTable("WebsiteAnnouncements");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Entities.Activity.CommentActivityEntity", b =>
|
||||||
|
{
|
||||||
|
b.HasBaseType("LBPUnion.ProjectLighthouse.Types.Entities.Activity.ActivityEntity");
|
||||||
|
|
||||||
|
b.Property<int>("CommentId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.HasIndex("CommentId");
|
||||||
|
|
||||||
|
b.HasDiscriminator().HasValue("CommentActivityEntity");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Entities.Activity.LevelActivityEntity", b =>
|
||||||
|
{
|
||||||
|
b.HasBaseType("LBPUnion.ProjectLighthouse.Types.Entities.Activity.ActivityEntity");
|
||||||
|
|
||||||
|
b.Property<int>("SlotId")
|
||||||
|
.ValueGeneratedOnUpdateSometimes()
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasColumnName("SlotId");
|
||||||
|
|
||||||
|
b.HasIndex("SlotId");
|
||||||
|
|
||||||
|
b.HasDiscriminator().HasValue("LevelActivityEntity");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Entities.Activity.NewsActivityEntity", b =>
|
||||||
|
{
|
||||||
|
b.HasBaseType("LBPUnion.ProjectLighthouse.Types.Entities.Activity.ActivityEntity");
|
||||||
|
|
||||||
|
b.Property<int>("NewsId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.HasIndex("NewsId");
|
||||||
|
|
||||||
|
b.HasDiscriminator().HasValue("NewsActivityEntity");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Entities.Activity.PhotoActivityEntity", b =>
|
||||||
|
{
|
||||||
|
b.HasBaseType("LBPUnion.ProjectLighthouse.Types.Entities.Activity.ActivityEntity");
|
||||||
|
|
||||||
|
b.Property<int>("PhotoId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.HasIndex("PhotoId");
|
||||||
|
|
||||||
|
b.HasDiscriminator().HasValue("PhotoActivityEntity");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Entities.Activity.PlaylistActivityEntity", b =>
|
||||||
|
{
|
||||||
|
b.HasBaseType("LBPUnion.ProjectLighthouse.Types.Entities.Activity.ActivityEntity");
|
||||||
|
|
||||||
|
b.Property<int>("PlaylistId")
|
||||||
|
.ValueGeneratedOnUpdateSometimes()
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasColumnName("PlaylistId");
|
||||||
|
|
||||||
|
b.HasIndex("PlaylistId");
|
||||||
|
|
||||||
|
b.HasDiscriminator().HasValue("PlaylistActivityEntity");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Entities.Activity.PlaylistWithSlotActivityEntity", b =>
|
||||||
|
{
|
||||||
|
b.HasBaseType("LBPUnion.ProjectLighthouse.Types.Entities.Activity.ActivityEntity");
|
||||||
|
|
||||||
|
b.Property<int>("PlaylistId")
|
||||||
|
.ValueGeneratedOnUpdateSometimes()
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasColumnName("PlaylistId");
|
||||||
|
|
||||||
|
b.Property<int>("SlotId")
|
||||||
|
.ValueGeneratedOnUpdateSometimes()
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasColumnName("SlotId");
|
||||||
|
|
||||||
|
b.HasIndex("PlaylistId");
|
||||||
|
|
||||||
|
b.HasDiscriminator().HasValue("PlaylistWithSlotActivityEntity");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Entities.Activity.ReviewActivityEntity", b =>
|
||||||
|
{
|
||||||
|
b.HasBaseType("LBPUnion.ProjectLighthouse.Types.Entities.Activity.ActivityEntity");
|
||||||
|
|
||||||
|
b.Property<int>("ReviewId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.HasIndex("ReviewId");
|
||||||
|
|
||||||
|
b.HasDiscriminator().HasValue("ReviewActivityEntity");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Entities.Activity.ScoreActivityEntity", b =>
|
||||||
|
{
|
||||||
|
b.HasBaseType("LBPUnion.ProjectLighthouse.Types.Entities.Activity.ActivityEntity");
|
||||||
|
|
||||||
|
b.Property<int>("ScoreId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.HasIndex("ScoreId");
|
||||||
|
|
||||||
|
b.HasDiscriminator().HasValue("ScoreActivityEntity");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Entities.Activity.UserActivityEntity", b =>
|
||||||
|
{
|
||||||
|
b.HasBaseType("LBPUnion.ProjectLighthouse.Types.Entities.Activity.ActivityEntity");
|
||||||
|
|
||||||
|
b.Property<int>("TargetUserId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.HasIndex("TargetUserId");
|
||||||
|
|
||||||
|
b.HasDiscriminator().HasValue("UserActivityEntity");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Entities.Activity.ActivityEntity", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("LBPUnion.ProjectLighthouse.Types.Entities.Profile.UserEntity", "User")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Entities.Interaction.HeartedLevelEntity", b =>
|
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Entities.Interaction.HeartedLevelEntity", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("LBPUnion.ProjectLighthouse.Types.Entities.Level.SlotEntity", "Slot")
|
b.HasOne("LBPUnion.ProjectLighthouse.Types.Entities.Level.SlotEntity", "Slot")
|
||||||
|
@ -1483,6 +1643,105 @@ namespace ProjectLighthouse.Migrations
|
||||||
b.Navigation("Publisher");
|
b.Navigation("Publisher");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Entities.Activity.CommentActivityEntity", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("LBPUnion.ProjectLighthouse.Types.Entities.Profile.CommentEntity", "Comment")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("CommentId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Comment");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Entities.Activity.LevelActivityEntity", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("LBPUnion.ProjectLighthouse.Types.Entities.Level.SlotEntity", "Slot")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("SlotId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Slot");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Entities.Activity.NewsActivityEntity", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("LBPUnion.ProjectLighthouse.Types.Entities.Website.WebsiteAnnouncementEntity", "News")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("NewsId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("News");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Entities.Activity.PhotoActivityEntity", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("LBPUnion.ProjectLighthouse.Types.Entities.Profile.PhotoEntity", "Photo")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("PhotoId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Photo");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Entities.Activity.PlaylistActivityEntity", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("LBPUnion.ProjectLighthouse.Types.Entities.Level.PlaylistEntity", "Playlist")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("PlaylistId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Playlist");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Entities.Activity.PlaylistWithSlotActivityEntity", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("LBPUnion.ProjectLighthouse.Types.Entities.Level.PlaylistEntity", "Playlist")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("PlaylistId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Playlist");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Entities.Activity.ReviewActivityEntity", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("LBPUnion.ProjectLighthouse.Types.Entities.Level.ReviewEntity", "Review")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ReviewId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Review");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Entities.Activity.ScoreActivityEntity", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("LBPUnion.ProjectLighthouse.Types.Entities.Level.ScoreEntity", "Score")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ScoreId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Score");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Entities.Activity.UserActivityEntity", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("LBPUnion.ProjectLighthouse.Types.Entities.Profile.UserEntity", "TargetUser")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("TargetUserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("TargetUser");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Entities.Profile.PhotoEntity", b =>
|
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Entities.Profile.PhotoEntity", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("PhotoSubjects");
|
b.Navigation("PhotoSubjects");
|
||||||
|
|
33
ProjectLighthouse/Types/Activity/ActivityDto.cs
Normal file
33
ProjectLighthouse/Types/Activity/ActivityDto.cs
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
using LBPUnion.ProjectLighthouse.Types.Entities.Activity;
|
||||||
|
|
||||||
|
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 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 ?? 0,
|
||||||
|
ActivityGroupType.Level => this.TargetSlotId ?? 0,
|
||||||
|
ActivityGroupType.Playlist => this.TargetPlaylistId ?? 0,
|
||||||
|
ActivityGroupType.News => this.TargetNewsId ?? 0,
|
||||||
|
_ => this.Activity.UserId,
|
||||||
|
};
|
||||||
|
|
||||||
|
public ActivityGroupType GroupType =>
|
||||||
|
this.TargetSlotId != 0
|
||||||
|
? ActivityGroupType.Level
|
||||||
|
: this.TargetUserId != 0
|
||||||
|
? ActivityGroupType.User
|
||||||
|
: this.TargetPlaylistId != 0
|
||||||
|
? ActivityGroupType.Playlist
|
||||||
|
: ActivityGroupType.News;
|
||||||
|
}
|
|
@ -4,12 +4,13 @@ using System.ComponentModel.DataAnnotations.Schema;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using LBPUnion.ProjectLighthouse.Database;
|
using LBPUnion.ProjectLighthouse.Database;
|
||||||
using LBPUnion.ProjectLighthouse.Helpers;
|
|
||||||
using LBPUnion.ProjectLighthouse.Types.Entities.Activity;
|
using LBPUnion.ProjectLighthouse.Types.Entities.Activity;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Entities.Interaction;
|
using LBPUnion.ProjectLighthouse.Types.Entities.Interaction;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
|
using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
|
||||||
|
using LBPUnion.ProjectLighthouse.Types.Entities.Website;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Levels;
|
using LBPUnion.ProjectLighthouse.Types.Levels;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
namespace LBPUnion.ProjectLighthouse.Types.Activity;
|
namespace LBPUnion.ProjectLighthouse.Types.Activity;
|
||||||
|
|
||||||
|
@ -44,7 +45,7 @@ public class ActivityEntityEventHandler : IEntityEventHandler
|
||||||
Type = EventType.Score,
|
Type = EventType.Score,
|
||||||
ScoreId = score.ScoreId,
|
ScoreId = score.ScoreId,
|
||||||
//TODO merge score migration
|
//TODO merge score migration
|
||||||
// UserId = int.Parse(score.PlayerIds[0]),
|
UserId = database.Users.Where(u => u.Username == score.PlayerIds[0]).Select(u => u.UserId).First(),
|
||||||
},
|
},
|
||||||
HeartedLevelEntity heartedLevel => new LevelActivityEntity
|
HeartedLevelEntity heartedLevel => new LevelActivityEntity
|
||||||
{
|
{
|
||||||
|
@ -58,12 +59,42 @@ public class ActivityEntityEventHandler : IEntityEventHandler
|
||||||
TargetUserId = heartedProfile.HeartedUserId,
|
TargetUserId = heartedProfile.HeartedUserId,
|
||||||
UserId = heartedProfile.UserId,
|
UserId = heartedProfile.UserId,
|
||||||
},
|
},
|
||||||
|
HeartedPlaylistEntity heartedPlaylist => new PlaylistActivityEntity
|
||||||
|
{
|
||||||
|
Type = EventType.HeartPlaylist,
|
||||||
|
PlaylistId = heartedPlaylist.PlaylistId,
|
||||||
|
UserId = heartedPlaylist.UserId,
|
||||||
|
},
|
||||||
VisitedLevelEntity visitedLevel => new LevelActivityEntity
|
VisitedLevelEntity visitedLevel => new LevelActivityEntity
|
||||||
{
|
{
|
||||||
Type = EventType.PlayLevel,
|
Type = EventType.PlayLevel,
|
||||||
SlotId = visitedLevel.SlotId,
|
SlotId = visitedLevel.SlotId,
|
||||||
UserId = visitedLevel.UserId,
|
UserId = visitedLevel.UserId,
|
||||||
},
|
},
|
||||||
|
ReviewEntity review => new ReviewActivityEntity
|
||||||
|
{
|
||||||
|
Type = EventType.ReviewLevel,
|
||||||
|
ReviewId = review.ReviewId,
|
||||||
|
UserId = review.ReviewerId,
|
||||||
|
},
|
||||||
|
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,
|
_ => null,
|
||||||
};
|
};
|
||||||
InsertActivity(database, activity);
|
InsertActivity(database, activity);
|
||||||
|
@ -82,6 +113,7 @@ public class ActivityEntityEventHandler : IEntityEventHandler
|
||||||
|
|
||||||
public void OnEntityChanged<T>(DatabaseContext database, T origEntity, T currentEntity) where T : class
|
public void OnEntityChanged<T>(DatabaseContext database, T origEntity, T currentEntity) where T : class
|
||||||
{
|
{
|
||||||
|
#if DEBUG
|
||||||
foreach (PropertyInfo propInfo in currentEntity.GetType().GetProperties())
|
foreach (PropertyInfo propInfo in currentEntity.GetType().GetProperties())
|
||||||
{
|
{
|
||||||
if (!propInfo.CanRead || !propInfo.CanWrite) continue;
|
if (!propInfo.CanRead || !propInfo.CanWrite) continue;
|
||||||
|
@ -97,14 +129,19 @@ public class ActivityEntityEventHandler : IEntityEventHandler
|
||||||
Console.WriteLine($@"Orig val: {origVal?.ToString() ?? "null"}");
|
Console.WriteLine($@"Orig val: {origVal?.ToString() ?? "null"}");
|
||||||
Console.WriteLine($@"New val: {newVal?.ToString() ?? "null"}");
|
Console.WriteLine($@"New val: {newVal?.ToString() ?? "null"}");
|
||||||
}
|
}
|
||||||
|
|
||||||
Console.WriteLine($@"OnEntityChanged: {currentEntity.GetType().Name}");
|
Console.WriteLine($@"OnEntityChanged: {currentEntity.GetType().Name}");
|
||||||
|
#endif
|
||||||
|
|
||||||
ActivityEntity? activity = null;
|
ActivityEntity? activity = null;
|
||||||
switch (currentEntity)
|
switch (currentEntity)
|
||||||
{
|
{
|
||||||
case VisitedLevelEntity visitedLevel:
|
case VisitedLevelEntity visitedLevel:
|
||||||
{
|
{
|
||||||
if (origEntity is not VisitedLevelEntity) break;
|
if (origEntity is not VisitedLevelEntity oldVisitedLevel) break;
|
||||||
|
|
||||||
|
int Plays(VisitedLevelEntity entity) => entity.PlaysLBP1 + entity.PlaysLBP2 + entity.PlaysLBP3;
|
||||||
|
|
||||||
|
if (Plays(oldVisitedLevel) >= Plays(visitedLevel)) break;
|
||||||
|
|
||||||
activity = new LevelActivityEntity
|
activity = new LevelActivityEntity
|
||||||
{
|
{
|
||||||
|
@ -118,25 +155,88 @@ public class ActivityEntityEventHandler : IEntityEventHandler
|
||||||
{
|
{
|
||||||
if (origEntity is not SlotEntity oldSlotEntity) break;
|
if (origEntity is not SlotEntity oldSlotEntity) break;
|
||||||
|
|
||||||
if (!oldSlotEntity.TeamPick && slotEntity.TeamPick)
|
switch (oldSlotEntity.TeamPick)
|
||||||
{
|
{
|
||||||
activity = new LevelActivityEntity
|
// When a level is team picked
|
||||||
|
case false when slotEntity.TeamPick:
|
||||||
|
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 !slotEntity.TeamPick:
|
||||||
|
database.Activities.OfType<LevelActivityEntity>()
|
||||||
|
.Where(a => a.Type == EventType.MMPickLevel)
|
||||||
|
.Where(a => a.SlotId == slotEntity.SlotId)
|
||||||
|
.ExecuteDelete();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
{
|
{
|
||||||
Type = EventType.MMPickLevel,
|
if (oldSlotEntity.SlotId == slotEntity.SlotId &&
|
||||||
SlotId = slotEntity.SlotId,
|
slotEntity.Type == SlotType.User &&
|
||||||
UserId = SlotHelper.GetPlaceholderUserId(database).Result,
|
oldSlotEntity.LastUpdated != slotEntity.LastUpdated)
|
||||||
};
|
{
|
||||||
}
|
activity = new LevelActivityEntity
|
||||||
else if (oldSlotEntity.SlotId == slotEntity.SlotId && slotEntity.Type == SlotType.User)
|
{
|
||||||
{
|
Type = EventType.PublishLevel,
|
||||||
activity = new LevelActivityEntity
|
SlotId = slotEntity.SlotId,
|
||||||
{
|
UserId = slotEntity.CreatorId,
|
||||||
Type = EventType.PublishLevel,
|
};
|
||||||
SlotId = slotEntity.SlotId,
|
}
|
||||||
UserId = slotEntity.CreatorId,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CommentEntity comment:
|
||||||
|
{
|
||||||
|
if (origEntity is not CommentEntity oldComment) 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;
|
||||||
|
Console.WriteLine($@"Old playlist slots: {string.Join(",", oldSlots)}");
|
||||||
|
Console.WriteLine($@"New playlist slots: {string.Join(",", newSlots)}");
|
||||||
|
|
||||||
|
int[] addedSlots = newSlots.Except(oldSlots).ToArray();
|
||||||
|
|
||||||
|
Console.WriteLine($@"Added playlist slots: {string.Join(",", addedSlots)}");
|
||||||
|
|
||||||
|
// 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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -149,17 +249,6 @@ public class ActivityEntityEventHandler : IEntityEventHandler
|
||||||
Console.WriteLine($@"OnEntityDeleted: {entity.GetType().Name}");
|
Console.WriteLine($@"OnEntityDeleted: {entity.GetType().Name}");
|
||||||
ActivityEntity? activity = entity switch
|
ActivityEntity? activity = entity switch
|
||||||
{
|
{
|
||||||
//TODO move this to EntityModified and use CommentEntity.Deleted
|
|
||||||
CommentEntity comment => comment.Type switch
|
|
||||||
{
|
|
||||||
CommentType.Level => new CommentActivityEntity
|
|
||||||
{
|
|
||||||
Type = EventType.DeleteLevelComment,
|
|
||||||
CommentId = comment.CommentId,
|
|
||||||
UserId = comment.PosterUserId,
|
|
||||||
},
|
|
||||||
_ => null,
|
|
||||||
},
|
|
||||||
HeartedLevelEntity heartedLevel => new LevelActivityEntity
|
HeartedLevelEntity heartedLevel => new LevelActivityEntity
|
||||||
{
|
{
|
||||||
Type = EventType.UnheartLevel,
|
Type = EventType.UnheartLevel,
|
||||||
|
|
|
@ -1,31 +1,59 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Xml.Serialization;
|
using System.Xml.Serialization;
|
||||||
|
|
||||||
namespace LBPUnion.ProjectLighthouse.Types.Activity;
|
namespace LBPUnion.ProjectLighthouse.Types.Activity;
|
||||||
|
|
||||||
public class ActivityGroup
|
public struct ActivityGroup
|
||||||
{
|
{
|
||||||
public DateTime Timestamp { get; set; }
|
public DateTime Timestamp { get; set; }
|
||||||
public int UserId { get; set; }
|
public int UserId { get; set; }
|
||||||
public int? TargetSlotId { get; set; }
|
public int? TargetSlotId { get; set; }
|
||||||
public int? TargetUserId { get; set; }
|
public int? TargetUserId { get; set; }
|
||||||
public int? TargetPlaylistId { get; set; }
|
public int? TargetPlaylistId { get; set; }
|
||||||
|
public int? TargetNewsId { get; set; }
|
||||||
|
public int? TargetTeamPickSlotId { get; set; }
|
||||||
|
|
||||||
public int TargetId =>
|
public int TargetId =>
|
||||||
this.GroupType switch
|
this.GroupType switch
|
||||||
{
|
{
|
||||||
ActivityGroupType.User => this.TargetUserId ?? 0,
|
ActivityGroupType.User => this.TargetUserId ?? this.UserId,
|
||||||
ActivityGroupType.Level => this.TargetSlotId ?? 0,
|
ActivityGroupType.Level => this.TargetSlotId?? 0,
|
||||||
|
ActivityGroupType.TeamPick => this.TargetTeamPickSlotId ?? 0,
|
||||||
ActivityGroupType.Playlist => this.TargetPlaylistId ?? 0,
|
ActivityGroupType.Playlist => this.TargetPlaylistId ?? 0,
|
||||||
|
ActivityGroupType.News => this.TargetNewsId ?? 0,
|
||||||
_ => this.UserId,
|
_ => this.UserId,
|
||||||
};
|
};
|
||||||
|
|
||||||
public ActivityGroupType GroupType =>
|
public ActivityGroupType GroupType =>
|
||||||
this.TargetSlotId != 0
|
(this.TargetSlotId ?? 0) != 0
|
||||||
? ActivityGroupType.Level
|
? ActivityGroupType.Level
|
||||||
: this.TargetUserId != 0
|
: (this.TargetUserId ?? 0) != 0
|
||||||
? ActivityGroupType.User
|
? ActivityGroupType.User
|
||||||
: ActivityGroupType.Playlist;
|
: (this.TargetPlaylistId ?? 0) != 0
|
||||||
|
? ActivityGroupType.Playlist
|
||||||
|
: (this.TargetNewsId ?? 0) != 0
|
||||||
|
? ActivityGroupType.News
|
||||||
|
: (this.TargetTeamPickSlotId ?? 0) != 0
|
||||||
|
? ActivityGroupType.TeamPick
|
||||||
|
: 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
|
public enum ActivityGroupType
|
||||||
|
@ -38,4 +66,10 @@ public enum ActivityGroupType
|
||||||
|
|
||||||
[XmlEnum("playlist")]
|
[XmlEnum("playlist")]
|
||||||
Playlist,
|
Playlist,
|
||||||
|
|
||||||
|
[XmlEnum("news")]
|
||||||
|
News,
|
||||||
|
|
||||||
|
[XmlEnum("slot")]
|
||||||
|
TeamPick,
|
||||||
}
|
}
|
|
@ -2,68 +2,71 @@
|
||||||
|
|
||||||
namespace LBPUnion.ProjectLighthouse.Types.Activity;
|
namespace LBPUnion.ProjectLighthouse.Types.Activity;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// UnheartLevel, UnheartUser, DeleteLevelComment, and UnpublishLevel don't actually do anything
|
||||||
|
/// </summary>
|
||||||
public enum EventType
|
public enum EventType
|
||||||
{
|
{
|
||||||
[XmlEnum("heart_level")]
|
[XmlEnum("heart_level")]
|
||||||
HeartLevel,
|
HeartLevel = 0,
|
||||||
|
|
||||||
[XmlEnum("unheart_level")]
|
[XmlEnum("unheart_level")]
|
||||||
UnheartLevel,
|
UnheartLevel = 1,
|
||||||
|
|
||||||
[XmlEnum("heart_user")]
|
[XmlEnum("heart_user")]
|
||||||
HeartUser,
|
HeartUser = 2,
|
||||||
|
|
||||||
[XmlEnum("unheart_user")]
|
[XmlEnum("unheart_user")]
|
||||||
UnheartUser,
|
UnheartUser = 3,
|
||||||
|
|
||||||
[XmlEnum("play_level")]
|
[XmlEnum("play_level")]
|
||||||
PlayLevel,
|
PlayLevel = 4,
|
||||||
|
|
||||||
[XmlEnum("rate_level")]
|
[XmlEnum("rate_level")]
|
||||||
RateLevel,
|
RateLevel = 5,
|
||||||
|
|
||||||
[XmlEnum("tag_level")]
|
[XmlEnum("tag_level")]
|
||||||
TagLevel,
|
TagLevel = 6,
|
||||||
|
|
||||||
[XmlEnum("comment_on_level")]
|
[XmlEnum("comment_on_level")]
|
||||||
CommentOnLevel,
|
CommentOnLevel = 7,
|
||||||
|
|
||||||
[XmlEnum("delete_level_comment")]
|
[XmlEnum("delete_level_comment")]
|
||||||
DeleteLevelComment,
|
DeleteLevelComment = 8,
|
||||||
|
|
||||||
[XmlEnum("upload_photo")]
|
[XmlEnum("upload_photo")]
|
||||||
UploadPhoto,
|
UploadPhoto = 9,
|
||||||
|
|
||||||
[XmlEnum("publish_level")]
|
[XmlEnum("publish_level")]
|
||||||
PublishLevel,
|
PublishLevel = 10,
|
||||||
|
|
||||||
[XmlEnum("unpublish_level")]
|
[XmlEnum("unpublish_level")]
|
||||||
UnpublishLevel,
|
UnpublishLevel = 11,
|
||||||
|
|
||||||
[XmlEnum("score")]
|
[XmlEnum("score")]
|
||||||
Score,
|
Score = 12,
|
||||||
|
|
||||||
[XmlEnum("news_post")]
|
[XmlEnum("news_post")]
|
||||||
NewsPost,
|
NewsPost = 13,
|
||||||
|
|
||||||
[XmlEnum("mm_pick_level")]
|
[XmlEnum("mm_pick_level")]
|
||||||
MMPickLevel,
|
MMPickLevel = 14,
|
||||||
|
|
||||||
[XmlEnum("dpad_rate_level")]
|
[XmlEnum("dpad_rate_level")]
|
||||||
DpadRateLevel,
|
DpadRateLevel = 15,
|
||||||
|
|
||||||
[XmlEnum("review_level")]
|
[XmlEnum("review_level")]
|
||||||
ReviewLevel,
|
ReviewLevel = 16,
|
||||||
|
|
||||||
[XmlEnum("comment_on_user")]
|
[XmlEnum("comment_on_user")]
|
||||||
CommentOnUser,
|
CommentOnUser = 17,
|
||||||
|
|
||||||
[XmlEnum("create_playlist")]
|
[XmlEnum("create_playlist")]
|
||||||
CreatePlaylist,
|
CreatePlaylist = 18,
|
||||||
|
|
||||||
[XmlEnum("heart_playlist")]
|
[XmlEnum("heart_playlist")]
|
||||||
HeartPlaylist,
|
HeartPlaylist = 19,
|
||||||
|
|
||||||
[XmlEnum("add_level_to_playlist")]
|
[XmlEnum("add_level_to_playlist")]
|
||||||
AddLevelToPlaylist,
|
AddLevelToPlaylist = 20,
|
||||||
}
|
}
|
|
@ -4,14 +4,13 @@ using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||||
namespace LBPUnion.ProjectLighthouse.Types.Entities.Activity;
|
namespace LBPUnion.ProjectLighthouse.Types.Entities.Activity;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Supported event types: play_level, heart_level, publish_level,
|
/// Supported event types: play_level, heart_level, publish_level, unheart_level, dpad_rate_level, rate_level, tag_level, mm_pick_level
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class LevelActivityEntity : ActivityEntity
|
public class LevelActivityEntity : ActivityEntity
|
||||||
{
|
{
|
||||||
|
[Column("SlotId")]
|
||||||
public int SlotId { get; set; }
|
public int SlotId { get; set; }
|
||||||
|
|
||||||
[ForeignKey(nameof(SlotId))]
|
[ForeignKey(nameof(SlotId))]
|
||||||
public SlotEntity Slot { get; set; }
|
public SlotEntity Slot { get; set; }
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,11 +1,15 @@
|
||||||
namespace LBPUnion.ProjectLighthouse.Types.Entities.Activity;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using LBPUnion.ProjectLighthouse.Types.Entities.Website;
|
||||||
|
|
||||||
|
namespace LBPUnion.ProjectLighthouse.Types.Entities.Activity;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Supported event types: NewsPost
|
/// Supported event types: NewsPost
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class NewsActivityEntity : ActivityEntity
|
public class NewsActivityEntity : ActivityEntity
|
||||||
{
|
{
|
||||||
public string Title { get; set; } = "";
|
public int NewsId { get; set; }
|
||||||
|
|
||||||
public string Body { get; set; } = "";
|
[ForeignKey(nameof(NewsId))]
|
||||||
|
public WebsiteAnnouncementEntity News { get; set; }
|
||||||
}
|
}
|
|
@ -4,12 +4,37 @@ using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||||
namespace LBPUnion.ProjectLighthouse.Types.Entities.Activity;
|
namespace LBPUnion.ProjectLighthouse.Types.Entities.Activity;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Supported event types: CreatePlaylist, HeartPlaylist, AddLevelToPlaylist
|
/// Supported event types: CreatePlaylist, HeartPlaylist
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class PlaylistActivityEntity : ActivityEntity
|
public class PlaylistActivityEntity : ActivityEntity
|
||||||
{
|
{
|
||||||
|
[Column("PlaylistId")]
|
||||||
public int PlaylistId { get; set; }
|
public int PlaylistId { get; set; }
|
||||||
|
|
||||||
[ForeignKey(nameof(PlaylistId))]
|
[ForeignKey(nameof(PlaylistId))]
|
||||||
public PlaylistEntity Playlist { get; set; }
|
public PlaylistEntity Playlist { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Supported event types: AddLevelToPlaylist
|
||||||
|
/// <para>
|
||||||
|
/// The relationship between <see cref="PlaylistActivityEntity"/> and <see cref="PlaylistWithSlotActivityEntity"/>
|
||||||
|
/// is slightly hacky but it allows conditional reuse of columns from other ActivityEntity's
|
||||||
|
///
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
public class PlaylistWithSlotActivityEntity : ActivityEntity
|
||||||
|
{
|
||||||
|
[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
|
||||||
|
/// <para>It effectively serves as extra storage for PlaylistActivityEntity to use for the AddLevelToPlaylistEvent</para>
|
||||||
|
/// </summary>
|
||||||
|
[Column("SlotId")]
|
||||||
|
public int SlotId { get; set; }
|
||||||
}
|
}
|
|
@ -3,12 +3,13 @@ using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||||
|
|
||||||
namespace LBPUnion.ProjectLighthouse.Types.Entities.Activity;
|
namespace LBPUnion.ProjectLighthouse.Types.Entities.Activity;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Supported event types: DpadRateLevel, ReviewLevel, RateLevel, TagLevel
|
||||||
|
/// </summary>
|
||||||
public class ReviewActivityEntity : ActivityEntity
|
public class ReviewActivityEntity : ActivityEntity
|
||||||
{
|
{
|
||||||
public int ReviewId { get; set; }
|
public int ReviewId { get; set; }
|
||||||
|
|
||||||
[ForeignKey(nameof(ReviewId))]
|
[ForeignKey(nameof(ReviewId))]
|
||||||
public ReviewEntity Review { get; set; }
|
public ReviewEntity Review { get; set; }
|
||||||
|
|
||||||
// TODO review_modified?
|
|
||||||
}
|
}
|
6
ProjectLighthouse/Types/Filter/IActivityFilter.cs
Normal file
6
ProjectLighthouse/Types/Filter/IActivityFilter.cs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
using LBPUnion.ProjectLighthouse.Types.Activity;
|
||||||
|
|
||||||
|
namespace LBPUnion.ProjectLighthouse.Types.Filter;
|
||||||
|
|
||||||
|
public interface IActivityFilter : IFilter<ActivityDto>
|
||||||
|
{ }
|
|
@ -0,0 +1,26 @@
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Xml.Serialization;
|
||||||
|
using LBPUnion.ProjectLighthouse.Database;
|
||||||
|
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||||
|
using LBPUnion.ProjectLighthouse.Types.Serialization.Review;
|
||||||
|
|
||||||
|
namespace LBPUnion.ProjectLighthouse.Types.Serialization.Activity.Events;
|
||||||
|
|
||||||
|
public class GameAddLevelToPlaylistEvent : GameEvent
|
||||||
|
{
|
||||||
|
[XmlElement("object_playlist_id")]
|
||||||
|
public int TargetPlaylistId { get; set; }
|
||||||
|
|
||||||
|
[XmlElement("object_slot_id")]
|
||||||
|
public ReviewSlot Slot { get; set; }
|
||||||
|
|
||||||
|
public new async Task PrepareSerialization(DatabaseContext database)
|
||||||
|
{
|
||||||
|
await base.PrepareSerialization(database);
|
||||||
|
|
||||||
|
SlotEntity slot = await database.Slots.FindAsync(this.Slot.SlotId);
|
||||||
|
if (slot == null) return;
|
||||||
|
|
||||||
|
this.Slot = ReviewSlot.CreateFromEntity(slot);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Xml.Serialization;
|
||||||
|
using LBPUnion.ProjectLighthouse.Database;
|
||||||
|
|
||||||
|
namespace LBPUnion.ProjectLighthouse.Types.Serialization.Activity.Events;
|
||||||
|
|
||||||
|
public class GameCreatePlaylistEvent : GameEvent
|
||||||
|
{
|
||||||
|
[XmlElement("object_playlist_id")]
|
||||||
|
public int TargetPlaylistId { get; set; }
|
||||||
|
|
||||||
|
public new async Task PrepareSerialization(DatabaseContext database)
|
||||||
|
{
|
||||||
|
await base.PrepareSerialization(database);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Xml.Serialization;
|
||||||
|
using LBPUnion.ProjectLighthouse.Database;
|
||||||
|
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||||
|
using LBPUnion.ProjectLighthouse.Types.Serialization.Review;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace LBPUnion.ProjectLighthouse.Types.Serialization.Activity.Events;
|
||||||
|
|
||||||
|
public class GameDpadRateLevelEvent : GameEvent
|
||||||
|
{
|
||||||
|
[XmlElement("object_slot_id")]
|
||||||
|
public ReviewSlot Slot { get; set; }
|
||||||
|
|
||||||
|
[XmlElement("dpad_rating")]
|
||||||
|
public int Rating { get; set; }
|
||||||
|
|
||||||
|
public new async Task PrepareSerialization(DatabaseContext database)
|
||||||
|
{
|
||||||
|
await base.PrepareSerialization(database);
|
||||||
|
|
||||||
|
SlotEntity slot = await database.Slots.FindAsync(this.Slot.SlotId);
|
||||||
|
if (slot == null) return;
|
||||||
|
|
||||||
|
this.Slot = ReviewSlot.CreateFromEntity(slot);
|
||||||
|
|
||||||
|
this.Rating = await database.RatedLevels.Where(r => r.SlotId == slot.SlotId && r.UserId == this.UserId)
|
||||||
|
.Select(r => r.Rating)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Xml.Serialization;
|
using System.Xml.Serialization;
|
||||||
|
@ -19,10 +20,19 @@ namespace LBPUnion.ProjectLighthouse.Types.Serialization.Activity.Events;
|
||||||
[XmlInclude(typeof(GameScoreEvent))]
|
[XmlInclude(typeof(GameScoreEvent))]
|
||||||
[XmlInclude(typeof(GameHeartLevelEvent))]
|
[XmlInclude(typeof(GameHeartLevelEvent))]
|
||||||
[XmlInclude(typeof(GameHeartUserEvent))]
|
[XmlInclude(typeof(GameHeartUserEvent))]
|
||||||
|
[XmlInclude(typeof(GameHeartPlaylistEvent))]
|
||||||
|
[XmlInclude(typeof(GameReviewEvent))]
|
||||||
|
[XmlInclude(typeof(GamePublishLevelEvent))]
|
||||||
|
[XmlInclude(typeof(GameRateLevelEvent))]
|
||||||
|
[XmlInclude(typeof(GameDpadRateLevelEvent))]
|
||||||
|
[XmlInclude(typeof(GameTeamPickLevelEvent))]
|
||||||
|
[XmlInclude(typeof(GameNewsEvent))]
|
||||||
|
[XmlInclude(typeof(GameCreatePlaylistEvent))]
|
||||||
|
[XmlInclude(typeof(GameAddLevelToPlaylistEvent))]
|
||||||
public class GameEvent : ILbpSerializable, INeedsPreparationForSerialization
|
public class GameEvent : ILbpSerializable, INeedsPreparationForSerialization
|
||||||
{
|
{
|
||||||
[XmlIgnore]
|
[XmlIgnore]
|
||||||
private int UserId { get; set; }
|
protected int UserId { get; set; }
|
||||||
|
|
||||||
[XmlAttribute("type")]
|
[XmlAttribute("type")]
|
||||||
public EventType Type { get; set; }
|
public EventType Type { get; set; }
|
||||||
|
@ -31,100 +41,190 @@ public class GameEvent : ILbpSerializable, INeedsPreparationForSerialization
|
||||||
public long Timestamp { get; set; }
|
public long Timestamp { get; set; }
|
||||||
|
|
||||||
[XmlElement("actor")]
|
[XmlElement("actor")]
|
||||||
|
[DefaultValue(null)]
|
||||||
public string Username { get; set; }
|
public string Username { get; set; }
|
||||||
|
|
||||||
protected async Task PrepareSerialization(DatabaseContext database)
|
protected async Task PrepareSerialization(DatabaseContext database)
|
||||||
{
|
{
|
||||||
Console.WriteLine($@"SERIALIZATION!! {this.UserId} - {this.GetHashCode()}");
|
Console.WriteLine($@"EVENT SERIALIZATION!! {this.UserId} - {this.GetHashCode()}");
|
||||||
UserEntity user = await database.Users.FindAsync(this.UserId);
|
UserEntity user = await database.Users.FindAsync(this.UserId);
|
||||||
if (user == null) return;
|
if (user == null) return;
|
||||||
this.Username = user.Username;
|
this.Username = user.Username;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IEnumerable<GameEvent> CreateFromActivityGroups(IGrouping<EventType, ActivityEntity> group)
|
public static IEnumerable<GameEvent> CreateFromActivities(IEnumerable<ActivityDto> activities)
|
||||||
{
|
{
|
||||||
List<GameEvent> events = new();
|
List<GameEvent> events = new();
|
||||||
|
List<IGrouping<EventType, ActivityDto>> typeGroups = activities.GroupBy(g => g.Activity.Type).ToList();
|
||||||
// ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault
|
foreach (IGrouping<EventType, ActivityDto> typeGroup in typeGroups)
|
||||||
// Events with Count need special treatment
|
|
||||||
switch (group.Key)
|
|
||||||
{
|
{
|
||||||
case EventType.PlayLevel:
|
// ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault
|
||||||
|
// Events with Count need special treatment
|
||||||
|
switch (typeGroup.Key)
|
||||||
{
|
{
|
||||||
if (group.First() is not LevelActivityEntity levelActivity) break;
|
case EventType.PlayLevel:
|
||||||
|
|
||||||
events.Add(new GamePlayLevelEvent
|
|
||||||
{
|
{
|
||||||
Slot = new ReviewSlot
|
if (typeGroup.First().Activity is not LevelActivityEntity levelActivity) break;
|
||||||
{
|
|
||||||
SlotId = levelActivity.SlotId,
|
|
||||||
},
|
|
||||||
Count = group.Count(),
|
|
||||||
UserId = levelActivity.UserId,
|
|
||||||
Timestamp = levelActivity.Timestamp.ToUnixTimeMilliseconds(),
|
|
||||||
Type = levelActivity.Type,
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case EventType.PublishLevel:
|
|
||||||
{
|
|
||||||
if (group.First() is not LevelActivityEntity levelActivity) break;
|
|
||||||
|
|
||||||
events.Add(new GamePublishLevelEvent
|
events.Add(new GamePlayLevelEvent
|
||||||
{
|
|
||||||
Slot = new ReviewSlot
|
|
||||||
{
|
{
|
||||||
SlotId = levelActivity.SlotId,
|
Slot = new ReviewSlot
|
||||||
},
|
{
|
||||||
Count = group.Count(),
|
SlotId = levelActivity.SlotId,
|
||||||
UserId = levelActivity.UserId,
|
},
|
||||||
Timestamp = levelActivity.Timestamp.ToUnixTimeMilliseconds(),
|
Count = typeGroup.Count(),
|
||||||
Type = levelActivity.Type,
|
UserId = levelActivity.UserId,
|
||||||
});
|
Timestamp = levelActivity.Timestamp.ToUnixTimeMilliseconds(),
|
||||||
break;
|
Type = levelActivity.Type,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Everything else can be handled as normal
|
||||||
|
default:
|
||||||
|
events.AddRange(typeGroup.Select(CreateFromActivity).Where(a => a != null));
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
// Everything else can be handled as normal
|
|
||||||
default: events.AddRange(group.Select(CreateFromActivity));
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return events.AsEnumerable();
|
return events.AsEnumerable();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static GameEvent CreateFromActivity(ActivityEntity activity)
|
private static bool IsValidActivity(ActivityEntity activity)
|
||||||
{
|
{
|
||||||
GameEvent gameEvent = activity.Type switch
|
return activity switch
|
||||||
|
{
|
||||||
|
CommentActivityEntity => activity.Type is EventType.CommentOnLevel or EventType.CommentOnUser
|
||||||
|
or EventType.DeleteLevelComment,
|
||||||
|
LevelActivityEntity => activity.Type is EventType.PlayLevel or EventType.HeartLevel
|
||||||
|
or EventType.UnheartLevel or EventType.DpadRateLevel or EventType.RateLevel or EventType.MMPickLevel
|
||||||
|
or EventType.PublishLevel or EventType.TagLevel,
|
||||||
|
NewsActivityEntity => activity.Type is EventType.NewsPost,
|
||||||
|
PhotoActivityEntity => activity.Type is EventType.UploadPhoto,
|
||||||
|
PlaylistActivityEntity => activity.Type is EventType.CreatePlaylist or EventType.HeartPlaylist,
|
||||||
|
PlaylistWithSlotActivityEntity => activity.Type is EventType.AddLevelToPlaylist,
|
||||||
|
ReviewActivityEntity => activity.Type is EventType.ReviewLevel,
|
||||||
|
ScoreActivityEntity => activity.Type is EventType.Score,
|
||||||
|
UserActivityEntity => activity.Type is EventType.HeartUser or EventType.UnheartUser
|
||||||
|
or EventType.CommentOnUser,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GameEvent CreateFromActivity(ActivityDto activity)
|
||||||
|
{
|
||||||
|
if (!IsValidActivity(activity.Activity))
|
||||||
|
{
|
||||||
|
Console.WriteLine(@"Invalid Activity: " + activity.Activity.ActivityId);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
int targetId = activity.TargetId;
|
||||||
|
|
||||||
|
GameEvent gameEvent = activity.Activity.Type switch
|
||||||
{
|
{
|
||||||
EventType.PlayLevel => new GamePlayLevelEvent
|
EventType.PlayLevel => new GamePlayLevelEvent
|
||||||
{
|
{
|
||||||
Slot = new ReviewSlot
|
Slot = new ReviewSlot
|
||||||
{
|
{
|
||||||
SlotId = ((LevelActivityEntity)activity).SlotId,
|
SlotId = targetId,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
EventType.CommentOnLevel => new GameSlotCommentEvent
|
|
||||||
{
|
|
||||||
CommentId = ((CommentActivityEntity)activity).CommentId,
|
|
||||||
},
|
|
||||||
EventType.CommentOnUser => new GameUserCommentEvent
|
|
||||||
{
|
|
||||||
CommentId = ((CommentActivityEntity)activity).CommentId,
|
|
||||||
},
|
|
||||||
EventType.HeartUser or EventType.UnheartUser => new GameHeartUserEvent
|
|
||||||
{
|
|
||||||
TargetUserId = ((UserActivityEntity)activity).TargetUserId,
|
|
||||||
},
|
|
||||||
EventType.HeartLevel or EventType.UnheartLevel => new GameHeartLevelEvent
|
EventType.HeartLevel or EventType.UnheartLevel => new GameHeartLevelEvent
|
||||||
{
|
{
|
||||||
TargetSlot = new ReviewSlot
|
TargetSlot = new ReviewSlot
|
||||||
{
|
{
|
||||||
SlotId = ((LevelActivityEntity)activity).SlotId,
|
SlotId = targetId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
EventType.DpadRateLevel => new GameDpadRateLevelEvent
|
||||||
|
{
|
||||||
|
Slot = new ReviewSlot
|
||||||
|
{
|
||||||
|
SlotId = targetId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
EventType.Score => new GameScoreEvent
|
||||||
|
{
|
||||||
|
ScoreId = ((ScoreActivityEntity)activity.Activity).ScoreId,
|
||||||
|
Slot = new ReviewSlot
|
||||||
|
{
|
||||||
|
SlotId = targetId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
EventType.RateLevel => new GameRateLevelEvent
|
||||||
|
{
|
||||||
|
Slot = new ReviewSlot
|
||||||
|
{
|
||||||
|
SlotId = targetId
|
||||||
|
},
|
||||||
|
},
|
||||||
|
EventType.CommentOnLevel => new GameSlotCommentEvent
|
||||||
|
{
|
||||||
|
CommentId = ((CommentActivityEntity)activity.Activity).CommentId,
|
||||||
|
},
|
||||||
|
EventType.CommentOnUser => new GameUserCommentEvent
|
||||||
|
{
|
||||||
|
CommentId = ((CommentActivityEntity)activity.Activity).CommentId,
|
||||||
|
},
|
||||||
|
EventType.HeartUser or EventType.UnheartUser => new GameHeartUserEvent
|
||||||
|
{
|
||||||
|
TargetUserId = targetId,
|
||||||
|
},
|
||||||
|
EventType.ReviewLevel => new GameReviewEvent
|
||||||
|
{
|
||||||
|
ReviewId = ((ReviewActivityEntity)activity.Activity).ReviewId,
|
||||||
|
Slot = new ReviewSlot
|
||||||
|
{
|
||||||
|
SlotId = targetId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
EventType.UploadPhoto => new GamePhotoUploadEvent
|
||||||
|
{
|
||||||
|
Slot = new ReviewSlot
|
||||||
|
{
|
||||||
|
SlotId = targetId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
EventType.MMPickLevel => new GameTeamPickLevelEvent
|
||||||
|
{
|
||||||
|
Slot = new ReviewSlot
|
||||||
|
{
|
||||||
|
SlotId = targetId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
EventType.PublishLevel => new GamePublishLevelEvent
|
||||||
|
{
|
||||||
|
Slot = new ReviewSlot
|
||||||
|
{
|
||||||
|
SlotId = targetId,
|
||||||
|
},
|
||||||
|
Count = 1,
|
||||||
|
},
|
||||||
|
EventType.NewsPost => new GameNewsEvent
|
||||||
|
{
|
||||||
|
NewsId = targetId,
|
||||||
|
},
|
||||||
|
EventType.CreatePlaylist => new GameCreatePlaylistEvent
|
||||||
|
{
|
||||||
|
TargetPlaylistId = targetId,
|
||||||
|
},
|
||||||
|
EventType.HeartPlaylist => new GameHeartPlaylistEvent
|
||||||
|
{
|
||||||
|
TargetPlaylistId = targetId,
|
||||||
|
},
|
||||||
|
EventType.AddLevelToPlaylist => new GameAddLevelToPlaylistEvent
|
||||||
|
{
|
||||||
|
TargetPlaylistId = targetId,
|
||||||
|
Slot = new ReviewSlot
|
||||||
|
{
|
||||||
|
SlotId = ((PlaylistWithSlotActivityEntity)activity.Activity).SlotId,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
_ => new GameEvent(),
|
_ => new GameEvent(),
|
||||||
};
|
};
|
||||||
gameEvent.UserId = activity.UserId;
|
gameEvent.UserId = activity.Activity.UserId;
|
||||||
gameEvent.Type = activity.Type;
|
gameEvent.Type = activity.Activity.Type;
|
||||||
gameEvent.Timestamp = activity.Timestamp.ToUnixTimeMilliseconds();
|
gameEvent.Timestamp = activity.Activity.Timestamp.ToUnixTimeMilliseconds();
|
||||||
return gameEvent;
|
return gameEvent;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -40,4 +40,15 @@ public class GameHeartLevelEvent : GameEvent
|
||||||
|
|
||||||
this.TargetSlot = ReviewSlot.CreateFromEntity(slot);
|
this.TargetSlot = ReviewSlot.CreateFromEntity(slot);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GameHeartPlaylistEvent : GameEvent
|
||||||
|
{
|
||||||
|
[XmlElement("object_playlist_id")]
|
||||||
|
public int TargetPlaylistId { get; set; }
|
||||||
|
|
||||||
|
public new async Task PrepareSerialization(DatabaseContext database)
|
||||||
|
{
|
||||||
|
await base.PrepareSerialization(database);
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
using System.Xml.Serialization;
|
||||||
|
|
||||||
|
namespace LBPUnion.ProjectLighthouse.Types.Serialization.Activity.Events;
|
||||||
|
|
||||||
|
public class GameNewsEvent : GameEvent
|
||||||
|
{
|
||||||
|
[XmlElement("news_id")]
|
||||||
|
public int NewsId { get; set; }
|
||||||
|
}
|
|
@ -18,7 +18,7 @@ public class GamePhotoUploadEvent : GameEvent
|
||||||
|
|
||||||
[XmlElement("object_slot_id")]
|
[XmlElement("object_slot_id")]
|
||||||
[DefaultValue(null)]
|
[DefaultValue(null)]
|
||||||
public ReviewSlot SlotId { get; set; }
|
public ReviewSlot Slot { get; set; }
|
||||||
|
|
||||||
[XmlElement("user_in_photo")]
|
[XmlElement("user_in_photo")]
|
||||||
public List<string> PhotoParticipants { get; set; }
|
public List<string> PhotoParticipants { get; set; }
|
||||||
|
@ -40,6 +40,6 @@ public class GamePhotoUploadEvent : GameEvent
|
||||||
SlotEntity slot = await database.Slots.FindAsync(photo.SlotId);
|
SlotEntity slot = await database.Slots.FindAsync(photo.SlotId);
|
||||||
if (slot == null) return;
|
if (slot == null) return;
|
||||||
|
|
||||||
this.SlotId = ReviewSlot.CreateFromEntity(slot);
|
this.Slot = ReviewSlot.CreateFromEntity(slot);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
using System.Threading.Tasks;
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using System.Xml.Serialization;
|
using System.Xml.Serialization;
|
||||||
using LBPUnion.ProjectLighthouse.Database;
|
using LBPUnion.ProjectLighthouse.Database;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||||
|
@ -12,7 +13,7 @@ public class GamePublishLevelEvent : GameEvent
|
||||||
public ReviewSlot Slot { get; set; }
|
public ReviewSlot Slot { get; set; }
|
||||||
|
|
||||||
[XmlElement("republish")]
|
[XmlElement("republish")]
|
||||||
public bool IsRepublish { get; set; }
|
public int IsRepublish { get; set; }
|
||||||
|
|
||||||
[XmlElement("count")]
|
[XmlElement("count")]
|
||||||
public int Count { get; set; }
|
public int Count { get; set; }
|
||||||
|
@ -26,6 +27,7 @@ public class GamePublishLevelEvent : GameEvent
|
||||||
|
|
||||||
this.Slot = ReviewSlot.CreateFromEntity(slot);
|
this.Slot = ReviewSlot.CreateFromEntity(slot);
|
||||||
// TODO does this work?
|
// TODO does this work?
|
||||||
this.IsRepublish = slot.LastUpdated == slot.FirstUploaded;
|
bool republish = Math.Abs(this.Timestamp - slot.FirstUploaded) > 5000;
|
||||||
|
this.IsRepublish = Convert.ToInt32(republish);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Xml.Serialization;
|
||||||
|
using LBPUnion.ProjectLighthouse.Database;
|
||||||
|
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||||
|
using LBPUnion.ProjectLighthouse.Types.Serialization.Review;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace LBPUnion.ProjectLighthouse.Types.Serialization.Activity.Events;
|
||||||
|
|
||||||
|
public class GameRateLevelEvent : GameEvent
|
||||||
|
{
|
||||||
|
[XmlElement("object_slot_id")]
|
||||||
|
public ReviewSlot Slot { get; set; }
|
||||||
|
|
||||||
|
[XmlElement("rating")]
|
||||||
|
public double Rating { get; set; }
|
||||||
|
|
||||||
|
public new async Task PrepareSerialization(DatabaseContext database)
|
||||||
|
{
|
||||||
|
await base.PrepareSerialization(database);
|
||||||
|
|
||||||
|
SlotEntity slot = await database.Slots.FindAsync(this.Slot.SlotId);
|
||||||
|
if (slot == null) return;
|
||||||
|
|
||||||
|
this.Slot = ReviewSlot.CreateFromEntity(slot);
|
||||||
|
|
||||||
|
this.Rating = await database.RatedLevels.Where(r => r.SlotId == slot.SlotId && r.UserId == this.UserId)
|
||||||
|
.Select(r => r.RatingLBP1)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
using System.ComponentModel;
|
using System;
|
||||||
|
using System.ComponentModel;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Xml.Serialization;
|
using System.Xml.Serialization;
|
||||||
using LBPUnion.ProjectLighthouse.Database;
|
using LBPUnion.ProjectLighthouse.Database;
|
||||||
|
@ -9,7 +10,7 @@ namespace LBPUnion.ProjectLighthouse.Types.Serialization.Activity.Events;
|
||||||
|
|
||||||
public class GameReviewEvent : GameEvent
|
public class GameReviewEvent : GameEvent
|
||||||
{
|
{
|
||||||
[XmlElement("slot_id")]
|
[XmlElement("object_slot_id")]
|
||||||
public ReviewSlot Slot { get; set; }
|
public ReviewSlot Slot { get; set; }
|
||||||
|
|
||||||
[XmlElement("review_id")]
|
[XmlElement("review_id")]
|
||||||
|
@ -21,9 +22,13 @@ public class GameReviewEvent : GameEvent
|
||||||
|
|
||||||
public new async Task PrepareSerialization(DatabaseContext database)
|
public new async Task PrepareSerialization(DatabaseContext database)
|
||||||
{
|
{
|
||||||
|
await base.PrepareSerialization(database);
|
||||||
|
|
||||||
ReviewEntity review = await database.Reviews.FindAsync(this.ReviewId);
|
ReviewEntity review = await database.Reviews.FindAsync(this.ReviewId);
|
||||||
if (review == null) return;
|
if (review == null) return;
|
||||||
|
|
||||||
|
this.ReviewTimestamp = this.Timestamp;
|
||||||
|
|
||||||
SlotEntity slot = await database.Slots.FindAsync(review.SlotId);
|
SlotEntity slot = await database.Slots.FindAsync(review.SlotId);
|
||||||
if (slot == null) return;
|
if (slot == null) return;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Xml.Serialization;
|
||||||
|
using LBPUnion.ProjectLighthouse.Database;
|
||||||
|
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||||
|
using LBPUnion.ProjectLighthouse.Types.Serialization.Review;
|
||||||
|
|
||||||
|
namespace LBPUnion.ProjectLighthouse.Types.Serialization.Activity.Events;
|
||||||
|
|
||||||
|
public class GameTeamPickLevelEvent : GameEvent
|
||||||
|
{
|
||||||
|
[XmlElement("object_slot_id")]
|
||||||
|
public ReviewSlot Slot { get; set; }
|
||||||
|
|
||||||
|
public new async Task PrepareSerialization(DatabaseContext database)
|
||||||
|
{
|
||||||
|
SlotEntity slot = await database.Slots.FindAsync(this.Slot.SlotId);
|
||||||
|
if (slot == null) return;
|
||||||
|
|
||||||
|
this.Slot = ReviewSlot.CreateFromEntity(slot);
|
||||||
|
|
||||||
|
// Don't serialize usernames for team picks
|
||||||
|
this.Username = null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
using System.Xml.Serialization;
|
||||||
|
|
||||||
|
namespace LBPUnion.ProjectLighthouse.Types.Serialization.Activity;
|
||||||
|
|
||||||
|
public class GameNewsStreamGroup : GameStreamGroup
|
||||||
|
{
|
||||||
|
[XmlElement("news_id")]
|
||||||
|
public int NewsId { get; set; }
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
using System.Xml.Serialization;
|
||||||
|
|
||||||
|
namespace LBPUnion.ProjectLighthouse.Types.Serialization.Activity;
|
||||||
|
|
||||||
|
public class GamePlaylistStreamGroup : GameStreamGroup
|
||||||
|
{
|
||||||
|
[XmlElement("playlist_id")]
|
||||||
|
public int PlaylistId { get; set; }
|
||||||
|
}
|
|
@ -1,18 +1,21 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Xml.Serialization;
|
using System.Xml.Serialization;
|
||||||
using LBPUnion.ProjectLighthouse.Database;
|
using LBPUnion.ProjectLighthouse.Database;
|
||||||
|
using LBPUnion.ProjectLighthouse.Extensions;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Activity;
|
using LBPUnion.ProjectLighthouse.Types.Activity;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Entities.Activity;
|
|
||||||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
|
using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
|
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
|
||||||
|
using LBPUnion.ProjectLighthouse.Types.Entities.Website;
|
||||||
|
using LBPUnion.ProjectLighthouse.Types.Serialization.News;
|
||||||
|
using LBPUnion.ProjectLighthouse.Types.Serialization.Playlist;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Serialization.Slot;
|
using LBPUnion.ProjectLighthouse.Types.Serialization.Slot;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Serialization.User;
|
using LBPUnion.ProjectLighthouse.Types.Serialization.User;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Users;
|
using LBPUnion.ProjectLighthouse.Types.Users;
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
|
|
||||||
namespace LBPUnion.ProjectLighthouse.Types.Serialization.Activity;
|
namespace LBPUnion.ProjectLighthouse.Types.Serialization.Activity;
|
||||||
|
|
||||||
|
@ -23,10 +26,16 @@ namespace LBPUnion.ProjectLighthouse.Types.Serialization.Activity;
|
||||||
public class GameStream : ILbpSerializable, INeedsPreparationForSerialization
|
public class GameStream : ILbpSerializable, INeedsPreparationForSerialization
|
||||||
{
|
{
|
||||||
[XmlIgnore]
|
[XmlIgnore]
|
||||||
private List<int> SlotIds { get; set; }
|
public List<int> SlotIds { get; set; }
|
||||||
|
|
||||||
[XmlIgnore]
|
[XmlIgnore]
|
||||||
private List<int> UserIds { get; set; }
|
public List<int> UserIds { get; set; }
|
||||||
|
|
||||||
|
[XmlIgnore]
|
||||||
|
public List<int> PlaylistIds { get; set; }
|
||||||
|
|
||||||
|
[XmlIgnore]
|
||||||
|
public List<int> NewsIds { get; set; }
|
||||||
|
|
||||||
[XmlIgnore]
|
[XmlIgnore]
|
||||||
private int TargetUserId { get; set; }
|
private int TargetUserId { get; set; }
|
||||||
|
@ -42,84 +51,85 @@ public class GameStream : ILbpSerializable, INeedsPreparationForSerialization
|
||||||
|
|
||||||
[XmlArray("groups")]
|
[XmlArray("groups")]
|
||||||
[XmlArrayItem("group")]
|
[XmlArrayItem("group")]
|
||||||
|
[DefaultValue(null)]
|
||||||
public List<GameStreamGroup> Groups { get; set; }
|
public List<GameStreamGroup> Groups { get; set; }
|
||||||
|
|
||||||
[XmlArray("slots")]
|
[XmlArray("slots")]
|
||||||
[XmlArrayItem("slot")]
|
[XmlArrayItem("slot")]
|
||||||
|
[DefaultValue(null)]
|
||||||
public List<SlotBase> Slots { get; set; }
|
public List<SlotBase> Slots { get; set; }
|
||||||
|
|
||||||
[XmlArray("users")]
|
[XmlArray("users")]
|
||||||
[XmlArrayItem("user")]
|
[XmlArrayItem("user")]
|
||||||
|
[DefaultValue(null)]
|
||||||
public List<GameUser> Users { get; set; }
|
public List<GameUser> Users { get; set; }
|
||||||
|
|
||||||
|
[XmlArray("playlists")]
|
||||||
|
[XmlArrayItem("playlist")]
|
||||||
|
[DefaultValue(null)]
|
||||||
|
public List<GamePlaylist> Playlists { get; set; }
|
||||||
|
|
||||||
[XmlArray("news")]
|
[XmlArray("news")]
|
||||||
[XmlArrayItem("item")]
|
[XmlArrayItem("item")]
|
||||||
public List<object> News { get; set; }
|
[DefaultValue(null)]
|
||||||
//TODO implement lbp1 and lbp2 news objects
|
public List<GameNewsObject> News { get; set; }
|
||||||
|
|
||||||
public async Task PrepareSerialization(DatabaseContext database)
|
public async Task PrepareSerialization(DatabaseContext database)
|
||||||
{
|
{
|
||||||
if (this.SlotIds.Count > 0)
|
async Task<List<TResult>> LoadEntities<TFrom, TResult>(List<int> ids, Func<TFrom, TResult> transformation)
|
||||||
|
where TFrom : class
|
||||||
{
|
{
|
||||||
this.Slots = new List<SlotBase>();
|
List<TResult> results = new();
|
||||||
foreach (int slotId in this.SlotIds)
|
if (ids.Count <= 0) return null;
|
||||||
|
foreach (int id in ids)
|
||||||
{
|
{
|
||||||
SlotEntity slot = await database.Slots.FindAsync(slotId);
|
TFrom entity = await database.Set<TFrom>().FindAsync(id);
|
||||||
if (slot == null) continue;
|
if (entity == null) continue;
|
||||||
|
|
||||||
this.Slots.Add(SlotBase.CreateFromEntity(slot, this.TargetGame, this.TargetUserId));
|
results.Add(transformation(entity));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.UserIds.Count > 0)
|
this.Slots = await LoadEntities<SlotEntity, SlotBase>(this.SlotIds, slot => SlotBase.CreateFromEntity(slot, this.TargetGame, this.TargetUserId));
|
||||||
{
|
this.Users = await LoadEntities<UserEntity, GameUser>(this.UserIds, user => GameUser.CreateFromEntity(user, this.TargetGame));
|
||||||
this.Users = new List<GameUser>();
|
this.Playlists = await LoadEntities<PlaylistEntity, GamePlaylist>(this.PlaylistIds, GamePlaylist.CreateFromEntity);
|
||||||
foreach (int userId in this.UserIds)
|
this.News = await LoadEntities<WebsiteAnnouncementEntity, GameNewsObject>(this.NewsIds, GameNewsObject.CreateFromEntity);
|
||||||
{
|
|
||||||
UserEntity user = await database.Users.FindAsync(userId);
|
|
||||||
if (user == null) continue;
|
|
||||||
|
|
||||||
this.Users.Add(GameUser.CreateFromEntity(user, this.TargetGame));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<GameStream> CreateFromEntityResult
|
public static GameStream CreateFromGroups
|
||||||
(
|
(GameTokenEntity token, List<OuterActivityGroup> groups, long startTimestamp, long endTimestamp)
|
||||||
DatabaseContext database,
|
|
||||||
GameTokenEntity token,
|
|
||||||
List<IGrouping<ActivityGroup, ActivityEntity>> results,
|
|
||||||
long startTimestamp,
|
|
||||||
long endTimestamp
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
List<int> slotIds = results.Where(g => g.Key.TargetSlotId != null && g.Key.TargetSlotId.Value != 0)
|
|
||||||
.Select(g => g.Key.TargetSlotId.Value)
|
|
||||||
.ToList();
|
|
||||||
Console.WriteLine($@"slotIds: {string.Join(",", slotIds)}");
|
|
||||||
List<int> userIds = results.Where(g => g.Key.TargetUserId != null && g.Key.TargetUserId.Value != 0)
|
|
||||||
.Select(g => g.Key.TargetUserId.Value)
|
|
||||||
.Distinct()
|
|
||||||
.Union(results.Select(g => g.Key.UserId))
|
|
||||||
.ToList();
|
|
||||||
// Cache target levels and users within DbContext
|
|
||||||
await database.Slots.Where(s => slotIds.Contains(s.SlotId)).LoadAsync();
|
|
||||||
await database.Users.Where(u => userIds.Contains(u.UserId)).LoadAsync();
|
|
||||||
Console.WriteLine($@"userIds: {string.Join(",", userIds)}");
|
|
||||||
Console.WriteLine($@"Stream contains {slotIds.Count} slots and {userIds.Count} users");
|
|
||||||
GameStream gameStream = new()
|
GameStream gameStream = new()
|
||||||
{
|
{
|
||||||
TargetUserId = token.UserId,
|
TargetUserId = token.UserId,
|
||||||
TargetGame = token.GameVersion,
|
TargetGame = token.GameVersion,
|
||||||
StartTimestamp = startTimestamp,
|
StartTimestamp = startTimestamp,
|
||||||
EndTimestamp = endTimestamp,
|
EndTimestamp = endTimestamp,
|
||||||
SlotIds = slotIds,
|
SlotIds = groups.GetIds(ActivityGroupType.Level),
|
||||||
UserIds = userIds,
|
UserIds = groups.GetIds(ActivityGroupType.User),
|
||||||
Groups = new List<GameStreamGroup>(),
|
PlaylistIds = groups.GetIds(ActivityGroupType.Playlist),
|
||||||
|
NewsIds = groups.GetIds(ActivityGroupType.News),
|
||||||
};
|
};
|
||||||
foreach (IGrouping<ActivityGroup, ActivityEntity> group in results)
|
if (groups.Count == 0) return gameStream;
|
||||||
|
|
||||||
|
gameStream.Groups = groups.Select(GameStreamGroup.CreateFromGroup).ToList();
|
||||||
|
|
||||||
|
// Workaround for level activity because it shouldn't contain nested activity groups
|
||||||
|
if (gameStream.Groups.Count == 1 && groups.First().Key.GroupType == ActivityGroupType.Level)
|
||||||
{
|
{
|
||||||
gameStream.Groups.Add(GameStreamGroup.CreateFromGrouping(group));
|
gameStream.Groups = gameStream.Groups.First().Groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Workaround to turn a single subgroup into the primary group for news and team picks
|
||||||
|
for (int i = 0; i < gameStream.Groups.Count; i++)
|
||||||
|
{
|
||||||
|
GameStreamGroup group = gameStream.Groups[i];
|
||||||
|
if (group.Type is not (ActivityGroupType.TeamPick or ActivityGroupType.News)) continue;
|
||||||
|
if (group.Groups.Count > 1) continue;
|
||||||
|
|
||||||
|
gameStream.Groups[i] = group.Groups.First();
|
||||||
}
|
}
|
||||||
|
|
||||||
return gameStream;
|
return gameStream;
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
#nullable enable
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Xml.Serialization;
|
||||||
|
using LBPUnion.ProjectLighthouse.Types.Activity;
|
||||||
|
|
||||||
|
namespace LBPUnion.ProjectLighthouse.Types.Serialization.Activity;
|
||||||
|
|
||||||
|
[XmlRoot("stream")]
|
||||||
|
// This class should only be deserialized
|
||||||
|
public class GameStreamFilter
|
||||||
|
{
|
||||||
|
[XmlArray("sources")]
|
||||||
|
[XmlArrayItem("source")]
|
||||||
|
public List<GameStreamFilterEventSource>? Sources { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[XmlRoot("source")]
|
||||||
|
public class GameStreamFilterEventSource
|
||||||
|
{
|
||||||
|
[XmlAttribute("type")]
|
||||||
|
public string? SourceType { get; set; }
|
||||||
|
|
||||||
|
[XmlArray("event_filters")]
|
||||||
|
[XmlArrayItem("event_filter")]
|
||||||
|
public List<EventType>? Types { get; set; }
|
||||||
|
}
|
|
@ -3,8 +3,8 @@ using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Xml.Serialization;
|
using System.Xml.Serialization;
|
||||||
|
using LBPUnion.ProjectLighthouse.Extensions;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Activity;
|
using LBPUnion.ProjectLighthouse.Types.Activity;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Entities.Activity;
|
|
||||||
using LBPUnion.ProjectLighthouse.Types.Serialization.Activity.Events;
|
using LBPUnion.ProjectLighthouse.Types.Serialization.Activity.Events;
|
||||||
using LBPUnion.ProjectLighthouse.Types.Serialization.Review;
|
using LBPUnion.ProjectLighthouse.Types.Serialization.Review;
|
||||||
|
|
||||||
|
@ -19,6 +19,8 @@ namespace LBPUnion.ProjectLighthouse.Types.Serialization.Activity;
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[XmlInclude(typeof(GameUserStreamGroup))]
|
[XmlInclude(typeof(GameUserStreamGroup))]
|
||||||
[XmlInclude(typeof(GameSlotStreamGroup))]
|
[XmlInclude(typeof(GameSlotStreamGroup))]
|
||||||
|
[XmlInclude(typeof(GamePlaylistStreamGroup))]
|
||||||
|
[XmlInclude(typeof(GameNewsStreamGroup))]
|
||||||
public class GameStreamGroup : ILbpSerializable
|
public class GameStreamGroup : ILbpSerializable
|
||||||
{
|
{
|
||||||
[XmlAttribute("type")]
|
[XmlAttribute("type")]
|
||||||
|
@ -35,46 +37,62 @@ public class GameStreamGroup : ILbpSerializable
|
||||||
[XmlArray("events")]
|
[XmlArray("events")]
|
||||||
[XmlArrayItem("event")]
|
[XmlArrayItem("event")]
|
||||||
[DefaultValue(null)]
|
[DefaultValue(null)]
|
||||||
|
// ReSharper disable once MemberCanBePrivate.Global
|
||||||
|
// (the serializer can't see this if it's private)
|
||||||
public List<GameEvent> Events { get; set; }
|
public List<GameEvent> Events { get; set; }
|
||||||
|
|
||||||
public static GameStreamGroup CreateFromGrouping(IGrouping<ActivityGroup, ActivityEntity> group)
|
public static GameStreamGroup CreateFromGroup(OuterActivityGroup group)
|
||||||
|
{
|
||||||
|
GameStreamGroup gameGroup = CreateGroup(group.Key.GroupType,
|
||||||
|
group.Key.TargetId,
|
||||||
|
streamGroup =>
|
||||||
|
{
|
||||||
|
streamGroup.Timestamp = group.Groups
|
||||||
|
.Max(g => g.MaxBy(a => a.Activity.Timestamp)?.Activity.Timestamp ?? group.Key.Timestamp)
|
||||||
|
.ToUnixTimeMilliseconds();
|
||||||
|
});
|
||||||
|
|
||||||
|
gameGroup.Groups = new List<GameStreamGroup>(group.Groups.Select(g => CreateGroup(g.Key.Type,
|
||||||
|
g.Key.TargetId,
|
||||||
|
streamGroup =>
|
||||||
|
{
|
||||||
|
streamGroup.Timestamp =
|
||||||
|
g.MaxBy(a => a.Activity.Timestamp).Activity.Timestamp.ToUnixTimeMilliseconds();
|
||||||
|
streamGroup.Events = GameEvent.CreateFromActivities(g).ToList();
|
||||||
|
}))
|
||||||
|
.ToList());
|
||||||
|
|
||||||
|
return gameGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GameStreamGroup CreateGroup
|
||||||
|
(ActivityGroupType type, int targetId, Action<GameStreamGroup> groupAction)
|
||||||
{
|
{
|
||||||
ActivityGroupType type = group.Key.GroupType;
|
|
||||||
GameStreamGroup gameGroup = type switch
|
GameStreamGroup gameGroup = type switch
|
||||||
{
|
{
|
||||||
ActivityGroupType.Level => new GameSlotStreamGroup
|
ActivityGroupType.Level or ActivityGroupType.TeamPick => new GameSlotStreamGroup
|
||||||
{
|
{
|
||||||
Slot = new ReviewSlot
|
Slot = new ReviewSlot
|
||||||
{
|
{
|
||||||
SlotId = group.Key.TargetId,
|
SlotId = targetId,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ActivityGroupType.User => new GameUserStreamGroup
|
ActivityGroupType.User => new GameUserStreamGroup
|
||||||
{
|
{
|
||||||
UserId = group.Key.TargetId,
|
UserId = targetId,
|
||||||
|
},
|
||||||
|
ActivityGroupType.Playlist => new GamePlaylistStreamGroup
|
||||||
|
{
|
||||||
|
PlaylistId = targetId,
|
||||||
|
},
|
||||||
|
ActivityGroupType.News => new GameNewsStreamGroup
|
||||||
|
{
|
||||||
|
NewsId = targetId,
|
||||||
},
|
},
|
||||||
_ => new GameStreamGroup(),
|
_ => new GameStreamGroup(),
|
||||||
};
|
};
|
||||||
gameGroup.Timestamp = new DateTimeOffset(group.Select(a => a.Timestamp).MaxBy(a => a)).ToUnixTimeMilliseconds();
|
|
||||||
gameGroup.Type = type;
|
gameGroup.Type = type;
|
||||||
|
groupAction(gameGroup);
|
||||||
List<IGrouping<EventType, ActivityEntity>> eventGroups = group.OrderByDescending(a => a.Timestamp).GroupBy(g => g.Type).ToList();
|
|
||||||
//TODO removeme debug
|
|
||||||
foreach (IGrouping<EventType, ActivityEntity> bruh in eventGroups)
|
|
||||||
{
|
|
||||||
Console.WriteLine($@"group key: {bruh.Key}, count={bruh.Count()}");
|
|
||||||
}
|
|
||||||
gameGroup.Groups = new List<GameStreamGroup>
|
|
||||||
{
|
|
||||||
new GameUserStreamGroup
|
|
||||||
{
|
|
||||||
UserId = group.Key.UserId,
|
|
||||||
Type = ActivityGroupType.User,
|
|
||||||
Timestamp = gameGroup.Timestamp,
|
|
||||||
Events = eventGroups.SelectMany(GameEvent.CreateFromActivityGroups).ToList(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return gameGroup;
|
return gameGroup;
|
||||||
}
|
}
|
||||||
}
|
}
|
63
ProjectLighthouse/Types/Serialization/News/GameNews.cs
Normal file
63
ProjectLighthouse/Types/Serialization/News/GameNews.cs
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Xml.Serialization;
|
||||||
|
using LBPUnion.ProjectLighthouse.Types.Entities.Website;
|
||||||
|
|
||||||
|
namespace LBPUnion.ProjectLighthouse.Types.Serialization.News;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used in LBP1 only
|
||||||
|
/// </summary>
|
||||||
|
[XmlRoot("news")]
|
||||||
|
public class GameNews : ILbpSerializable
|
||||||
|
{
|
||||||
|
[XmlElement("subcategory")]
|
||||||
|
public List<GameNewsSubcategory> Entries { get; set; }
|
||||||
|
|
||||||
|
public static GameNews CreateFromEntity(List<WebsiteAnnouncementEntity> entities) =>
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Entries = entities.Select(entity => new GameNewsSubcategory
|
||||||
|
{
|
||||||
|
Item = new GameNewsItem
|
||||||
|
{
|
||||||
|
Content = new GameNewsContent
|
||||||
|
{
|
||||||
|
Frame = new GameNewsFrame
|
||||||
|
{
|
||||||
|
Title = entity.Title,
|
||||||
|
Width = 512,
|
||||||
|
Container = new List<GameNewsFrameContainer>
|
||||||
|
{
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Content = entity.Content,
|
||||||
|
Width = 512,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.ToList(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[XmlRoot("subcategory")]
|
||||||
|
public class GameNewsSubcategory : ILbpSerializable
|
||||||
|
{
|
||||||
|
[XmlElement("item")]
|
||||||
|
public GameNewsItem Item { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GameNewsItem : ILbpSerializable
|
||||||
|
{
|
||||||
|
[XmlElement("content")]
|
||||||
|
public GameNewsContent Content { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GameNewsContent : ILbpSerializable
|
||||||
|
{
|
||||||
|
[XmlElement("frame")]
|
||||||
|
public GameNewsFrame Frame { get; set; }
|
||||||
|
}
|
40
ProjectLighthouse/Types/Serialization/News/GameNewsFrame.cs
Normal file
40
ProjectLighthouse/Types/Serialization/News/GameNewsFrame.cs
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
#nullable enable
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Xml.Serialization;
|
||||||
|
using LBPUnion.ProjectLighthouse.Types.Serialization.Slot;
|
||||||
|
using LBPUnion.ProjectLighthouse.Types.Serialization.User;
|
||||||
|
|
||||||
|
namespace LBPUnion.ProjectLighthouse.Types.Serialization.News;
|
||||||
|
|
||||||
|
[XmlRoot("frame")]
|
||||||
|
public class GameNewsFrame : ILbpSerializable
|
||||||
|
{
|
||||||
|
[XmlAttribute("width")]
|
||||||
|
public int Width { get; set; }
|
||||||
|
|
||||||
|
[XmlElement("title")]
|
||||||
|
public string Title { get; set; } = "";
|
||||||
|
|
||||||
|
[XmlElement("item")]
|
||||||
|
[DefaultValue(null)]
|
||||||
|
public List<GameNewsFrameContainer>? Container { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GameNewsFrameContainer : ILbpSerializable
|
||||||
|
{
|
||||||
|
[XmlAttribute("width")]
|
||||||
|
public int Width { get; set; }
|
||||||
|
|
||||||
|
[XmlElement("content")]
|
||||||
|
[DefaultValue(null)]
|
||||||
|
public string Content { get; set; } = "";
|
||||||
|
|
||||||
|
[XmlElement("npHandle")]
|
||||||
|
[DefaultValue(null)]
|
||||||
|
public MinimalUserProfile? User { get; set; }
|
||||||
|
|
||||||
|
[XmlElement("slot")]
|
||||||
|
[DefaultValue(null)]
|
||||||
|
public MinimalSlot? Slot { get; set; }
|
||||||
|
}
|
54
ProjectLighthouse/Types/Serialization/News/GameNewsObject.cs
Normal file
54
ProjectLighthouse/Types/Serialization/News/GameNewsObject.cs
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Xml.Serialization;
|
||||||
|
using LBPUnion.ProjectLighthouse.Types.Entities.Website;
|
||||||
|
|
||||||
|
namespace LBPUnion.ProjectLighthouse.Types.Serialization.News;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used in LBP2 and beyond
|
||||||
|
/// </summary>
|
||||||
|
[XmlRoot("item")]
|
||||||
|
public class GameNewsObject : ILbpSerializable
|
||||||
|
{
|
||||||
|
[XmlElement("id")]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
[XmlElement("title")]
|
||||||
|
public string Title { get; set; }
|
||||||
|
|
||||||
|
[XmlElement("summary")]
|
||||||
|
public string Summary { get; set; }
|
||||||
|
|
||||||
|
[XmlElement("text")]
|
||||||
|
public string Text { get; set; }
|
||||||
|
|
||||||
|
[XmlElement("date")]
|
||||||
|
public long Timestamp { get; set; }
|
||||||
|
|
||||||
|
[XmlElement("image")]
|
||||||
|
[DefaultValue(null)]
|
||||||
|
public GameNewsImage Image { get; set; }
|
||||||
|
|
||||||
|
[XmlElement("category")]
|
||||||
|
public string Category { get; set; }
|
||||||
|
|
||||||
|
public static GameNewsObject CreateFromEntity(WebsiteAnnouncementEntity entity) =>
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Id = entity.AnnouncementId,
|
||||||
|
Title = entity.Title,
|
||||||
|
Summary = "there's an extra spot for summary here",
|
||||||
|
Text = entity.Content,
|
||||||
|
Category = "no_category",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[XmlRoot("image")]
|
||||||
|
public class GameNewsImage : ILbpSerializable
|
||||||
|
{
|
||||||
|
[XmlElement("hash")]
|
||||||
|
public string Hash { get; set; }
|
||||||
|
|
||||||
|
[XmlElement("alignment")]
|
||||||
|
public string Alignment { get; set; }
|
||||||
|
}
|
|
@ -30,7 +30,6 @@ public class GamePlaylist : ILbpSerializable, INeedsPreparationForSerialization
|
||||||
[XmlElement("name")]
|
[XmlElement("name")]
|
||||||
public string Name { get; set; } = "";
|
public string Name { get; set; } = "";
|
||||||
|
|
||||||
[DefaultValue("")]
|
|
||||||
[XmlElement("description")]
|
[XmlElement("description")]
|
||||||
public string Description { get; set; } = "";
|
public string Description { get; set; } = "";
|
||||||
|
|
||||||
|
@ -62,6 +61,7 @@ public class GamePlaylist : ILbpSerializable, INeedsPreparationForSerialization
|
||||||
Username = authorUsername,
|
Username = authorUsername,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.LevelCount = this.SlotIds.Length;
|
||||||
this.Hearts = await database.HeartedPlaylists.CountAsync(h => h.HeartedPlaylistId == this.PlaylistId);
|
this.Hearts = await database.HeartedPlaylists.CountAsync(h => h.HeartedPlaylistId == this.PlaylistId);
|
||||||
this.PlaylistQuota = ServerConfiguration.Instance.UserGeneratedContentLimits.ListsQuota;
|
this.PlaylistQuota = ServerConfiguration.Instance.UserGeneratedContentLimits.ListsQuota;
|
||||||
List<string> iconList = this.SlotIds.Select(id => database.Slots.FirstOrDefault(s => s.SlotId == id))
|
List<string> iconList = this.SlotIds.Select(id => database.Slots.FirstOrDefault(s => s.SlotId == id))
|
||||||
|
|
22
ProjectLighthouse/Types/Serialization/Slot/MinimalSlot.cs
Normal file
22
ProjectLighthouse/Types/Serialization/Slot/MinimalSlot.cs
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
using System.Xml.Serialization;
|
||||||
|
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||||
|
using LBPUnion.ProjectLighthouse.Types.Levels;
|
||||||
|
|
||||||
|
namespace LBPUnion.ProjectLighthouse.Types.Serialization.Slot;
|
||||||
|
|
||||||
|
[XmlRoot("slot")]
|
||||||
|
public class MinimalSlot : ILbpSerializable
|
||||||
|
{
|
||||||
|
[XmlElement("type")]
|
||||||
|
public SlotType Type { get; set; }
|
||||||
|
|
||||||
|
[XmlElement("id")]
|
||||||
|
public int SlotId { get; set; }
|
||||||
|
|
||||||
|
public MinimalSlot CreateFromEntity(SlotEntity slot) =>
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Type = slot.Type,
|
||||||
|
SlotId = slot.SlotId,
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,4 +1,6 @@
|
||||||
using System.ComponentModel;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Xml.Serialization;
|
using System.Xml.Serialization;
|
||||||
|
@ -185,15 +187,21 @@ public class GameUser : ILbpSerializable, INeedsPreparationForSerialization
|
||||||
|
|
||||||
int entitledSlots = ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots + stats.BonusSlots;
|
int entitledSlots = ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots + stats.BonusSlots;
|
||||||
|
|
||||||
IQueryable<SlotEntity> SlotCount(GameVersion version)
|
Dictionary<GameVersion, int> slotsByGame = await database.Slots.Where(s => s.CreatorId == this.UserId && !s.CrossControllerRequired)
|
||||||
{
|
.GroupBy(s => s.GameVersion)
|
||||||
return database.Slots.Where(s => s.CreatorId == this.UserId && s.GameVersion == version);
|
.Select(g => new
|
||||||
}
|
{
|
||||||
|
Game = g.Key,
|
||||||
|
Count = g.Count(),
|
||||||
|
})
|
||||||
|
.ToDictionaryAsync(k => k.Game, k => k.Count);
|
||||||
|
|
||||||
|
int GetSlotCount(GameVersion version) => slotsByGame.TryGetValue(version, out int count) ? count : 0;
|
||||||
|
|
||||||
if (this.TargetGame == GameVersion.LittleBigPlanetVita)
|
if (this.TargetGame == GameVersion.LittleBigPlanetVita)
|
||||||
{
|
{
|
||||||
this.Lbp2EntitledSlots = entitledSlots;
|
this.Lbp2EntitledSlots = entitledSlots;
|
||||||
this.Lbp2UsedSlots = await SlotCount(GameVersion.LittleBigPlanetVita).CountAsync();
|
this.Lbp2UsedSlots = GetSlotCount(GameVersion.LittleBigPlanetVita);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -201,9 +209,9 @@ public class GameUser : ILbpSerializable, INeedsPreparationForSerialization
|
||||||
this.Lbp2EntitledSlots = entitledSlots;
|
this.Lbp2EntitledSlots = entitledSlots;
|
||||||
this.CrossControlEntitledSlots = entitledSlots;
|
this.CrossControlEntitledSlots = entitledSlots;
|
||||||
this.Lbp3EntitledSlots = entitledSlots;
|
this.Lbp3EntitledSlots = entitledSlots;
|
||||||
this.Lbp1UsedSlots = await SlotCount(GameVersion.LittleBigPlanet1).CountAsync();
|
this.Lbp1UsedSlots = GetSlotCount(GameVersion.LittleBigPlanet1);
|
||||||
this.Lbp2UsedSlots = await SlotCount(GameVersion.LittleBigPlanet2).CountAsync(s => !s.CrossControllerRequired);
|
this.Lbp2UsedSlots = GetSlotCount(GameVersion.LittleBigPlanet2);
|
||||||
this.Lbp3UsedSlots = await SlotCount(GameVersion.LittleBigPlanet3).CountAsync();
|
this.Lbp3UsedSlots = GetSlotCount(GameVersion.LittleBigPlanet3);
|
||||||
|
|
||||||
this.Lbp1FreeSlots = this.Lbp1EntitledSlots - this.Lbp1UsedSlots;
|
this.Lbp1FreeSlots = this.Lbp1EntitledSlots - this.Lbp1UsedSlots;
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue