mirror of
https://github.com/LBPUnion/ProjectLighthouse.git
synced 2025-08-03 02:28:39 +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.Filter;
|
||||
using LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
using LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Servers.API.Responses;
|
||||
using LBPUnion.ProjectLighthouse.Types.Users;
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
using System.Linq.Expressions;
|
||||
using LBPUnion.ProjectLighthouse.Database;
|
||||
using LBPUnion.ProjectLighthouse.Extensions;
|
||||
using LBPUnion.ProjectLighthouse.Filter.Filters.Activity;
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.StorableLists.Stores;
|
||||
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.Levels;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization.Activity;
|
||||
|
@ -29,143 +28,210 @@ public class ActivityController : ControllerBase
|
|||
this.database = database;
|
||||
}
|
||||
|
||||
public class ActivityDto
|
||||
{
|
||||
public required ActivityEntity Activity { get; set; }
|
||||
public int? TargetSlotId { get; set; }
|
||||
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
|
||||
/// <summary>
|
||||
/// This method is only used for LBP2 so we exclude playlists
|
||||
/// </summary>
|
||||
private async Task<IQueryable<ActivityDto>> GetFilters
|
||||
(
|
||||
IQueryable<ActivityDto> dtoQuery,
|
||||
GameTokenEntity token,
|
||||
bool excludeNews,
|
||||
bool excludeMyLevels,
|
||||
bool excludeFriends,
|
||||
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>();
|
||||
|
||||
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)
|
||||
.Select(hp => hp.HeartedUserId)
|
||||
.ToListAsync();
|
||||
|
||||
predicate = excludeFavouriteUsers
|
||||
? predicate.Or(a => !favouriteUsers.Contains(a.Activity.UserId))
|
||||
: predicate.Or(a => favouriteUsers.Contains(a.Activity.UserId));
|
||||
List<int>? friendIds = UserFriendStore.GetUserFriendData(token.UserId)?.FriendIds;
|
||||
friendIds ??= new List<int>();
|
||||
|
||||
predicate = excludeMyself
|
||||
? predicate.Or(a => a.Activity.UserId != token.UserId)
|
||||
: predicate.Or(a => a.Activity.UserId == token.UserId);
|
||||
// This is how lbp3 does its filtering
|
||||
GameStreamFilter? filter = await this.DeserializeBody<GameStreamFilter>();
|
||||
if (filter?.Sources != null)
|
||||
{
|
||||
foreach (GameStreamFilterEventSource filterSource in filter.Sources.Where(filterSource =>
|
||||
filterSource.SourceType != null && filterSource.Types?.Count != 0))
|
||||
{
|
||||
EventType[] types = filterSource.Types?.ToArray() ?? Array.Empty<EventType>();
|
||||
EventTypeFilter eventFilter = new(types);
|
||||
predicate = filterSource.SourceType switch
|
||||
{
|
||||
"MyLevels" => predicate.Or(new MyLevelActivityFilter(token.UserId, eventFilter).GetPredicate()),
|
||||
"FavouriteUsers" => predicate.Or(
|
||||
new IncludeUserIdFilter(favouriteUsers, eventFilter).GetPredicate()),
|
||||
"Friends" => predicate.Or(new IncludeUserIdFilter(friendIds, eventFilter).GetPredicate()),
|
||||
_ => predicate,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
.Where(a => a.Timestamp < upperBound)
|
||||
.OrderByDescending(a => a.Timestamp)
|
||||
.Select(a => a.Timestamp)
|
||||
return activity.OrderByDescending(a => a.Activity.Timestamp)
|
||||
.Where(a => a.Activity.Timestamp < upperBound)
|
||||
.Select(a => a.Activity.Timestamp)
|
||||
.FirstOrDefaultAsync();
|
||||
}
|
||||
|
||||
private async Task<(DateTime Start, DateTime End)> GetTimeBounds
|
||||
(IQueryable<ActivityDto> activityQuery, long? startTime, long? endTime)
|
||||
{
|
||||
if (startTime is null or 0) startTime = TimeHelper.TimestampMillis;
|
||||
|
||||
DateTime start = DateTimeExtensions.FromUnixTimeMilliseconds(startTime.Value);
|
||||
DateTime end;
|
||||
|
||||
if (endTime == null)
|
||||
{
|
||||
end = await this.GetMostRecentEventTime(activityQuery, start);
|
||||
// If there is no recent event then set it to the the start
|
||||
if (end == DateTime.MinValue) end = start;
|
||||
end = end.Subtract(TimeSpan.FromDays(7));
|
||||
}
|
||||
else
|
||||
{
|
||||
end = DateTimeExtensions.FromUnixTimeMilliseconds(endTime.Value);
|
||||
// Don't allow more than 7 days worth of activity in a single page
|
||||
if (start.Subtract(end).TotalDays > 7)
|
||||
{
|
||||
end = start.Subtract(TimeSpan.FromDays(7));
|
||||
}
|
||||
}
|
||||
|
||||
return (start, end);
|
||||
}
|
||||
|
||||
private static DateTime GetOldestTime
|
||||
(IReadOnlyCollection<IGrouping<ActivityGroup, ActivityDto>> groups, DateTime defaultTimestamp) =>
|
||||
groups.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]
|
||||
public async Task<IActionResult> GlobalActivity
|
||||
(
|
||||
long timestamp,
|
||||
long endTimestamp,
|
||||
bool excludeNews,
|
||||
bool excludeMyLevels,
|
||||
bool excludeFriends,
|
||||
|
@ -175,112 +241,109 @@ public class ActivityController : ControllerBase
|
|||
{
|
||||
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;
|
||||
|
||||
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,
|
||||
IQueryable<ActivityDto> activityEvents = await this.GetFilters(this.database.Activities.ToActivityDto(true),
|
||||
token,
|
||||
excludeNews,
|
||||
excludeMyLevels,
|
||||
excludeFriends,
|
||||
excludeFavouriteUsers,
|
||||
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(
|
||||
$@"{key.GroupType}: Timestamp: {key.Timestamp}, UserId: {key.UserId}, TargetSlotId: {key.TargetSlotId}, " +
|
||||
@$"TargetUserId: {key.TargetUserId}, TargetPlaylistId: {key.TargetPlaylistId}");
|
||||
foreach (ActivityEntity activity in group)
|
||||
Console.WriteLine(@$"Outer group key: {outer.Key}");
|
||||
List<IGrouping<InnerActivityGroup, ActivityDto>> itemGroup = outer.Groups;
|
||||
foreach (IGrouping<InnerActivityGroup, ActivityDto> item in itemGroup)
|
||||
{
|
||||
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}")]
|
||||
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();
|
||||
|
||||
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")
|
||||
slotId = await SlotHelper.GetPlaceholderSlotId(this.database, slotId, SlotType.Developer);
|
||||
// Slot activity
|
||||
if (isLevelActivity)
|
||||
{
|
||||
if (slotType == "developer")
|
||||
slotId = await SlotHelper.GetPlaceholderSlotId(this.database, slotId, SlotType.Developer);
|
||||
|
||||
IQueryable<ActivityDto> slotActivity = this.database.Activities.Select(ActivityToDto())
|
||||
.Where(a => a.TargetSlotId == slotId);
|
||||
if (!await this.database.Slots.AnyAsync(s => s.SlotId == slotId)) return this.NotFound();
|
||||
|
||||
DateTime start = DateTimeOffset.FromUnixTimeMilliseconds(timestamp).DateTime;
|
||||
DateTime end = DateTimeOffset.FromUnixTimeMilliseconds(endTimestamp).DateTime;
|
||||
activityQuery = activityQuery.Where(dto => dto.TargetSlotId == slotId);
|
||||
}
|
||||
// User activity
|
||||
else
|
||||
{
|
||||
int userId = await this.database.Users.Where(u => u.Username == username)
|
||||
.Select(u => u.UserId)
|
||||
.FirstOrDefaultAsync();
|
||||
if (userId == 0) return this.NotFound();
|
||||
activityQuery = activityQuery.Where(dto => dto.Activity.UserId == userId);
|
||||
}
|
||||
|
||||
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));
|
||||
long oldestTimestamp = new DateTimeOffset(oldestTime).ToUnixTimeMilliseconds();
|
||||
List<IGrouping<ActivityGroup, ActivityDto>> groups = await activityQuery.ToActivityGroups().ToListAsync();
|
||||
|
||||
return this.Ok(await GameStream.CreateFromEntityResult(this.database, token, groups, timestamp, oldestTimestamp));
|
||||
}
|
||||
List<OuterActivityGroup> outerGroups = groups.ToOuterActivityGroups();
|
||||
|
||||
[HttpGet("user2/{userId:int}/")]
|
||||
public async Task<IActionResult> UserActivity(int userId, long timestamp)
|
||||
{
|
||||
GameTokenEntity token = this.GetToken();
|
||||
long oldestTimestamp = GetOldestTime(groups, times.Start).ToUnixTimeMilliseconds();
|
||||
|
||||
if (token.GameVersion == GameVersion.LittleBigPlanet1) return this.BadRequest();
|
||||
await this.CacheEntities(outerGroups);
|
||||
|
||||
if (timestamp > TimeHelper.TimestampMillis || timestamp <= 0) timestamp = TimeHelper.TimestampMillis;
|
||||
|
||||
long endTimestamp = timestamp - 864_000;
|
||||
|
||||
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));
|
||||
return this.Ok(GameStream.CreateFromGroups(token,
|
||||
outerGroups,
|
||||
times.Start.ToUnixTimeMilliseconds(),
|
||||
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();
|
||||
}
|
||||
|
||||
[HttpGet("review/user/{slotId:int}/{reviewerName}")]
|
||||
public async Task<IActionResult> GetReview(int slotId, string reviewerName)
|
||||
{
|
||||
GameTokenEntity token = this.GetToken();
|
||||
|
||||
int reviewerId = await this.database.Users.Where(u => u.Username == reviewerName)
|
||||
.Select(s => s.UserId)
|
||||
.FirstOrDefaultAsync();
|
||||
if (reviewerId == 0) return this.NotFound();
|
||||
|
||||
ReviewEntity? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.ReviewerId == reviewerId && r.SlotId == slotId);
|
||||
if (review == null) return this.NotFound();
|
||||
|
||||
return this.Ok(GameReview.CreateFromEntity(review, token));
|
||||
}
|
||||
|
||||
[HttpGet("reviewsFor/user/{slotId:int}")]
|
||||
public async Task<IActionResult> ReviewsFor(int slotId)
|
||||
{
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
using LBPUnion.ProjectLighthouse.Database;
|
||||
using LBPUnion.ProjectLighthouse.Extensions;
|
||||
using LBPUnion.ProjectLighthouse.Filter;
|
||||
using LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
using LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
using LBPUnion.ProjectLighthouse.Filter.Sorts;
|
||||
using LBPUnion.ProjectLighthouse.Servers.GameServer.Extensions;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
|
|
|
@ -3,7 +3,7 @@ using System.Linq.Expressions;
|
|||
using LBPUnion.ProjectLighthouse.Database;
|
||||
using LBPUnion.ProjectLighthouse.Extensions;
|
||||
using LBPUnion.ProjectLighthouse.Filter;
|
||||
using LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
using LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
using LBPUnion.ProjectLighthouse.Filter.Sorts;
|
||||
using LBPUnion.ProjectLighthouse.Filter.Sorts.Metadata;
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
|
|
|
@ -4,7 +4,7 @@ using Microsoft.AspNetCore.Authorization;
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using LBPUnion.ProjectLighthouse.Extensions;
|
||||
using LBPUnion.ProjectLighthouse.Filter;
|
||||
using LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
using LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
using LBPUnion.ProjectLighthouse.Servers.GameServer.Extensions;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization.Slot;
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
using LBPUnion.ProjectLighthouse.Filter;
|
||||
using LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
using LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
|
||||
using LBPUnion.ProjectLighthouse.Types.Levels;
|
||||
using LBPUnion.ProjectLighthouse.Types.Users;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#nullable enable
|
||||
using LBPUnion.ProjectLighthouse.Database;
|
||||
using LBPUnion.ProjectLighthouse.Filter;
|
||||
using LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
using LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
using LBPUnion.ProjectLighthouse.Database;
|
||||
using LBPUnion.ProjectLighthouse.Extensions;
|
||||
using LBPUnion.ProjectLighthouse.Filter;
|
||||
using LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
using LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
using LBPUnion.ProjectLighthouse.Filter.Sorts;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
|
||||
|
|
|
@ -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.Filters;
|
||||
using LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
using LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Slots;
|
||||
using LBPUnion.ProjectLighthouse.Servers.GameServer.Extensions;
|
||||
using LBPUnion.ProjectLighthouse.Tests.Helpers;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using LBPUnion.ProjectLighthouse.Filter;
|
||||
using LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
using LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
|
||||
using LBPUnion.ProjectLighthouse.Types.Levels;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
using System;
|
||||
using LBPUnion.ProjectLighthouse.Configuration;
|
||||
using LBPUnion.ProjectLighthouse.Types.Activity;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Activity;
|
||||
|
@ -90,30 +91,34 @@ public partial class DatabaseContext : DbContext
|
|||
public static DatabaseContext CreateNewInstance()
|
||||
{
|
||||
DbContextOptionsBuilder<DatabaseContext> builder = new();
|
||||
builder.UseMySql(ServerConfiguration.Instance.DbConnectionString,
|
||||
MySqlServerVersion.LatestSupportedServerVersion);
|
||||
ConfigureBuilder()(builder);
|
||||
return new DatabaseContext(builder.Options);
|
||||
}
|
||||
|
||||
public static Action<DbContextOptionsBuilder> ConfigureBuilder()
|
||||
{
|
||||
return builder =>
|
||||
{
|
||||
builder.UseMySql(ServerConfiguration.Instance.DbConnectionString,
|
||||
MySqlServerVersion.LatestSupportedServerVersion);
|
||||
builder.AddInterceptors(new ActivityInterceptor(new ActivityEntityEventHandler()));
|
||||
};
|
||||
}
|
||||
|
||||
#region Activity
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
//TODO implement reviews
|
||||
modelBuilder.Entity<LevelActivityEntity>().UseTphMappingStrategy();
|
||||
modelBuilder.Entity<PhotoActivityEntity>().UseTphMappingStrategy();
|
||||
modelBuilder.Entity<PlaylistActivityEntity>().UseTphMappingStrategy();
|
||||
modelBuilder.Entity<PlaylistWithSlotActivityEntity>().UseTphMappingStrategy();
|
||||
modelBuilder.Entity<ScoreActivityEntity>().UseTphMappingStrategy();
|
||||
modelBuilder.Entity<UserActivityEntity>().UseTphMappingStrategy();
|
||||
modelBuilder.Entity<NewsActivityEntity>().UseTphMappingStrategy();
|
||||
modelBuilder.Entity<CommentActivityEntity>().UseTphMappingStrategy();
|
||||
modelBuilder.Entity<UserActivityEntity>().UseTphMappingStrategy();
|
||||
modelBuilder.Entity<ReviewActivityEntity>().UseTphMappingStrategy();
|
||||
base.OnModelCreating(modelBuilder);
|
||||
}
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
{
|
||||
optionsBuilder.AddInterceptors(new ActivityInterceptor(new ActivityEntityEventHandler()));
|
||||
base.OnConfiguring(optionsBuilder);
|
||||
}
|
||||
#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)
|
||||
{
|
||||
string bodyString = await controller.ReadBodyAsync();
|
||||
if (bodyString.Length == 0) return default;
|
||||
try
|
||||
{
|
||||
// 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.Filter;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
|
||||
public class AdventureFilter : ISlotFilter
|
||||
{
|
|
@ -5,7 +5,7 @@ using LBPUnion.ProjectLighthouse.Extensions;
|
|||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
|
||||
public class AuthorLabelFilter : ISlotFilter
|
||||
{
|
||||
|
@ -20,7 +20,7 @@ public class AuthorLabelFilter : ISlotFilter
|
|||
{
|
||||
Expression<Func<SlotEntity, bool>> predicate = PredicateExtensions.True<SlotEntity>();
|
||||
predicate = this.labels.Aggregate(predicate,
|
||||
(current, label) => current.And(s => s.AuthorLabels.Contains(label)));
|
||||
(current, label) => PredicateExtensions.And<SlotEntity>(current, s => s.AuthorLabels.Contains(label)));
|
||||
return predicate;
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@ using System.Linq.Expressions;
|
|||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
|
||||
public class CreatorFilter : ISlotFilter
|
||||
{
|
|
@ -3,7 +3,7 @@ using System.Linq.Expressions;
|
|||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
|
||||
public class CrossControlFilter : ISlotFilter
|
||||
{
|
|
@ -3,7 +3,7 @@ using System.Linq.Expressions;
|
|||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
|
||||
public class ExcludeAdventureFilter : ISlotFilter
|
||||
{
|
|
@ -3,7 +3,7 @@ using System.Linq.Expressions;
|
|||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
|
||||
public class ExcludeCrossControlFilter : ISlotFilter
|
||||
{
|
|
@ -4,7 +4,7 @@ using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
|||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
using LBPUnion.ProjectLighthouse.Types.Users;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
|
||||
public class ExcludeLBP1OnlyFilter : ISlotFilter
|
||||
{
|
|
@ -3,7 +3,7 @@ using System.Linq.Expressions;
|
|||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
|
||||
public class ExcludeMovePackFilter : ISlotFilter
|
||||
{
|
|
@ -4,7 +4,7 @@ using LBPUnion.ProjectLighthouse.Extensions;
|
|||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
|
||||
public class FirstUploadedFilter : ISlotFilter
|
||||
{
|
|
@ -4,7 +4,7 @@ using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
|||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
using LBPUnion.ProjectLighthouse.Types.Users;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
|
||||
public class GameVersionFilter : ISlotFilter
|
||||
{
|
|
@ -6,7 +6,7 @@ using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
|||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
using LBPUnion.ProjectLighthouse.Types.Users;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
|
||||
public class GameVersionListFilter : ISlotFilter
|
||||
{
|
||||
|
@ -19,5 +19,5 @@ public class GameVersionListFilter : ISlotFilter
|
|||
|
||||
public Expression<Func<SlotEntity, bool>> GetPredicate() =>
|
||||
this.versions.Aggregate(PredicateExtensions.False<SlotEntity>(),
|
||||
(current, version) => current.Or(s => s.GameVersion == version));
|
||||
(current, version) => PredicateExtensions.Or<SlotEntity>(current, s => s.GameVersion == version));
|
||||
}
|
|
@ -3,7 +3,7 @@ using System.Linq.Expressions;
|
|||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
|
||||
public class HiddenSlotFilter : ISlotFilter
|
||||
{
|
|
@ -3,7 +3,7 @@ using System.Linq.Expressions;
|
|||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
|
||||
public class MovePackFilter : ISlotFilter
|
||||
{
|
|
@ -4,7 +4,7 @@ using LBPUnion.ProjectLighthouse.Extensions;
|
|||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
|
||||
public class PlayerCountFilter : ISlotFilter
|
||||
{
|
|
@ -4,7 +4,7 @@ using System.Linq.Expressions;
|
|||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
|
||||
public class ResultTypeFilter : ISlotFilter
|
||||
{
|
|
@ -6,7 +6,7 @@ using LBPUnion.ProjectLighthouse.Extensions;
|
|||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
|
||||
public class SlotIdFilter : ISlotFilter
|
||||
{
|
||||
|
@ -20,7 +20,7 @@ public class SlotIdFilter : ISlotFilter
|
|||
public Expression<Func<SlotEntity, bool>> GetPredicate()
|
||||
{
|
||||
Expression<Func<SlotEntity, bool>> predicate = PredicateExtensions.False<SlotEntity>();
|
||||
predicate = this.slotIds.Aggregate(predicate, (current, slotId) => current.Or(s => s.SlotId == slotId));
|
||||
predicate = this.slotIds.Aggregate(predicate, (current, slotId) => PredicateExtensions.Or<SlotEntity>(current, s => s.SlotId == slotId));
|
||||
return predicate;
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
|||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
using LBPUnion.ProjectLighthouse.Types.Levels;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
|
||||
public class SlotTypeFilter : ISlotFilter
|
||||
{
|
|
@ -3,7 +3,7 @@ using System.Linq.Expressions;
|
|||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
|
||||
public class SubLevelFilter : ISlotFilter
|
||||
{
|
|
@ -3,7 +3,7 @@ using System.Linq.Expressions;
|
|||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
|
||||
public class TeamPickFilter : ISlotFilter
|
||||
{
|
|
@ -4,7 +4,7 @@ using LBPUnion.ProjectLighthouse.Extensions;
|
|||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
namespace LBPUnion.ProjectLighthouse.Filter.Filters.Slot;
|
||||
|
||||
public class TextFilter : ISlotFilter
|
||||
{
|
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("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 =>
|
||||
{
|
||||
b.Property<int>("HeartedLevelId")
|
||||
|
@ -1090,6 +1120,136 @@ namespace ProjectLighthouse.Migrations
|
|||
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 =>
|
||||
{
|
||||
b.HasOne("LBPUnion.ProjectLighthouse.Types.Entities.Level.SlotEntity", "Slot")
|
||||
|
@ -1483,6 +1643,105 @@ namespace ProjectLighthouse.Migrations
|
|||
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 =>
|
||||
{
|
||||
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.Reflection;
|
||||
using LBPUnion.ProjectLighthouse.Database;
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Activity;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Interaction;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Website;
|
||||
using LBPUnion.ProjectLighthouse.Types.Levels;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Types.Activity;
|
||||
|
||||
|
@ -44,7 +45,7 @@ public class ActivityEntityEventHandler : IEntityEventHandler
|
|||
Type = EventType.Score,
|
||||
ScoreId = score.ScoreId,
|
||||
//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
|
||||
{
|
||||
|
@ -58,12 +59,42 @@ public class ActivityEntityEventHandler : IEntityEventHandler
|
|||
TargetUserId = heartedProfile.HeartedUserId,
|
||||
UserId = heartedProfile.UserId,
|
||||
},
|
||||
HeartedPlaylistEntity heartedPlaylist => new PlaylistActivityEntity
|
||||
{
|
||||
Type = EventType.HeartPlaylist,
|
||||
PlaylistId = heartedPlaylist.PlaylistId,
|
||||
UserId = heartedPlaylist.UserId,
|
||||
},
|
||||
VisitedLevelEntity visitedLevel => new LevelActivityEntity
|
||||
{
|
||||
Type = EventType.PlayLevel,
|
||||
SlotId = visitedLevel.SlotId,
|
||||
UserId = visitedLevel.UserId,
|
||||
},
|
||||
ReviewEntity review => new ReviewActivityEntity
|
||||
{
|
||||
Type = EventType.ReviewLevel,
|
||||
ReviewId = review.ReviewId,
|
||||
UserId = review.ReviewerId,
|
||||
},
|
||||
RatedLevelEntity ratedLevel => new LevelActivityEntity
|
||||
{
|
||||
Type = ratedLevel.Rating != 0 ? EventType.DpadRateLevel : EventType.RateLevel,
|
||||
SlotId = ratedLevel.SlotId,
|
||||
UserId = ratedLevel.UserId,
|
||||
},
|
||||
PlaylistEntity playlist => new PlaylistActivityEntity
|
||||
{
|
||||
Type = EventType.CreatePlaylist,
|
||||
PlaylistId = playlist.PlaylistId,
|
||||
UserId = playlist.CreatorId,
|
||||
},
|
||||
WebsiteAnnouncementEntity announcement => new NewsActivityEntity
|
||||
{
|
||||
Type = EventType.NewsPost,
|
||||
UserId = announcement.PublisherId ?? 0,
|
||||
NewsId = announcement.AnnouncementId,
|
||||
},
|
||||
_ => null,
|
||||
};
|
||||
InsertActivity(database, activity);
|
||||
|
@ -82,6 +113,7 @@ public class ActivityEntityEventHandler : IEntityEventHandler
|
|||
|
||||
public void OnEntityChanged<T>(DatabaseContext database, T origEntity, T currentEntity) where T : class
|
||||
{
|
||||
#if DEBUG
|
||||
foreach (PropertyInfo propInfo in currentEntity.GetType().GetProperties())
|
||||
{
|
||||
if (!propInfo.CanRead || !propInfo.CanWrite) continue;
|
||||
|
@ -97,14 +129,19 @@ public class ActivityEntityEventHandler : IEntityEventHandler
|
|||
Console.WriteLine($@"Orig val: {origVal?.ToString() ?? "null"}");
|
||||
Console.WriteLine($@"New val: {newVal?.ToString() ?? "null"}");
|
||||
}
|
||||
|
||||
Console.WriteLine($@"OnEntityChanged: {currentEntity.GetType().Name}");
|
||||
#endif
|
||||
|
||||
ActivityEntity? activity = null;
|
||||
switch (currentEntity)
|
||||
{
|
||||
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
|
||||
{
|
||||
|
@ -118,25 +155,88 @@ public class ActivityEntityEventHandler : IEntityEventHandler
|
|||
{
|
||||
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,
|
||||
SlotId = slotEntity.SlotId,
|
||||
UserId = SlotHelper.GetPlaceholderUserId(database).Result,
|
||||
};
|
||||
}
|
||||
else if (oldSlotEntity.SlotId == slotEntity.SlotId && slotEntity.Type == SlotType.User)
|
||||
{
|
||||
activity = new LevelActivityEntity
|
||||
{
|
||||
Type = EventType.PublishLevel,
|
||||
SlotId = slotEntity.SlotId,
|
||||
UserId = slotEntity.CreatorId,
|
||||
};
|
||||
}
|
||||
if (oldSlotEntity.SlotId == slotEntity.SlotId &&
|
||||
slotEntity.Type == SlotType.User &&
|
||||
oldSlotEntity.LastUpdated != slotEntity.LastUpdated)
|
||||
{
|
||||
activity = new LevelActivityEntity
|
||||
{
|
||||
Type = EventType.PublishLevel,
|
||||
SlotId = slotEntity.SlotId,
|
||||
UserId = slotEntity.CreatorId,
|
||||
};
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CommentEntity comment:
|
||||
{
|
||||
if (origEntity is not CommentEntity oldComment) break;
|
||||
|
||||
if (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;
|
||||
}
|
||||
}
|
||||
|
@ -149,17 +249,6 @@ public class ActivityEntityEventHandler : IEntityEventHandler
|
|||
Console.WriteLine($@"OnEntityDeleted: {entity.GetType().Name}");
|
||||
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
|
||||
{
|
||||
Type = EventType.UnheartLevel,
|
||||
|
|
|
@ -1,31 +1,59 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Types.Activity;
|
||||
|
||||
public class ActivityGroup
|
||||
public struct ActivityGroup
|
||||
{
|
||||
public DateTime Timestamp { get; set; }
|
||||
public int UserId { get; set; }
|
||||
public int? TargetSlotId { get; set; }
|
||||
public int? TargetUserId { get; set; }
|
||||
public int? TargetPlaylistId { get; set; }
|
||||
public int? TargetNewsId { get; set; }
|
||||
public int? TargetTeamPickSlotId { get; set; }
|
||||
|
||||
public int TargetId =>
|
||||
this.GroupType switch
|
||||
{
|
||||
ActivityGroupType.User => this.TargetUserId ?? 0,
|
||||
ActivityGroupType.Level => this.TargetSlotId ?? 0,
|
||||
ActivityGroupType.User => this.TargetUserId ?? this.UserId,
|
||||
ActivityGroupType.Level => this.TargetSlotId?? 0,
|
||||
ActivityGroupType.TeamPick => this.TargetTeamPickSlotId ?? 0,
|
||||
ActivityGroupType.Playlist => this.TargetPlaylistId ?? 0,
|
||||
ActivityGroupType.News => this.TargetNewsId ?? 0,
|
||||
_ => this.UserId,
|
||||
};
|
||||
|
||||
public ActivityGroupType GroupType =>
|
||||
this.TargetSlotId != 0
|
||||
(this.TargetSlotId ?? 0) != 0
|
||||
? ActivityGroupType.Level
|
||||
: this.TargetUserId != 0
|
||||
: (this.TargetUserId ?? 0) != 0
|
||||
? 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
|
||||
|
@ -38,4 +66,10 @@ public enum ActivityGroupType
|
|||
|
||||
[XmlEnum("playlist")]
|
||||
Playlist,
|
||||
|
||||
[XmlEnum("news")]
|
||||
News,
|
||||
|
||||
[XmlEnum("slot")]
|
||||
TeamPick,
|
||||
}
|
|
@ -2,68 +2,71 @@
|
|||
|
||||
namespace LBPUnion.ProjectLighthouse.Types.Activity;
|
||||
|
||||
/// <summary>
|
||||
/// UnheartLevel, UnheartUser, DeleteLevelComment, and UnpublishLevel don't actually do anything
|
||||
/// </summary>
|
||||
public enum EventType
|
||||
{
|
||||
[XmlEnum("heart_level")]
|
||||
HeartLevel,
|
||||
HeartLevel = 0,
|
||||
|
||||
[XmlEnum("unheart_level")]
|
||||
UnheartLevel,
|
||||
UnheartLevel = 1,
|
||||
|
||||
[XmlEnum("heart_user")]
|
||||
HeartUser,
|
||||
HeartUser = 2,
|
||||
|
||||
[XmlEnum("unheart_user")]
|
||||
UnheartUser,
|
||||
UnheartUser = 3,
|
||||
|
||||
[XmlEnum("play_level")]
|
||||
PlayLevel,
|
||||
PlayLevel = 4,
|
||||
|
||||
[XmlEnum("rate_level")]
|
||||
RateLevel,
|
||||
RateLevel = 5,
|
||||
|
||||
[XmlEnum("tag_level")]
|
||||
TagLevel,
|
||||
TagLevel = 6,
|
||||
|
||||
[XmlEnum("comment_on_level")]
|
||||
CommentOnLevel,
|
||||
CommentOnLevel = 7,
|
||||
|
||||
[XmlEnum("delete_level_comment")]
|
||||
DeleteLevelComment,
|
||||
DeleteLevelComment = 8,
|
||||
|
||||
[XmlEnum("upload_photo")]
|
||||
UploadPhoto,
|
||||
UploadPhoto = 9,
|
||||
|
||||
[XmlEnum("publish_level")]
|
||||
PublishLevel,
|
||||
PublishLevel = 10,
|
||||
|
||||
[XmlEnum("unpublish_level")]
|
||||
UnpublishLevel,
|
||||
UnpublishLevel = 11,
|
||||
|
||||
[XmlEnum("score")]
|
||||
Score,
|
||||
Score = 12,
|
||||
|
||||
[XmlEnum("news_post")]
|
||||
NewsPost,
|
||||
NewsPost = 13,
|
||||
|
||||
[XmlEnum("mm_pick_level")]
|
||||
MMPickLevel,
|
||||
MMPickLevel = 14,
|
||||
|
||||
[XmlEnum("dpad_rate_level")]
|
||||
DpadRateLevel,
|
||||
DpadRateLevel = 15,
|
||||
|
||||
[XmlEnum("review_level")]
|
||||
ReviewLevel,
|
||||
ReviewLevel = 16,
|
||||
|
||||
[XmlEnum("comment_on_user")]
|
||||
CommentOnUser,
|
||||
CommentOnUser = 17,
|
||||
|
||||
[XmlEnum("create_playlist")]
|
||||
CreatePlaylist,
|
||||
CreatePlaylist = 18,
|
||||
|
||||
[XmlEnum("heart_playlist")]
|
||||
HeartPlaylist,
|
||||
HeartPlaylist = 19,
|
||||
|
||||
[XmlEnum("add_level_to_playlist")]
|
||||
AddLevelToPlaylist,
|
||||
AddLevelToPlaylist = 20,
|
||||
}
|
|
@ -4,14 +4,13 @@ using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
|||
namespace LBPUnion.ProjectLighthouse.Types.Entities.Activity;
|
||||
|
||||
/// <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>
|
||||
public class LevelActivityEntity : ActivityEntity
|
||||
{
|
||||
[Column("SlotId")]
|
||||
public int SlotId { get; set; }
|
||||
|
||||
[ForeignKey(nameof(SlotId))]
|
||||
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>
|
||||
/// Supported event types: NewsPost
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Supported event types: CreatePlaylist, HeartPlaylist, AddLevelToPlaylist
|
||||
/// Supported event types: CreatePlaylist, HeartPlaylist
|
||||
/// </summary>
|
||||
public class PlaylistActivityEntity : ActivityEntity
|
||||
{
|
||||
[Column("PlaylistId")]
|
||||
public int PlaylistId { get; set; }
|
||||
|
||||
[ForeignKey(nameof(PlaylistId))]
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Supported event types: DpadRateLevel, ReviewLevel, RateLevel, TagLevel
|
||||
/// </summary>
|
||||
public class ReviewActivityEntity : ActivityEntity
|
||||
{
|
||||
public int ReviewId { get; set; }
|
||||
|
||||
[ForeignKey(nameof(ReviewId))]
|
||||
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.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Serialization;
|
||||
|
@ -19,10 +20,19 @@ namespace LBPUnion.ProjectLighthouse.Types.Serialization.Activity.Events;
|
|||
[XmlInclude(typeof(GameScoreEvent))]
|
||||
[XmlInclude(typeof(GameHeartLevelEvent))]
|
||||
[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
|
||||
{
|
||||
[XmlIgnore]
|
||||
private int UserId { get; set; }
|
||||
protected int UserId { get; set; }
|
||||
|
||||
[XmlAttribute("type")]
|
||||
public EventType Type { get; set; }
|
||||
|
@ -31,100 +41,190 @@ public class GameEvent : ILbpSerializable, INeedsPreparationForSerialization
|
|||
public long Timestamp { get; set; }
|
||||
|
||||
[XmlElement("actor")]
|
||||
[DefaultValue(null)]
|
||||
public string Username { get; set; }
|
||||
|
||||
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);
|
||||
if (user == null) return;
|
||||
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();
|
||||
|
||||
// ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault
|
||||
// Events with Count need special treatment
|
||||
switch (group.Key)
|
||||
List<IGrouping<EventType, ActivityDto>> typeGroups = activities.GroupBy(g => g.Activity.Type).ToList();
|
||||
foreach (IGrouping<EventType, ActivityDto> typeGroup in typeGroups)
|
||||
{
|
||||
case EventType.PlayLevel:
|
||||
// ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault
|
||||
// Events with Count need special treatment
|
||||
switch (typeGroup.Key)
|
||||
{
|
||||
if (group.First() is not LevelActivityEntity levelActivity) break;
|
||||
|
||||
events.Add(new GamePlayLevelEvent
|
||||
case EventType.PlayLevel:
|
||||
{
|
||||
Slot = new ReviewSlot
|
||||
{
|
||||
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;
|
||||
if (typeGroup.First().Activity is not LevelActivityEntity levelActivity) break;
|
||||
|
||||
events.Add(new GamePublishLevelEvent
|
||||
{
|
||||
Slot = new ReviewSlot
|
||||
events.Add(new GamePlayLevelEvent
|
||||
{
|
||||
SlotId = levelActivity.SlotId,
|
||||
},
|
||||
Count = group.Count(),
|
||||
UserId = levelActivity.UserId,
|
||||
Timestamp = levelActivity.Timestamp.ToUnixTimeMilliseconds(),
|
||||
Type = levelActivity.Type,
|
||||
});
|
||||
break;
|
||||
Slot = new ReviewSlot
|
||||
{
|
||||
SlotId = levelActivity.SlotId,
|
||||
},
|
||||
Count = typeGroup.Count(),
|
||||
UserId = levelActivity.UserId,
|
||||
Timestamp = levelActivity.Timestamp.ToUnixTimeMilliseconds(),
|
||||
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();
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
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
|
||||
{
|
||||
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(),
|
||||
};
|
||||
gameEvent.UserId = activity.UserId;
|
||||
gameEvent.Type = activity.Type;
|
||||
gameEvent.Timestamp = activity.Timestamp.ToUnixTimeMilliseconds();
|
||||
gameEvent.UserId = activity.Activity.UserId;
|
||||
gameEvent.Type = activity.Activity.Type;
|
||||
gameEvent.Timestamp = activity.Activity.Timestamp.ToUnixTimeMilliseconds();
|
||||
return gameEvent;
|
||||
}
|
||||
}
|
|
@ -40,4 +40,15 @@ public class GameHeartLevelEvent : GameEvent
|
|||
|
||||
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")]
|
||||
[DefaultValue(null)]
|
||||
public ReviewSlot SlotId { get; set; }
|
||||
public ReviewSlot Slot { get; set; }
|
||||
|
||||
[XmlElement("user_in_photo")]
|
||||
public List<string> PhotoParticipants { get; set; }
|
||||
|
@ -40,6 +40,6 @@ public class GamePhotoUploadEvent : GameEvent
|
|||
SlotEntity slot = await database.Slots.FindAsync(photo.SlotId);
|
||||
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 LBPUnion.ProjectLighthouse.Database;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
|
@ -12,7 +13,7 @@ public class GamePublishLevelEvent : GameEvent
|
|||
public ReviewSlot Slot { get; set; }
|
||||
|
||||
[XmlElement("republish")]
|
||||
public bool IsRepublish { get; set; }
|
||||
public int IsRepublish { get; set; }
|
||||
|
||||
[XmlElement("count")]
|
||||
public int Count { get; set; }
|
||||
|
@ -26,6 +27,7 @@ public class GamePublishLevelEvent : GameEvent
|
|||
|
||||
this.Slot = ReviewSlot.CreateFromEntity(slot);
|
||||
// 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.Xml.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Database;
|
||||
|
@ -9,7 +10,7 @@ namespace LBPUnion.ProjectLighthouse.Types.Serialization.Activity.Events;
|
|||
|
||||
public class GameReviewEvent : GameEvent
|
||||
{
|
||||
[XmlElement("slot_id")]
|
||||
[XmlElement("object_slot_id")]
|
||||
public ReviewSlot Slot { get; set; }
|
||||
|
||||
[XmlElement("review_id")]
|
||||
|
@ -21,9 +22,13 @@ public class GameReviewEvent : GameEvent
|
|||
|
||||
public new async Task PrepareSerialization(DatabaseContext database)
|
||||
{
|
||||
await base.PrepareSerialization(database);
|
||||
|
||||
ReviewEntity review = await database.Reviews.FindAsync(this.ReviewId);
|
||||
if (review == null) return;
|
||||
|
||||
this.ReviewTimestamp = this.Timestamp;
|
||||
|
||||
SlotEntity slot = await database.Slots.FindAsync(review.SlotId);
|
||||
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.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Database;
|
||||
using LBPUnion.ProjectLighthouse.Extensions;
|
||||
using LBPUnion.ProjectLighthouse.Types.Activity;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Activity;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.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.User;
|
||||
using LBPUnion.ProjectLighthouse.Types.Users;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Types.Serialization.Activity;
|
||||
|
||||
|
@ -23,10 +26,16 @@ namespace LBPUnion.ProjectLighthouse.Types.Serialization.Activity;
|
|||
public class GameStream : ILbpSerializable, INeedsPreparationForSerialization
|
||||
{
|
||||
[XmlIgnore]
|
||||
private List<int> SlotIds { get; set; }
|
||||
public List<int> SlotIds { get; set; }
|
||||
|
||||
[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]
|
||||
private int TargetUserId { get; set; }
|
||||
|
@ -42,84 +51,85 @@ public class GameStream : ILbpSerializable, INeedsPreparationForSerialization
|
|||
|
||||
[XmlArray("groups")]
|
||||
[XmlArrayItem("group")]
|
||||
[DefaultValue(null)]
|
||||
public List<GameStreamGroup> Groups { get; set; }
|
||||
|
||||
[XmlArray("slots")]
|
||||
[XmlArrayItem("slot")]
|
||||
[DefaultValue(null)]
|
||||
public List<SlotBase> Slots { get; set; }
|
||||
|
||||
[XmlArray("users")]
|
||||
[XmlArrayItem("user")]
|
||||
[DefaultValue(null)]
|
||||
public List<GameUser> Users { get; set; }
|
||||
|
||||
[XmlArray("playlists")]
|
||||
[XmlArrayItem("playlist")]
|
||||
[DefaultValue(null)]
|
||||
public List<GamePlaylist> Playlists { get; set; }
|
||||
|
||||
[XmlArray("news")]
|
||||
[XmlArrayItem("item")]
|
||||
public List<object> News { get; set; }
|
||||
//TODO implement lbp1 and lbp2 news objects
|
||||
[DefaultValue(null)]
|
||||
public List<GameNewsObject> News { get; set; }
|
||||
|
||||
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>();
|
||||
foreach (int slotId in this.SlotIds)
|
||||
List<TResult> results = new();
|
||||
if (ids.Count <= 0) return null;
|
||||
foreach (int id in ids)
|
||||
{
|
||||
SlotEntity slot = await database.Slots.FindAsync(slotId);
|
||||
if (slot == null) continue;
|
||||
TFrom entity = await database.Set<TFrom>().FindAsync(id);
|
||||
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.Users = new List<GameUser>();
|
||||
foreach (int userId in this.UserIds)
|
||||
{
|
||||
UserEntity user = await database.Users.FindAsync(userId);
|
||||
if (user == null) continue;
|
||||
|
||||
this.Users.Add(GameUser.CreateFromEntity(user, this.TargetGame));
|
||||
}
|
||||
}
|
||||
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.Playlists = await LoadEntities<PlaylistEntity, GamePlaylist>(this.PlaylistIds, GamePlaylist.CreateFromEntity);
|
||||
this.News = await LoadEntities<WebsiteAnnouncementEntity, GameNewsObject>(this.NewsIds, GameNewsObject.CreateFromEntity);
|
||||
}
|
||||
|
||||
public static async Task<GameStream> CreateFromEntityResult
|
||||
(
|
||||
DatabaseContext database,
|
||||
GameTokenEntity token,
|
||||
List<IGrouping<ActivityGroup, ActivityEntity>> results,
|
||||
long startTimestamp,
|
||||
long endTimestamp
|
||||
)
|
||||
public static GameStream CreateFromGroups
|
||||
(GameTokenEntity token, List<OuterActivityGroup> groups, 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()
|
||||
{
|
||||
TargetUserId = token.UserId,
|
||||
TargetGame = token.GameVersion,
|
||||
StartTimestamp = startTimestamp,
|
||||
EndTimestamp = endTimestamp,
|
||||
SlotIds = slotIds,
|
||||
UserIds = userIds,
|
||||
Groups = new List<GameStreamGroup>(),
|
||||
SlotIds = groups.GetIds(ActivityGroupType.Level),
|
||||
UserIds = groups.GetIds(ActivityGroupType.User),
|
||||
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;
|
||||
|
|
|
@ -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.Linq;
|
||||
using System.Xml.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Extensions;
|
||||
using LBPUnion.ProjectLighthouse.Types.Activity;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Activity;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization.Activity.Events;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization.Review;
|
||||
|
||||
|
@ -19,6 +19,8 @@ namespace LBPUnion.ProjectLighthouse.Types.Serialization.Activity;
|
|||
/// </summary>
|
||||
[XmlInclude(typeof(GameUserStreamGroup))]
|
||||
[XmlInclude(typeof(GameSlotStreamGroup))]
|
||||
[XmlInclude(typeof(GamePlaylistStreamGroup))]
|
||||
[XmlInclude(typeof(GameNewsStreamGroup))]
|
||||
public class GameStreamGroup : ILbpSerializable
|
||||
{
|
||||
[XmlAttribute("type")]
|
||||
|
@ -35,46 +37,62 @@ public class GameStreamGroup : ILbpSerializable
|
|||
[XmlArray("events")]
|
||||
[XmlArrayItem("event")]
|
||||
[DefaultValue(null)]
|
||||
// ReSharper disable once MemberCanBePrivate.Global
|
||||
// (the serializer can't see this if it's private)
|
||||
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
|
||||
{
|
||||
ActivityGroupType.Level => new GameSlotStreamGroup
|
||||
ActivityGroupType.Level or ActivityGroupType.TeamPick => new GameSlotStreamGroup
|
||||
{
|
||||
Slot = new ReviewSlot
|
||||
{
|
||||
SlotId = group.Key.TargetId,
|
||||
SlotId = targetId,
|
||||
},
|
||||
},
|
||||
ActivityGroupType.User => new GameUserStreamGroup
|
||||
{
|
||||
UserId = group.Key.TargetId,
|
||||
UserId = targetId,
|
||||
},
|
||||
ActivityGroupType.Playlist => new GamePlaylistStreamGroup
|
||||
{
|
||||
PlaylistId = targetId,
|
||||
},
|
||||
ActivityGroupType.News => new GameNewsStreamGroup
|
||||
{
|
||||
NewsId = targetId,
|
||||
},
|
||||
_ => new GameStreamGroup(),
|
||||
};
|
||||
gameGroup.Timestamp = new DateTimeOffset(group.Select(a => a.Timestamp).MaxBy(a => a)).ToUnixTimeMilliseconds();
|
||||
gameGroup.Type = type;
|
||||
|
||||
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(),
|
||||
},
|
||||
};
|
||||
|
||||
groupAction(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")]
|
||||
public string Name { get; set; } = "";
|
||||
|
||||
[DefaultValue("")]
|
||||
[XmlElement("description")]
|
||||
public string Description { get; set; } = "";
|
||||
|
||||
|
@ -62,6 +61,7 @@ public class GamePlaylist : ILbpSerializable, INeedsPreparationForSerialization
|
|||
Username = authorUsername,
|
||||
};
|
||||
|
||||
this.LevelCount = this.SlotIds.Length;
|
||||
this.Hearts = await database.HeartedPlaylists.CountAsync(h => h.HeartedPlaylistId == this.PlaylistId);
|
||||
this.PlaylistQuota = ServerConfiguration.Instance.UserGeneratedContentLimits.ListsQuota;
|
||||
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.Threading.Tasks;
|
||||
using System.Xml.Serialization;
|
||||
|
@ -185,15 +187,21 @@ public class GameUser : ILbpSerializable, INeedsPreparationForSerialization
|
|||
|
||||
int entitledSlots = ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots + stats.BonusSlots;
|
||||
|
||||
IQueryable<SlotEntity> SlotCount(GameVersion version)
|
||||
{
|
||||
return database.Slots.Where(s => s.CreatorId == this.UserId && s.GameVersion == version);
|
||||
}
|
||||
Dictionary<GameVersion, int> slotsByGame = await database.Slots.Where(s => s.CreatorId == this.UserId && !s.CrossControllerRequired)
|
||||
.GroupBy(s => s.GameVersion)
|
||||
.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)
|
||||
{
|
||||
this.Lbp2EntitledSlots = entitledSlots;
|
||||
this.Lbp2UsedSlots = await SlotCount(GameVersion.LittleBigPlanetVita).CountAsync();
|
||||
this.Lbp2UsedSlots = GetSlotCount(GameVersion.LittleBigPlanetVita);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -201,9 +209,9 @@ public class GameUser : ILbpSerializable, INeedsPreparationForSerialization
|
|||
this.Lbp2EntitledSlots = entitledSlots;
|
||||
this.CrossControlEntitledSlots = entitledSlots;
|
||||
this.Lbp3EntitledSlots = entitledSlots;
|
||||
this.Lbp1UsedSlots = await SlotCount(GameVersion.LittleBigPlanet1).CountAsync();
|
||||
this.Lbp2UsedSlots = await SlotCount(GameVersion.LittleBigPlanet2).CountAsync(s => !s.CrossControllerRequired);
|
||||
this.Lbp3UsedSlots = await SlotCount(GameVersion.LittleBigPlanet3).CountAsync();
|
||||
this.Lbp1UsedSlots = GetSlotCount(GameVersion.LittleBigPlanet1);
|
||||
this.Lbp2UsedSlots = GetSlotCount(GameVersion.LittleBigPlanet2);
|
||||
this.Lbp3UsedSlots = GetSlotCount(GameVersion.LittleBigPlanet3);
|
||||
|
||||
this.Lbp1FreeSlots = this.Lbp1EntitledSlots - this.Lbp1UsedSlots;
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue