Skip to content

Commit

Permalink
Implement automatic filter query documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
turecross321 committed Oct 31, 2023
1 parent 4cbfbc9 commit 62ec794
Show file tree
Hide file tree
Showing 37 changed files with 270 additions and 165 deletions.
8 changes: 4 additions & 4 deletions SoundShapesServer/Database/GameDatabaseContext.Leaderboard.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,14 @@ public void RemoveLeaderboardEntry(LeaderboardEntry entry)
return _realm.All<LeaderboardEntry>().FirstOrDefault(e => e.Id == objectId);
}

public (LeaderboardEntry[], int) GetPaginatedLeaderboardEntries(LeaderboardOrderType order, bool descending, LeaderboardFilters filters, int from, int count, GameUser? accessor)
public (LeaderboardEntry[], int) GetPaginatedLeaderboardEntries(GameLevel level, LeaderboardOrderType order, bool descending, LeaderboardFilters filters, int from, int count, GameUser? accessor)
{
return GetLeaderboardEntries(order, descending, filters).Paginate(from, count);
return GetLeaderboardEntries(level, order, descending, filters).Paginate(from, count);
}

public IQueryable<LeaderboardEntry> GetLeaderboardEntries(LeaderboardOrderType order, bool descending,
public IQueryable<LeaderboardEntry> GetLeaderboardEntries(GameLevel level, LeaderboardOrderType order, bool descending,
LeaderboardFilters filters)
{
return _realm.All<LeaderboardEntry>().FilterLeaderboard(filters).OrderLeaderboard(order, descending);
return _realm.All<LeaderboardEntry>().FilterLeaderboard(level, filters).OrderLeaderboard(order, descending);
}
}
13 changes: 11 additions & 2 deletions SoundShapesServer/Database/GameDatabaseProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ namespace SoundShapesServer.Database;

