mirror of
https://github.com/JogmansonReal/Radicade.git
synced 2026-10-26 23:49:02 -07:00
Radicade Public Code
This commit is contained in:
@@ -0,0 +1,633 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using RadicadeServer.DB;
|
||||
using RadicadeServer.Extensions;
|
||||
using RadicadeServer.RecNet.Leaderboards;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace RadicadeServer.Leaderboard;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static class LeaderboardStore
|
||||
{
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private const int FirstGlobalStatChannel = 1000;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static async Task<object?> SubmitScoreAsync(IDbContextFactory<RecDBContext> dbFactory, string? rawBody, long authPlayerId, long queryPlayerId)
|
||||
{
|
||||
var parsed = ParseStatSubmission(rawBody);
|
||||
if (parsed == null) return null;
|
||||
|
||||
long playerId = parsed.Value.PlayerIdFromBody > 0 ? parsed.Value.PlayerIdFromBody
|
||||
: authPlayerId > 0 ? authPlayerId
|
||||
: queryPlayerId;
|
||||
if (playerId <= 0)
|
||||
{
|
||||
return new
|
||||
{
|
||||
Success = false,
|
||||
Reason = "No player identity"
|
||||
};
|
||||
}
|
||||
|
||||
await using var db = await dbFactory.CreateDbContextAsync();
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
var added = 0;
|
||||
foreach (var (channel, score, time) in parsed.Value.Stats)
|
||||
{
|
||||
if (score == 0 && time == 0) continue;
|
||||
db.LeaderboardScores.Add(new LeaderboardScore
|
||||
{
|
||||
RoomId = parsed.Value.RoomId,
|
||||
SubRoomId = parsed.Value.SubRoomId,
|
||||
PlayerId = playerId,
|
||||
StatChannel = channel,
|
||||
Score = score,
|
||||
Time = time,
|
||||
SubmittedAt = now
|
||||
});
|
||||
added++;
|
||||
}
|
||||
|
||||
if (added == 0) return new { Success = false, Reason = "Nothing to record" };
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
|
||||
|
||||
var last = parsed.Value.Stats[^1];
|
||||
var board = await LoadBoardAsync(db, parsed.Value.RoomId, last.Channel);
|
||||
int rank = RankBoard(board, false).FindIndex(e => e.PlayerId == playerId) + 1;
|
||||
int total = board.GroupBy(x => x.PlayerId).Count();
|
||||
|
||||
return new
|
||||
{
|
||||
Success = true,
|
||||
PlayerId = playerId,
|
||||
RoomId = parsed.Value.RoomId,
|
||||
StatChannel = last.Channel,
|
||||
StatValue = last.Score,
|
||||
Rank = rank,
|
||||
Total = total
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static async Task<LeaderboardResponse> GetRanksAsync(IDbContextFactory<RecDBContext> dbFactory, GetRanksRequest req)
|
||||
{
|
||||
await using var db = await dbFactory.CreateDbContextAsync();
|
||||
|
||||
var board = await LoadBoardAsync(db, req.RoomId, req.StatChannel);
|
||||
var ranked = RankBoard(board, req.SortAscending);
|
||||
var names = await LoadPlayerNamesAsync(db, BoardPlayerIds(ranked, req.PlayerId));
|
||||
|
||||
int total = ranked.Count;
|
||||
int start = req.RankStart > 0 ? req.RankStart : 1;
|
||||
int end = req.RankEnd >= req.RankStart ? req.RankEnd : Math.Min(total, 100);
|
||||
var slice = ranked
|
||||
.Skip(start - 1)
|
||||
.Take(Math.Max(0, end - start + 1))
|
||||
.Select((e, i) => new LeaderboardEntryResponse
|
||||
{
|
||||
PlayerId = e.PlayerId,
|
||||
PlayerName = names.TryGetValue(e.PlayerId, out var n) ? n : "",
|
||||
Score = e.BestValue,
|
||||
Time = e.BestTime,
|
||||
Rank = start + i
|
||||
})
|
||||
.ToList();
|
||||
|
||||
int myRank = 0;
|
||||
if (req.PlayerId > 0)
|
||||
{
|
||||
var me = ranked.FirstOrDefault(e => e.PlayerId == req.PlayerId);
|
||||
if (me != null) myRank = ranked.IndexOf(me) + 1;
|
||||
}
|
||||
|
||||
if (total == 0 && req.PlayerId > 0)
|
||||
{
|
||||
slice.Add(CreatePlaceholderEntry(req.PlayerId, names));
|
||||
total = 1;
|
||||
myRank = 1;
|
||||
}
|
||||
|
||||
return new LeaderboardResponse { Rows = slice, Total = total, MyRank = myRank };
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static async Task<object> GetPlayerRankAsync(IDbContextFactory<RecDBContext> dbFactory, GetRankRequest req)
|
||||
{
|
||||
await using var db = await dbFactory.CreateDbContextAsync();
|
||||
|
||||
var board = await LoadBoardAsync(db, req.RoomId, req.StatChannel);
|
||||
var ranked = RankBoard(board, req.SortAscending);
|
||||
var names = await LoadPlayerNamesAsync(db, BoardPlayerIds(ranked, req.PlayerId));
|
||||
|
||||
int total = ranked.Count;
|
||||
var entry = ranked.FirstOrDefault(e => e.PlayerId == req.PlayerId);
|
||||
int rank = entry == null ? 0 : ranked.IndexOf(entry) + 1;
|
||||
double score = entry?.BestValue ?? 0;
|
||||
long time = entry?.BestTime ?? 0;
|
||||
string name = names.TryGetValue(req.PlayerId, out var n) ? n : "";
|
||||
|
||||
if (entry == null && req.PlayerId > 0 && total == 0)
|
||||
{
|
||||
rank = 1;
|
||||
total = 1;
|
||||
}
|
||||
|
||||
var row = new LeaderboardEntryResponse
|
||||
{
|
||||
PlayerId = req.PlayerId,
|
||||
PlayerName = name,
|
||||
Score = score,
|
||||
Time = time,
|
||||
Rank = rank
|
||||
};
|
||||
|
||||
return new
|
||||
{
|
||||
row.PlayerId,
|
||||
row.PlayerName,
|
||||
row.Score,
|
||||
row.Time,
|
||||
row.Rank,
|
||||
Total = total,
|
||||
Rows = new List<LeaderboardEntryResponse> { row }
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static async Task<LeaderboardResponse> GetNearbyScoresAsync(IDbContextFactory<RecDBContext> dbFactory, GetNearbyScoresRequest req)
|
||||
{
|
||||
await using var db = await dbFactory.CreateDbContextAsync();
|
||||
|
||||
var window = req.WindowSize > 0 ? req.WindowSize : 10;
|
||||
|
||||
var board = await LoadBoardAsync(db, req.RoomId, req.StatChannel);
|
||||
var ranked = RankBoard(board, req.SortAscending);
|
||||
var names = await LoadPlayerNamesAsync(db, BoardPlayerIds(ranked, req.PlayerId));
|
||||
|
||||
int total = ranked.Count;
|
||||
int myRank = 0;
|
||||
if (req.PlayerId > 0)
|
||||
{
|
||||
var me = ranked.FirstOrDefault(e => e.PlayerId == req.PlayerId);
|
||||
if (me != null) myRank = ranked.IndexOf(me) + 1;
|
||||
}
|
||||
|
||||
|
||||
int start = myRank > 0 ? Math.Max(1, myRank - window / 2) : 1;
|
||||
var slice = ranked
|
||||
.Skip(start - 1)
|
||||
.Take(window)
|
||||
.Select((e, i) => new LeaderboardEntryResponse
|
||||
{
|
||||
PlayerId = e.PlayerId,
|
||||
PlayerName = names.TryGetValue(e.PlayerId, out var n) ? n : "",
|
||||
Score = e.BestValue,
|
||||
Time = e.BestTime,
|
||||
Rank = start + i
|
||||
})
|
||||
.ToList();
|
||||
|
||||
|
||||
if (req.PlayerId > 0 && myRank > 0 && slice.All(e => e.PlayerId != req.PlayerId))
|
||||
{
|
||||
var me = ranked.First(e => e.PlayerId == req.PlayerId);
|
||||
slice.Add(new LeaderboardEntryResponse
|
||||
{
|
||||
PlayerId = me.PlayerId,
|
||||
PlayerName = names.TryGetValue(me.PlayerId, out var n) ? n : "",
|
||||
Score = me.BestValue,
|
||||
Time = me.BestTime,
|
||||
Rank = myRank
|
||||
});
|
||||
}
|
||||
|
||||
if (total == 0 && req.PlayerId > 0)
|
||||
{
|
||||
slice.Add(CreatePlaceholderEntry(req.PlayerId, names));
|
||||
total = 1;
|
||||
myRank = 1;
|
||||
}
|
||||
|
||||
return new LeaderboardResponse { Rows = slice, Total = total, MyRank = myRank };
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private static (long RoomId, long SubRoomId, long PlayerIdFromBody, List<(int Channel, double Score, long Time)> Stats)? ParseStatSubmission(string? rawBody)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(rawBody)) return null;
|
||||
|
||||
JsonElement root;
|
||||
try
|
||||
{
|
||||
using var doc = JsonDocument.Parse(rawBody);
|
||||
root = doc.RootElement.Clone();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
long roomId = 0, subRoomId = 0, playerId = 0;
|
||||
var stats = new List<(int Channel, double Score, long Time)>();
|
||||
|
||||
if (root.ValueKind == JsonValueKind.Object)
|
||||
{
|
||||
roomId = GetLong(root, "RoomId", "roomId", "Room");
|
||||
subRoomId = GetLong(root, "SubRoomId", "subRoomId", "SubRoom");
|
||||
playerId = GetLong(root, "PlayerId", "playerId", "Player");
|
||||
|
||||
var nested = GetArray(root, "Stats", "stats", "Scores", "scores");
|
||||
if (nested != null)
|
||||
{
|
||||
foreach (var item in nested)
|
||||
AddStat(stats, item);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddStat(stats, root);
|
||||
}
|
||||
}
|
||||
else if (root.ValueKind == JsonValueKind.Array)
|
||||
{
|
||||
foreach (var item in root.EnumerateArray())
|
||||
AddStat(stats, item);
|
||||
}
|
||||
|
||||
if (stats.Count == 0) return null;
|
||||
return (roomId, subRoomId, playerId, stats);
|
||||
}
|
||||
|
||||
private static void AddStat(List<(int Channel, double Score, long Time)> stats, JsonElement el)
|
||||
{
|
||||
var channel = GetInt(el, "StatChannel", "statChannel", "Channel", "BoardNumber", "Board", "StatId", "statId");
|
||||
var score = GetDouble(el, "Score", "score", "StatValue", "statValue", "Value", "value", "Wins", "wins", "Count", "count");
|
||||
var time = GetLong(el, "Time", "time", "Milliseconds", "TimeMs");
|
||||
if (score != 0 || time != 0)
|
||||
stats.Add((channel, score, time));
|
||||
}
|
||||
|
||||
internal static List<JsonElement>? GetArray(JsonElement el, params string[] names)
|
||||
{
|
||||
foreach (var n in names)
|
||||
{
|
||||
if (el.TryGetProperty(n, out var p) && p.ValueKind == JsonValueKind.Array)
|
||||
return p.EnumerateArray().ToList();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
internal static int GetInt(JsonElement el, params string[] names)
|
||||
{
|
||||
foreach (var n in names)
|
||||
if (el.TryGetProperty(n, out var p) && p.ValueKind == JsonValueKind.Number && p.TryGetInt32(out var v))
|
||||
return v;
|
||||
return 0;
|
||||
}
|
||||
|
||||
internal static double GetDouble(JsonElement el, params string[] names)
|
||||
{
|
||||
foreach (var n in names)
|
||||
if (el.TryGetProperty(n, out var p) && p.ValueKind == JsonValueKind.Number && p.TryGetDouble(out var v))
|
||||
return v;
|
||||
return 0;
|
||||
}
|
||||
|
||||
internal static long GetLong(JsonElement el, params string[] names)
|
||||
{
|
||||
foreach (var n in names)
|
||||
if (el.TryGetProperty(n, out var p) && p.ValueKind == JsonValueKind.Number && p.TryGetInt64(out var v))
|
||||
return v;
|
||||
return 0;
|
||||
}
|
||||
|
||||
internal static bool GetBool(JsonElement el, params string[] names)
|
||||
{
|
||||
foreach (var n in names)
|
||||
if (el.TryGetProperty(n, out var p) && p.ValueKind == JsonValueKind.True)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private static async Task<List<LeaderboardScore>> LoadBoardAsync(RecDBContext db, long roomId, int statChannel)
|
||||
{
|
||||
var q = db.LeaderboardScores.AsNoTracking();
|
||||
bool global = statChannel >= FirstGlobalStatChannel || roomId <= 0;
|
||||
q = global
|
||||
? q.Where(x => x.StatChannel == statChannel)
|
||||
: q.Where(x => x.RoomId == roomId && x.StatChannel == statChannel);
|
||||
return await q.ToListAsync();
|
||||
}
|
||||
|
||||
private static List<RankedEntry> RankBoard(List<LeaderboardScore> scores, bool sortAscending)
|
||||
{
|
||||
|
||||
|
||||
|
||||
var best = scores
|
||||
.GroupBy(x => x.PlayerId)
|
||||
.Select(g => new
|
||||
{
|
||||
PlayerId = g.Key,
|
||||
BestValue = sortAscending
|
||||
? g.Min(x => x.Score != 0 ? x.Score : x.Time)
|
||||
: g.Max(x => x.Score != 0 ? x.Score : x.Time),
|
||||
BestTime = sortAscending ? g.Min(x => x.Time) : g.Max(x => x.Time)
|
||||
})
|
||||
.ToList();
|
||||
|
||||
|
||||
|
||||
var ordered = sortAscending
|
||||
? best.OrderBy(x => x.BestValue).ThenBy(x => x.BestTime).ToList()
|
||||
: best.OrderByDescending(x => x.BestValue).ThenBy(x => x.BestTime).ToList();
|
||||
|
||||
return ordered.Select(e => new RankedEntry(e.PlayerId, e.BestValue, e.BestTime)).ToList();
|
||||
}
|
||||
|
||||
private static List<long> BoardPlayerIds(List<RankedEntry> ranked, int askingPlayerId)
|
||||
{
|
||||
var ids = ranked.Select(e => e.PlayerId).ToList();
|
||||
if (askingPlayerId > 0 && !ids.Contains(askingPlayerId)) ids.Add(askingPlayerId);
|
||||
return ids;
|
||||
}
|
||||
|
||||
private static async Task<Dictionary<long, string>> LoadPlayerNamesAsync(RecDBContext db, List<long> playerIds)
|
||||
{
|
||||
if (playerIds.Count == 0) return new Dictionary<long, string>();
|
||||
return await db.Profiles.AsNoTracking()
|
||||
.Where(p => playerIds.Contains(p.accountId))
|
||||
.ToDictionaryAsync(p => p.accountId, p => p.displayName ?? p.username);
|
||||
}
|
||||
|
||||
private static LeaderboardEntryResponse CreatePlaceholderEntry(long playerId, Dictionary<long, string> names)
|
||||
{
|
||||
return new LeaderboardEntryResponse
|
||||
{
|
||||
PlayerId = playerId,
|
||||
PlayerName = names.TryGetValue(playerId, out var name) ? name : "",
|
||||
Score = 0,
|
||||
Time = 0,
|
||||
Rank = 1
|
||||
};
|
||||
}
|
||||
|
||||
private sealed record RankedEntry(long PlayerId, double BestValue, long BestTime);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public abstract class LeaderboardControllerBase : Controller
|
||||
{
|
||||
protected readonly IDbContextFactory<RecDBContext> DbFactory;
|
||||
|
||||
protected LeaderboardControllerBase(IDbContextFactory<RecDBContext> dbFactory)
|
||||
{
|
||||
DbFactory = dbFactory;
|
||||
}
|
||||
|
||||
private async Task<string?> ReadBodyAsync()
|
||||
{
|
||||
using var reader = new StreamReader(Request.Body);
|
||||
return await reader.ReadToEndAsync();
|
||||
}
|
||||
|
||||
private long QueryPlayerId()
|
||||
{
|
||||
if (long.TryParse(Request.Query["playerId"], out var id) && id > 0) return id;
|
||||
if (long.TryParse(Request.Query["PlayerId"], out id) && id > 0) return id;
|
||||
return 0;
|
||||
}
|
||||
|
||||
private void FillPlayerIdFromRequest(GetRankRequest req)
|
||||
{
|
||||
if (req.PlayerId > 0) return;
|
||||
|
||||
var queryPlayerId = QueryPlayerId();
|
||||
if (queryPlayerId > 0 && queryPlayerId <= int.MaxValue)
|
||||
{
|
||||
req.PlayerId = (int)queryPlayerId;
|
||||
return;
|
||||
}
|
||||
|
||||
var authPlayerId = User.GetUserId();
|
||||
if (authPlayerId > 0 && authPlayerId <= int.MaxValue)
|
||||
req.PlayerId = (int)authPlayerId.Value;
|
||||
}
|
||||
|
||||
private async Task<GetRanksRequest> ParseRanksRequestAsync()
|
||||
{
|
||||
var raw = await ReadBodyAsync();
|
||||
var req = new GetRanksRequest();
|
||||
if (!string.IsNullOrWhiteSpace(raw))
|
||||
{
|
||||
try
|
||||
{
|
||||
using var doc = JsonDocument.Parse(raw);
|
||||
var el = doc.RootElement;
|
||||
req.PlayerId = LeaderboardStore.GetInt(el, "PlayerId", "playerId", "Player");
|
||||
req.StatChannel = LeaderboardStore.GetInt(el, "StatChannel", "statChannel", "Channel", "StatId", "statId");
|
||||
req.RoomId = LeaderboardStore.GetLong(el, "RoomId", "roomId", "Room");
|
||||
req.FilterType = LeaderboardStore.GetInt(el, "FilterType", "filterType");
|
||||
req.Timeframe = LeaderboardStore.GetInt(el, "Timeframe", "timeframe");
|
||||
req.SortAscending = LeaderboardStore.GetBool(el, "SortAscending", "sortAscending");
|
||||
req.RankStart = LeaderboardStore.GetInt(el, "RankStart", "rankStart");
|
||||
req.RankEnd = LeaderboardStore.GetInt(el, "RankEnd", "rankEnd");
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
return req;
|
||||
}
|
||||
|
||||
private async Task<GetNearbyScoresRequest> ParseNearbyScoresRequestAsync()
|
||||
{
|
||||
var raw = await ReadBodyAsync();
|
||||
var req = new GetNearbyScoresRequest();
|
||||
if (!string.IsNullOrWhiteSpace(raw))
|
||||
{
|
||||
try
|
||||
{
|
||||
using var doc = JsonDocument.Parse(raw);
|
||||
var el = doc.RootElement;
|
||||
req.PlayerId = LeaderboardStore.GetInt(el, "PlayerId", "playerId", "Player");
|
||||
req.StatChannel = LeaderboardStore.GetInt(el, "StatChannel", "statChannel", "Channel", "StatId", "statId");
|
||||
req.RoomId = LeaderboardStore.GetLong(el, "RoomId", "roomId", "Room");
|
||||
req.FilterType = LeaderboardStore.GetInt(el, "FilterType", "filterType");
|
||||
req.Timeframe = LeaderboardStore.GetInt(el, "Timeframe", "timeframe");
|
||||
req.SortAscending = LeaderboardStore.GetBool(el, "SortAscending", "sortAscending");
|
||||
req.WindowSize = LeaderboardStore.GetInt(el, "WindowSize", "windowSize");
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
return req;
|
||||
}
|
||||
|
||||
[HttpPost("CheckAndSetStat")]
|
||||
public async Task<IActionResult> CheckAndSetStat()
|
||||
{
|
||||
var rawBody = await ReadBodyAsync();
|
||||
var result = await LeaderboardStore.SubmitScoreAsync(DbFactory, rawBody, User.GetUserId() ?? 0, QueryPlayerId());
|
||||
return result == null ? BadRequest(new { Success = false }) : Ok(result);
|
||||
}
|
||||
|
||||
[HttpPost("SubmitScore")]
|
||||
public async Task<IActionResult> SubmitScore()
|
||||
{
|
||||
var rawBody = await ReadBodyAsync();
|
||||
var result = await LeaderboardStore.SubmitScoreAsync(DbFactory, rawBody, User.GetUserId() ?? 0, QueryPlayerId());
|
||||
return result == null ? BadRequest(new { Success = false }) : Ok(result);
|
||||
}
|
||||
|
||||
[HttpPost("GetRanks")]
|
||||
public async Task<IActionResult> GetRanks()
|
||||
{
|
||||
var req = await ParseRanksRequestAsync();
|
||||
FillPlayerIdFromRequest(req);
|
||||
return Ok(await LeaderboardStore.GetRanksAsync(DbFactory, req));
|
||||
}
|
||||
|
||||
[HttpGet("GetRanks")]
|
||||
public async Task<IActionResult> GetRanksGet([FromQuery] GetRanksRequest req)
|
||||
{
|
||||
if (req == null) return BadRequest();
|
||||
FillPlayerIdFromRequest(req);
|
||||
return Ok(await LeaderboardStore.GetRanksAsync(DbFactory, req));
|
||||
}
|
||||
|
||||
[HttpPost("GetPlayerRank")]
|
||||
public async Task<IActionResult> GetPlayerRank()
|
||||
{
|
||||
var req = await ParseRanksRequestAsync();
|
||||
FillPlayerIdFromRequest(req);
|
||||
return Ok(await LeaderboardStore.GetPlayerRankAsync(DbFactory, req));
|
||||
}
|
||||
|
||||
[HttpGet("GetPlayerRank")]
|
||||
public async Task<IActionResult> GetPlayerRankGet([FromQuery] GetRankRequest req)
|
||||
{
|
||||
if (req == null) return BadRequest();
|
||||
FillPlayerIdFromRequest(req);
|
||||
return Ok(await LeaderboardStore.GetPlayerRankAsync(DbFactory, req));
|
||||
}
|
||||
|
||||
[HttpPost("GetNearbyScores")]
|
||||
public async Task<IActionResult> GetNearbyScores()
|
||||
{
|
||||
var req = await ParseNearbyScoresRequestAsync();
|
||||
FillPlayerIdFromRequest(req);
|
||||
return Ok(await LeaderboardStore.GetNearbyScoresAsync(DbFactory, req));
|
||||
}
|
||||
|
||||
[HttpGet("GetNearbyScores")]
|
||||
public async Task<IActionResult> GetNearbyScoresGet([FromQuery] GetNearbyScoresRequest req)
|
||||
{
|
||||
if (req == null) return BadRequest();
|
||||
FillPlayerIdFromRequest(req);
|
||||
return Ok(await LeaderboardStore.GetNearbyScoresAsync(DbFactory, req));
|
||||
}
|
||||
|
||||
internal static string Truncate(string? s)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(s)) return "";
|
||||
return s.Length <= 500 ? s : s.Substring(0, 500) + "...";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Route("leaderboard/leaderboard")]
|
||||
public class LeaderboardAPI : LeaderboardControllerBase
|
||||
{
|
||||
public LeaderboardAPI(IDbContextFactory<RecDBContext> dbFactory) : base(dbFactory) { }
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[Route("leaderboard")]
|
||||
public class LeaderboardCompatAPI : LeaderboardControllerBase
|
||||
{
|
||||
public LeaderboardCompatAPI(IDbContextFactory<RecDBContext> dbFactory) : base(dbFactory) { }
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[Route("leaderboard/{**path}")]
|
||||
public class LeaderboardCatchallController : Controller
|
||||
{
|
||||
[HttpGet]
|
||||
[HttpPost]
|
||||
[HttpPut]
|
||||
[HttpDelete]
|
||||
[HttpPatch]
|
||||
public async Task<IActionResult> Catch(string? path)
|
||||
{
|
||||
string body = "";
|
||||
try
|
||||
{
|
||||
using var reader = new StreamReader(Request.Body);
|
||||
body = await reader.ReadToEndAsync();
|
||||
}
|
||||
catch { }
|
||||
return NotFound();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user