public class GameDatabaseProvider : RealmDatabaseProvider<GameDatabaseContext>
{
protected override ulong SchemaVersion => 81;
protected override ulong SchemaVersion => 82;

protected override List<Type> SchemaTypes => new()
{
Expand Down Expand Up @@ -96,7 +96,6 @@ protected override void Migrate(Migration migration, ulong oldVersion)
for (int i = 0; i < newLevels.Count(); i++)
{
Console.WriteLine($"Getting hasUfo & hasFirefly data from levels... ({i}/{newLevels.Count()})");
dynamic oldLevel = oldLevels.ElementAt(i);
GameLevel newLevel = newLevels.ElementAt(i);

if (!File.Exists(newLevel.LevelFilePath))
Expand All @@ -110,6 +109,16 @@ protected override void Migrate(Migration migration, ulong oldVersion)
newLevel.HasFirefly = ssLevel.EntitiesB.Any(e => e.EntityType == "Platformer_EntityPacks_GameStuff_FireflyCheckpoint");
}
}

if (oldVersion < 82)
{
for (int i = 0; i < newLevels.Count(); i++)
{
Console.WriteLine($"Setting level upload platform to unknown... ({i}/{newLevels.Count()})");
GameLevel newLevel = newLevels.ElementAt(i);
newLevel.UploadPlatform = PlatformType.Unknown;
}
}

IQueryable<dynamic> oldAlbums = migration.OldRealm.DynamicApi.All("GameAlbum");
IQueryable<GameAlbum> newAlbums = migration.NewRealm.All<GameAlbum>();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace SoundShapesServer.Documentation.Attributes;

[AttributeUsage(AttributeTargets.Property)]
public class DocPropertyQueryAttribute : Attribute
{
public DocPropertyQueryAttribute(string parameterName, string summary)
{
ParameterName = parameterName;
Summary = summary;
}

public string ParameterName { get; }

public string Summary { get; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System.Reflection;
using AttribDoc;
using AttribDoc.Attributes;

namespace SoundShapesServer.Documentation.Attributes;

[AttributeUsage(AttributeTargets.Method)]
public class DocUsesFilterAttribute<T> : DocAttribute
{
public override void AddDataToRouteDocumentation(MethodInfo method, Route route)
{
foreach (PropertyInfo property in typeof(T).GetProperties())
{
DocPropertyQueryAttribute? attribute = property.GetCustomAttribute<DocPropertyQueryAttribute>();
if (attribute != null)
{
route.Parameters.Add(new Parameter(attribute.ParameterName, ParameterType.Query, attribute.Summary));
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,23 +77,14 @@ public ApiResponse<ApiPunishmentResponse> RevokePunishment(RequestContext contex

[ApiEndpoint("punishments")]
[DocUsesPageData]
[DocUsesFilter<PunishmentFilters>]
[MinimumPermissions(PermissionsType.Moderator)]
[DocSummary("Lists punishments.")]
public ApiListResponse<ApiPunishmentResponse> GetPunishments(RequestContext context, GameDatabaseContext database, GameUser user)
{
(int from, int count, bool descending) = context.GetPageData();

GameUser? author = context.QueryString["author"].ToUser(database);
GameUser? recipient = context.QueryString["recipient"].ToUser(database);
bool? revoked = context.QueryString["revoked"].ToBool();

PunishmentFilters filters = new PunishmentFilters
{
Author = author,
Recipient = recipient,
Revoked = revoked
};

PunishmentFilters filters = context.GetFilters<PunishmentFilters>(database);
(Punishment[] punishments, int totalPunishments) = database.GetPaginatedPunishments(PunishmentOrderType.CreationDate, descending, filters, from, count);

return new ApiListResponse<ApiPunishmentResponse>(punishments.Select(p=>new ApiPunishmentResponse(p)), totalPunishments);
Expand Down
5 changes: 3 additions & 2 deletions SoundShapesServer/Endpoints/Api/Events/ApiEventsEndpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,13 @@ public class ApiEventsEndpoint : EndpointGroup
{
[ApiEndpoint("events"), Authentication(false)]
[DocUsesPageData]
[DocUsesFilter<EventFilters>]
[DocSummary("Lists events.")]
public ApiListResponse<ApiEventResponse> GetEvents(RequestContext context, GameDatabaseContext database, GameUser? user)
{
(int from, int count, bool descending) = context.GetPageData();

EventFilters filters = context.GetEventFilters(database);
EventFilters filters = context.GetFilters<EventFilters>(database);
EventOrderType orderType = context.GetEventOrder();

(GameEvent[] events, int totalEvents) = database.GetPaginatedEvents(orderType, descending, filters, from, count, user);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public class ApiLeaderboardEndpoints : EndpointGroup
{
[ApiEndpoint("levels/id/{levelId}/leaderboard"), Authentication(false)]
[DocUsesPageData]
[DocUsesFilter<LeaderboardFilters>]
[DocSummary("Retrieves leaderboard of level.")]
[DocError(typeof(ApiNotFoundError), ApiNotFoundError.LevelNotFoundWhen)]
public ApiListResponse<ApiLeaderboardEntryResponse> GetLeaderboard(RequestContext context, GameDatabaseContext database, string levelId, GameUser? user)
Expand All @@ -28,11 +29,7 @@ public ApiListResponse<ApiLeaderboardEntryResponse> GetLeaderboard(RequestContex
if (level == null)
return ApiNotFoundError.LevelNotFound;

GameUser? byUser = context.QueryString["byUser"].ToUser(database);
bool? obsolete = context.QueryString["obsolete"].ToBool() ?? false;
bool? completed = context.QueryString["completed"].ToBool() ?? true;
string? orderString = context.QueryString["orderBy"];

LeaderboardOrderType order = orderString switch
{
"score" => LeaderboardOrderType.Score,
Expand All @@ -42,8 +39,8 @@ public ApiListResponse<ApiLeaderboardEntryResponse> GetLeaderboard(RequestContex
_ => LeaderboardOrderType.Score
};

LeaderboardFilters filters = new LeaderboardFilters{OnLevel = level, ByUser = byUser, Completed = completed, Obsolete = obsolete};
(LeaderboardEntry[] paginatedEntries, int totalEntries) = database.GetPaginatedLeaderboardEntries(order, descending, filters, from, count, user);
LeaderboardFilters filters = context.GetFilters<LeaderboardFilters>(database);
(LeaderboardEntry[] paginatedEntries, int totalEntries) = database.GetPaginatedLeaderboardEntries(level, order, descending, filters, from, count, user);

return new ApiListResponse<ApiLeaderboardEntryResponse>(paginatedEntries.Select(e =>
new ApiLeaderboardEntryResponse(e, order, filters)), totalEntries);
Expand Down
10 changes: 2 additions & 8 deletions SoundShapesServer/Endpoints/Api/Levels/ApiDailyLevelEndpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,13 @@ public class ApiDailyLevelEndpoint : EndpointGroup
{
[ApiEndpoint("daily"), Authentication(false)]
[DocUsesPageData]
[DocUsesFilter<DailyLevelFilters>]
[DocSummary("Lists levels that have been picked as daily levels.")]
public ApiListResponse<ApiDailyLevelResponse> GetDailyLevelObjects(RequestContext context, GameDatabaseContext database)
{
(int from, int count, bool descending) = context.GetPageData();

DateTimeOffset? date = context.QueryString["date"].ToDateFromUnix();
bool? latestDate = context.QueryString["latestDate"].ToBool();

DailyLevelFilters filters = new DailyLevelFilters
{
Date = date,
LatestDate = latestDate
};
DailyLevelFilters filters = context.GetFilters<DailyLevelFilters>(database);

string? orderString = context.QueryString["orderBy"];
DailyLevelOrderType order = orderString switch
Expand Down
3 changes: 2 additions & 1 deletion SoundShapesServer/Endpoints/Api/Levels/ApiLevelEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,13 @@ public class ApiLevelEndpoints: EndpointGroup
{
[ApiEndpoint("levels"), Authentication(false)]
[DocUsesPageData]
[DocUsesFilter<LevelFilters>]
[DocSummary("Lists levels.")]
public ApiListResponse<ApiLevelBriefResponse> GetLevels(RequestContext context, GameDatabaseContext database, GameUser? user)
{
(int from, int count, bool descending) = context.GetPageData();

LevelFilters filters = context.GetLevelFilters(database);
LevelFilters filters = context.GetFilters<LevelFilters>(database);
LevelOrderType order = context.GetLevelOrderType();

(GameLevel[] levels, int levelCount) = database.GetPaginatedLevels(order, descending, filters, from, count, user);
Expand Down
11 changes: 2 additions & 9 deletions SoundShapesServer/Endpoints/Api/News/ApiNewsEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,21 +29,14 @@ public ApiResponse<ApiNewsEntryResponse> NewsEntryWithId(RequestContext context,

[ApiEndpoint("news"), Authentication(false)]
[DocUsesPageData]
[DocUsesFilter<NewsFilters>]
[DocSummary("Lists news.")]
public ApiListResponse<ApiNewsEntryResponse> News(RequestContext context, GameDatabaseContext database)
{
(int from, int count, bool descending) = context.GetPageData();

string? orderString = context.QueryString["orderBy"];

string? language = context.QueryString["language"];
List<GameUser>? authors = context.QueryString["authors"].ToUsers(database);

NewsFilters filters = new NewsFilters
{
Language = language,
Authors = authors?.ToArray()
};
NewsFilters filters = context.GetFilters<NewsFilters>(database);

NewsOrderType order = orderString switch
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,27 +49,14 @@ public ApiResponse<ApiReportResponse> GetReport(RequestContext context, GameData

[ApiEndpoint("reports")]
[DocUsesPageData]
[DocUsesFilter<ReportFilters>]
[MinimumPermissions(PermissionsType.Moderator)]
[DocSummary("Lists reports.")]
public ApiListResponse<ApiReportResponse> GetReports(RequestContext context, GameDatabaseContext database, GameUser user, string id)
{
(int from, int count, bool descending) = context.GetPageData();

ReportContentType? contentType = context.QueryString["contentType"].ToEnum<ReportContentType>();
ReportReasonType? reasonType = context.QueryString["reasonType"].ToEnum<ReportReasonType>();
GameUser? contentUser = context.QueryString["userId"].ToUser(database);
GameLevel? contentLevel = context.QueryString["levelId"].ToLevel(database);
LeaderboardEntry? contentLeaderboardEntry =
context.QueryString["leaderboardEntryId"].ToLeaderboardEntry(database);

ReportFilters filters = new ReportFilters
{
ContentType = contentType,
ReasonType = reasonType,
ContentUser = contentUser,
ContentLevel = contentLevel,
ContentLeaderboardEntry = contentLeaderboardEntry
};
ReportFilters filters = context.GetFilters<ReportFilters>(database);

(Report[] reports, int totalReports) = database.GetPaginatedReports(ReportOrderType.Date, descending, filters, from, count);
return new ApiListResponse<ApiReportResponse>(reports.Select(r=>new ApiReportResponse(r)), totalReports);
Expand Down
6 changes: 3 additions & 3 deletions SoundShapesServer/Endpoints/Game/EventsEndpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ public class EventsEndpoint : EndpointGroup

[GameEndpoint("~identity:{id}/~stream:news.page")]
[GameEndpoint("~index:activity.page")]
public ListResponse<EventResponse> GetEvents(RequestContext context, GameDatabaseContext database, GameUser? user)
public ListResponse<EventResponse> GetEvents(RequestContext context, GameDatabaseContext database, GameUser user)
{
(int from, int count, bool descending) = context.GetPageData();

EventFilters filters = context.GetEventFilters(database);
filters.EventTypes ??= _gameEventTypes.ToList();
EventFilters filters = context.GetFilters<EventFilters>(database);
filters.EventTypes ??= _gameEventTypes.ToIntArray();
EventOrderType order = context.GetEventOrder();

(GameEvent[] events, int totalEvents) = database.GetPaginatedEvents(order, descending, filters, from, count, user);
Expand Down
8 changes: 4 additions & 4 deletions SoundShapesServer/Endpoints/Game/LeaderboardEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@ public Response SubmitScore(RequestContext context, GameDatabaseContext database

LeaderboardOrderType order = LeaderboardOrderType.Score;
const bool descending = false;
LeaderboardFilters filters = new LeaderboardFilters{OnLevel = level, Completed = true, Obsolete = false};
LeaderboardFilters filters = new LeaderboardFilters{Completed = true, Obsolete = false};

(LeaderboardEntry[] paginatedEntries, int totalEntries) = database.GetPaginatedLeaderboardEntries(order, descending, filters, from, count, user);
(LeaderboardEntry[] paginatedEntries, int totalEntries) = database.GetPaginatedLeaderboardEntries(level, order, descending, filters, from, count, user);

return new ListResponse<LeaderboardEntryResponse>(
paginatedEntries.Select(t => new LeaderboardEntryResponse(t, order, filters)), totalEntries, from, count);
Expand All @@ -88,9 +88,9 @@ public Response SubmitScore(RequestContext context, GameDatabaseContext database

const LeaderboardOrderType order = LeaderboardOrderType.Score;
const bool descending = false;
LeaderboardFilters filters = new LeaderboardFilters{OnLevel = level, ByUser = user, Completed = true, Obsolete = false};
LeaderboardFilters filters = new LeaderboardFilters{ByUser = user, Completed = true, Obsolete = false};

(LeaderboardEntry[] paginatedEntries, int _) = database.GetPaginatedLeaderboardEntries(order, descending, filters, 0, 1, user);
(LeaderboardEntry[] paginatedEntries, int _) = database.GetPaginatedLeaderboardEntries(level, order, descending, filters, 0, 1, user);
return paginatedEntries.Select(e=> new LeaderboardEntryResponse(e, order, filters)).ToArray();
}
}
2 changes: 1 addition & 1 deletion SoundShapesServer/Endpoints/Game/Levels/LevelEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ public class LevelEndpoints : EndpointGroup
break;
}

filters ??= context.GetLevelFilters(database);
filters ??= context.GetFilters<LevelFilters>(database);
order ??= context.GetLevelOrderType();

(GameLevel[] levels, int totalLevels) = database.GetPaginatedLevels((LevelOrderType)order, descending, filters, from, count, user);
Expand Down
10 changes: 10 additions & 0 deletions SoundShapesServer/Extensions/EnumArrayExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace SoundShapesServer.Extensions;

public static class EnumArrayExtensions
{
public static int[] ToIntArray<T>(this T[] array) where T : struct, Enum
{
List<int> intList = array.Select(enumValue => (int)Enum.ToObject(typeof(T), enumValue)).ToList();
return intList.ToArray();
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
using SoundShapesServer.Types.Leaderboard;
using SoundShapesServer.Types.Levels;

namespace SoundShapesServer.Extensions.Queryable;

public static class LeaderboardQueryableExtensions
{
public static IQueryable<LeaderboardEntry> FilterLeaderboard(this IQueryable<LeaderboardEntry> entries,
public static IQueryable<LeaderboardEntry> FilterLeaderboard(this IQueryable<LeaderboardEntry> entries, GameLevel level,
LeaderboardFilters filters)
{
entries = entries.Where(e => e.Level == filters.OnLevel);
entries = entries.Where(e => e.Level == level);

if (filters.ByUser != null)
{
Expand Down
23 changes: 11 additions & 12 deletions SoundShapesServer/Extensions/Queryable/LevelQueryableExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ public static class LevelQueryableExtensions
{
public static IQueryable<GameLevel> FilterLevels(this IQueryable<GameLevel> levels, GameDatabaseContext database, LevelFilters filters, GameUser? accessor)
{
if (filters.AnyCompletions != null)
levels = levels.Where(l => l.UniqueCompletionsCount > 0 == filters.AnyCompletions);

if (filters.AnyCompletions == true)
levels = levels.Where(l => l.UniqueCompletionsCount > 0);
else if (filters.AnyCompletions == false)
levels = levels.Where(l => l.UniqueCompletionsCount == 0);

if (filters.CreatedBefore != null)
levels = levels.Where(l => l.CreationDate <= filters.CreatedBefore);

Expand Down Expand Up @@ -122,22 +124,19 @@ public static IQueryable<GameLevel> FilterLevels(this IQueryable<GameLevel> leve

if (filters.UploadPlatforms != null)
{
IEnumerable<GameLevel> tempLevels = new List<GameLevel>();

// ReSharper disable once LoopCanBeConvertedToQuery
foreach (PlatformType platformType in filters.UploadPlatforms)
IQueryable<GameLevel> tempLevels = new List<GameLevel>().AsQueryable();
foreach (int platform in filters.UploadPlatforms)
{
tempLevels = tempLevels.Concat(levels.Where(e=> e._UploadPlatform == (int)platformType));
IQueryable<GameLevel> levelsOnPlatform = levels.Where(l => l._UploadPlatform == platform);
tempLevels = tempLevels.Concat(levelsOnPlatform);
}

levels = tempLevels.AsQueryable();
levels = tempLevels;
}

// Automatically remove unlisted and private levels from results
if ((accessor?.PermissionsType ?? PermissionsType.Default) < PermissionsType.Moderator)
{
IQueryable<GameLevel> nonPublicLevels = levels.Where(l => l.Author != accessor && l._Visibility != (int)LevelVisibility.Public);
levels = levels.AsEnumerable().Except(nonPublicLevels).AsQueryable();
levels = levels.Where(l => l._Visibility == (int)LevelVisibility.Public || l.Author == accessor);
}

return levels;
Expand Down
Loading

0 comments on commit 62ec794

Please sign in to comment.