feat: Enhance API endpoints with new methods, improved response structures, and added Photon access token endpoint

This commit is contained in:
Alexa
2026-06-23 12:42:24 +03:00
parent 4034449a6d
commit 27e5e0064c
10 changed files with 304 additions and 67 deletions
@@ -60,16 +60,34 @@ public class CompatibilityFeatureController(DorkNetDbContext db) : ControllerBas
.ExecuteDeleteAsync(); .ExecuteDeleteAsync();
} }
// 2020.12 deserializer for this response expects `subscription` to be a var active = targetId == pid || await db.Subscriptions.AnyAsync(s =>
// nested OBJECT (Subscription DTO with subscriptionId/recNetPlayerId/... s.SubscriberPlayerId == pid && s.TargetPlayerId == targetId);
// fields), NOT a boolean. Sending a bool throws InvalidCastException var now = DateTime.UtcNow;
// on the client ("Unable to cast object of type 'Boolean' to type var subscriptionId = unchecked((pid * 397L) ^ targetId);
// 'Dictionary`2'"). Send null when there's no active subscription — var subscription = new Dictionary<string, object?>
// the client's generic-cast helper returns default(T) which is fine.
return Ok(new
{ {
subscription = (object?)null, ["subscriptionId"] = subscriptionId,
platformAccountSubscribedPlayerId = targetId, ["recNetPlayerId"] = pid,
["platformAccountSubscribedPlayerId"] = targetId,
["isActive"] = active,
["startedAt"] = now,
["currentPeriodEnd"] = active ? now.AddYears(10) : now,
["renewalDate"] = active ? now.AddYears(10) : now,
["SubscriptionId"] = subscriptionId,
["RecNetPlayerId"] = pid,
["PlatformAccountSubscribedPlayerId"] = targetId,
["IsActive"] = active,
["StartedAt"] = now,
["CurrentPeriodEnd"] = active ? now.AddYears(10) : now,
["RenewalDate"] = active ? now.AddYears(10) : now,
};
return Ok(new Dictionary<string, object?>
{
["subscription"] = subscription,
["platformAccountSubscribedPlayerId"] = targetId,
["Subscription"] = subscription,
["PlatformAccountSubscribedPlayerId"] = targetId,
}); });
} }
@@ -44,6 +44,7 @@ public class ExternalFriendInviteController(DorkNetDbContext db) : ControllerBas
} }
[HttpGet("api/externalfriendinvite/v1/getplatformreferrers")] [HttpGet("api/externalfriendinvite/v1/getplatformreferrers")]
[HttpPost("api/externalfriendinvite/v1/getplatformreferrers")]
public async Task<IActionResult> GetPlatformReferrers() public async Task<IActionResult> GetPlatformReferrers()
{ {
var rows = await db.PlayerSettings var rows = await db.PlayerSettings
@@ -54,6 +55,7 @@ public class ExternalFriendInviteController(DorkNetDbContext db) : ControllerBas
} }
[HttpGet("api/externalfriendinvite/v1/gettextmessagereferrers")] [HttpGet("api/externalfriendinvite/v1/gettextmessagereferrers")]
[HttpPost("api/externalfriendinvite/v1/gettextmessagereferrers")]
public async Task<IActionResult> GetTextMessageReferrers() public async Task<IActionResult> GetTextMessageReferrers()
{ {
var rows = await db.PlayerSettings var rows = await db.PlayerSettings
@@ -1,6 +1,7 @@
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using DorkNet.Models.Auth;
using DorkNet.Server.Auth; using DorkNet.Server.Auth;
using DorkNet.Server.Data; using DorkNet.Server.Data;
using DorkNet.Server.Data.Entities; using DorkNet.Server.Data.Entities;
@@ -25,7 +26,14 @@ public class InfluencerPartnerProgramController(DorkNetDbContext db) : Controlle
.Where(p => creatorIds.Contains(p.Id)) .Where(p => creatorIds.Contains(p.Id))
.OrderBy(p => p.Username) .OrderBy(p => p.Username)
.ToListAsync(); .ToListAsync();
return Ok(players.Select(ToInfluencerWire)); var influencers = players.Select(ToInfluencerWire).ToList();
return Ok(new Dictionary<string, object>
{
["Influencers"] = influencers,
["influencers"] = influencers,
["Accounts"] = influencers,
["accounts"] = influencers,
});
} }
[HttpGet("api/influencerpartnerprogram/influencer")] [HttpGet("api/influencerpartnerprogram/influencer")]
@@ -92,18 +100,16 @@ public class InfluencerPartnerProgramController(DorkNetDbContext db) : Controlle
return Ok(new { Success = true }); return Ok(new { Success = true });
} }
private static object ToInfluencerWire(PlayerEntity player) => new private static RecNetAccount ToInfluencerWire(PlayerEntity player) => new()
{ {
AccountId = (int)player.Id, AccountId = (int)player.Id,
RawUsername = player.Username, RawUsername = player.Username,
player.Username, Username = player.Username,
DisplayName = player.DisplayName ?? player.Username, DisplayName = player.DisplayName ?? player.Username,
ProfileImage = player.ProfileImageName ?? string.Empty, ProfileImage = player.ProfileImageName ?? string.Empty,
TreatAsJunior = false, TreatAsJunior = false,
HasBirthday = true, HasBirthday = true,
Platforms = 1, Platforms = 1,
IsInfluencer = true, CreatedAt = player.CreatedAt == default ? DateTime.UtcNow : player.CreatedAt,
SupportedInfluencerId = (int?)null,
LocalSupportedInfluencerId = (int?)null,
}; };
} }
@@ -1,6 +1,8 @@
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json; using System.Text.Json;
using DorkNet.Server.Auth; using DorkNet.Server.Auth;
using DorkNet.Server.Data; using DorkNet.Server.Data;
@@ -29,12 +31,20 @@ public class KeepsakesController(DorkNetDbContext db, DomainConfig domain) : Con
[HttpGet("categories")] [HttpGet("categories")]
[AllowAnonymous] [AllowAnonymous]
public IActionResult Categories() => Ok(new[] public IActionResult Categories()
{ {
new { Category = "account", DisplayName = "Account" }, var results = new[]
new { Category = "event", DisplayName = "Events" }, {
new { Category = "room", DisplayName = "Rooms" }, new { KeepsakeCategoryId = 0, VisualId = "account", LimitPerRoom = 0, XpValue = 0, IconOutlineImageName = string.Empty, IconFilledImageName = string.Empty },
}); new { KeepsakeCategoryId = 1, VisualId = "event", LimitPerRoom = 0, XpValue = 0, IconOutlineImageName = string.Empty, IconFilledImageName = string.Empty },
new { KeepsakeCategoryId = 2, VisualId = "room", LimitPerRoom = 64, XpValue = 0, IconOutlineImageName = string.Empty, IconFilledImageName = string.Empty },
};
return Ok(new
{
Results = results,
TotalResults = results.Length,
});
}
[HttpGet("events")] [HttpGet("events")]
public async Task<IActionResult> Events() public async Task<IActionResult> Events()
@@ -62,16 +72,22 @@ public class KeepsakesController(DorkNetDbContext db, DomainConfig domain) : Con
|| EF.Functions.Like(k.EventKey, $"room/{roomIdText}/%"))) || EF.Functions.Like(k.EventKey, $"room/{roomIdText}/%")))
.OrderByDescending(k => k.EarnedAt) .OrderByDescending(k => k.EarnedAt)
.ToListAsync(); .ToListAsync();
return Ok(rows.Select(ToWire)); var instances = rows.Select(row => ToRoomInstanceWire(row, roomId)).ToList();
return Ok(new
{
Instances = instances,
CollectionRecords = Array.Empty<object>(),
KeepsakeProgressionEventIds = Array.Empty<long>(),
});
} }
[HttpGet("globalconfig")] [HttpGet("globalconfig")]
[AllowAnonymous] [AllowAnonymous]
public IActionResult GlobalConfig() => Ok(new public IActionResult GlobalConfig() => Ok(new
{ {
Enabled = true, KeepsakeFeatureEnabled = true,
Categories = new[] { "account", "event", "room" }, KeepsakeRoomLimit = 64,
CdnBaseUrl = $"https://{domain.Sub("cdn")}/", SocialXpBoostEnabled = false,
}); });
[HttpPost] [HttpPost]
@@ -162,6 +178,32 @@ public class KeepsakesController(DorkNetDbContext db, DomainConfig domain) : Con
row.EarnedAt, row.EarnedAt,
}; };
private static object ToRoomInstanceWire(KeepsakeEntity row, long roomId)
{
var placedByAccountId = row.PlayerId is > 0 and <= int.MaxValue
? (int)row.PlayerId
: 0;
return new
{
KeepsakeInstanceId = StableKeepsakeInstanceId(row.Id).ToString("D"),
KeepsakeCategoryConfigId = CategoryId(row.Category),
PlacedByAccountId = placedByAccountId,
RoomId = roomId,
SubRoomId = (long?)null,
};
}
private static int CategoryId(string? category) =>
string.Equals(category, "event", StringComparison.OrdinalIgnoreCase) ? 1 :
string.Equals(category, "room", StringComparison.OrdinalIgnoreCase) ? 2 :
0;
private static Guid StableKeepsakeInstanceId(long id)
{
var hash = MD5.HashData(Encoding.UTF8.GetBytes($"keepsakeInstance:{id}"));
return new Guid(hash);
}
private static string Trim(string? value, int max) private static string Trim(string? value, int max)
{ {
var s = (value ?? string.Empty).Trim(); var s = (value ?? string.Empty).Trim();
@@ -3,11 +3,12 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using DorkNet.Server.Auth; using DorkNet.Server.Auth;
using DorkNet.Server.Data; using DorkNet.Server.Data;
using DorkNet.Server.Services;
namespace DorkNet.Server.Controllers.API.ProgressionEvents; namespace DorkNet.Server.Controllers.API.ProgressionEvents;
[ApiController] [ApiController]
public class ProgressionEventsController(DorkNetDbContext db) : ControllerBase public class ProgressionEventsController(DorkNetDbContext db, ServerSettingsService serverSettings) : ControllerBase
{ {
[HttpGet("api/progressionEvents")] [HttpGet("api/progressionEvents")]
[Authorize] [Authorize]
@@ -28,19 +29,60 @@ public class ProgressionEventsController(DorkNetDbContext db) : ControllerBase
return Ok(rows); return Ok(rows);
} }
[HttpGet("api/progressionEvents/active")] [HttpGet("api/progressionEvents/event/{eventId:long}")]
[AllowAnonymous] [AllowAnonymous]
public IActionResult Active() public async Task<IActionResult> Event(long eventId)
{ {
var start = DateTime.UtcNow.Date.AddDays(-(int)DateTime.UtcNow.DayOfWeek); var activeId = CurrentProgressionEventId();
var end = start.AddDays(7); if (eventId != activeId)
return NotFound();
var weekly = await serverSettings.GetWeeklyChallengesAsync();
var (start, end) = CurrentWeekWindow();
return Ok(new return Ok(new
{ {
EventKey = $"weekly:{start:yyyyMMdd}", ProgressionEventId = activeId,
Name = "Weekly Progression", Name = "Weekly Progression",
StartAt = start, Rewards = new[]
EndAt = end, {
Active = true, new
{
ProgressionEventRewardId = activeId * 1000L,
GiftDropId = weekly.Reward.GiftDropId != 0 ? weekly.Reward.GiftDropId : activeId,
ImageName = string.Empty,
Xp = weekly.Reward.Xp,
RewardIndex = 0,
IsBonus = false,
},
},
KeepsakeRoomLists = Array.Empty<object>(),
StartTime = start,
EndTime = end,
CollectionEndTime = end,
UsesBoost = false,
BoostDailyGameplayMinutesLimit = 0,
BoostXpMultiplier = 1.0,
PurchasableXpBoostId = (Guid?)null,
ActiveExperiment = string.Empty,
ChallengesIconImageName = string.Empty,
RewardsPipImageName = string.Empty,
EventInfoImageName = string.Empty,
}); });
} }
[HttpGet("api/progressionEvents/active")]
[AllowAnonymous]
public IActionResult Active() => Ok(CurrentProgressionEventId());
private static long CurrentProgressionEventId()
{
var (start, _) = CurrentWeekWindow();
return long.Parse(start.ToString("yyyyMMdd"));
}
private static (DateTime Start, DateTime End) CurrentWeekWindow()
{
var start = DateTime.UtcNow.Date.AddDays(-(int)DateTime.UtcNow.DayOfWeek);
return (start, start.AddDays(7));
}
} }
@@ -25,15 +25,29 @@ public class QuickPlayController(DorkNetDbContext db) : ControllerBase
/// immediately teleports — that's WHY users were randomly /// immediately teleports — that's WHY users were randomly
/// spawning in Paddleball etc. /// spawning in Paddleball etc.
/// ///
/// Correct behaviour: return empty {} unless we actually have a /// Correct behaviour: return the full no-target launch DTO unless
/// queued match for the caller. The /set endpoint is what /// we actually have a queued match for the caller. The /set endpoint
/// queues; getandclear is the consume. /// is what queues; getandclear is the consume.
/// ///
/// We don't persist queue state on a single-region private /// We don't persist queue state on a single-region private
/// server (no real matchmaking pool), so always return empty.</summary> /// server (no real matchmaking pool), so always return empty.</summary>
[HttpGet("api/quickPlay/v1/getandclear")] [HttpGet("api/quickPlay/v1/getandclear")]
[HttpGet("api/quickPlay/v2/getandclear")] [HttpGet("api/quickPlay/v2/getandclear")]
public IActionResult GetAndClear() => Ok(new { }); public IActionResult GetAndClear() => Ok(QuickPlayLaunchTarget.Empty);
private sealed class QuickPlayLaunchTarget
{
public static readonly QuickPlayLaunchTarget Empty = new();
public bool OKCGKFJELAC { get; init; }
public string INMKAFBAOPC { get; init; } = string.Empty;
public int? FIOPJIIOCGA { get; init; }
public string ICKOEFDNOPM { get; init; } = string.Empty;
public int? KLDCJBLLDKN { get; init; }
public bool OFBMKDAONNE { get; init; }
public object? FMKHKNCEBCN { get; init; }
public long? EEFNKAFLPLG { get; init; }
}
public sealed class SetQuickPlayRequest public sealed class SetQuickPlayRequest
{ {
@@ -1,6 +1,8 @@
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using System.Security.Cryptography;
using System.Text;
using DorkNet.Server.Auth; using DorkNet.Server.Auth;
using DorkNet.Server.Data; using DorkNet.Server.Data;
using DorkNet.Server.Data.Entities; using DorkNet.Server.Data.Entities;
@@ -13,8 +15,9 @@ public class RoomConsumablesController(DorkNetDbContext db, LevelService level)
{ {
[HttpGet("api/roomconsumables")] [HttpGet("api/roomconsumables")]
[HttpGet("api/roomconsumables/v1/roomConsumable")] [HttpGet("api/roomconsumables/v1/roomConsumable")]
[HttpGet("api/roomconsumables/v1/roomConsumable/room/{roomId:long}")]
[AllowAnonymous] [AllowAnonymous]
public async Task<IActionResult> List([FromQuery] long? id = null) public async Task<IActionResult> List([FromRoute] long? roomId = null, [FromQuery] long? id = null)
{ {
var query = db.StoreItems var query = db.StoreItems
.Where(i => i.IsActive && i.Category.ToLower() == "consumable"); .Where(i => i.IsActive && i.Category.ToLower() == "consumable");
@@ -27,6 +30,24 @@ public class RoomConsumablesController(DorkNetDbContext db, LevelService level)
return Ok(rows.Select(ToWire)); return Ok(rows.Select(ToWire));
} }
[HttpGet("api/roomconsumables/v1/roomConsumable/room/{roomId:long}/me")]
[Authorize]
public async Task<IActionResult> MineForRoom(long roomId)
{
var me = this.RequireCurrentPlayerId();
var rows = await (from inventory in db.PlayerInventory
join item in db.StoreItems on inventory.ItemSlug equals item.Slug
where inventory.PlayerId == me
&& inventory.Quantity > 0
&& item.IsActive
&& item.Category.ToLower() == "consumable"
orderby item.DisplayName
select new { Inventory = inventory, Item = item })
.Take(200)
.ToListAsync();
return Ok(rows.Select(row => ToInventoryWire(row.Item, row.Inventory)));
}
[HttpPost("api/roomconsumables/v1/roomconsumable/{itemId:long}/purchase/currency")] [HttpPost("api/roomconsumables/v1/roomconsumable/{itemId:long}/purchase/currency")]
[Authorize] [Authorize]
public Task<IActionResult> PurchaseCurrency(long itemId, [FromQuery] int? currencyType = null) => public Task<IActionResult> PurchaseCurrency(long itemId, [FromQuery] int? currencyType = null) =>
@@ -73,16 +94,46 @@ public class RoomConsumablesController(DorkNetDbContext db, LevelService level)
}); });
} }
private static object ToWire(StoreItemEntity item) => new private static object ToWire(StoreItemEntity item)
{ {
RoomConsumableId = item.Id, var roomConsumableId = StableRoomConsumableId(item.Id).ToString("D");
ItemId = item.Id, return new
item.Slug, {
Name = item.DisplayName, RoomConsumableId = roomConsumableId,
item.Description, Name = item.DisplayName,
item.ImageName, item.ImageName,
item.Price, item.Description,
item.CurrencyType, item.Price,
item.Category, CurrencyId = (Guid?)null,
}; CreatedAt = DateTimeOffset.UnixEpoch,
ItemId = item.Id,
item.Slug,
item.CurrencyType,
item.Category,
};
}
private static object ToInventoryWire(StoreItemEntity item, PlayerInventoryEntity inventory)
{
var createdAt = inventory.AcquiredAt == default
? DateTimeOffset.UnixEpoch
: new DateTimeOffset(DateTime.SpecifyKind(inventory.AcquiredAt, DateTimeKind.Utc));
return new
{
Id = StableRoomConsumableId(item.Id).ToString("D"),
ConsumableItemDesc = ToWire(item),
Count = Math.Max(0, inventory.Quantity),
InitialCount = Math.Max(0, inventory.Quantity),
CreatedAt = createdAt,
ActiveDurationMinutes = 0,
IsActive = inventory.IsActive,
IsTransferable = false,
};
}
private static Guid StableRoomConsumableId(long itemId)
{
var hash = MD5.HashData(Encoding.UTF8.GetBytes($"roomConsumable:{itemId}"));
return new Guid(hash);
}
} }
@@ -48,12 +48,32 @@ public class RoomsController(
/// TagsCsv column (e.g. `#community`, `#recroomoriginal`). /// TagsCsv column (e.g. `#community`, `#recroomoriginal`).
/// </summary> /// </summary>
[HttpGet("api/rooms/v1/hot")] [HttpGet("api/rooms/v1/hot")]
[HttpGet("rooms/hot")] public async Task<IActionResult> HotV1(
public async Task<IActionResult> Hot(
[FromQuery] string? roomScoreType, [FromQuery] string? roomScoreType,
[FromQuery] string? tags) [FromQuery] string? tags)
=> Ok((await rooms.HotAsync(tags)).Select(RoomService.ToWireRoom).ToList()); => Ok((await rooms.HotAsync(tags)).Select(RoomService.ToWireRoom).ToList());
[HttpGet("rooms/hot")]
public async Task<IActionResult> HotRoomServer(
[FromQuery] string? tag,
[FromQuery] string? tags,
[FromQuery] int skip = 0,
[FromQuery] int take = 100)
{
var all = await rooms.HotAsync(tag ?? tags, take: 200);
var page = all
.Skip(Math.Max(skip, 0))
.Take(Math.Clamp(take, 1, 100))
.Select(RoomService.ToWireRoom)
.ToList();
return Ok(new
{
Results = page,
TotalResults = all.Count,
});
}
/// <summary> /// <summary>
/// GET <c>/roomserver/rooms/recommendations</c> — the home-tab /// GET <c>/roomserver/rooms/recommendations</c> — the home-tab
/// "Recommended" carousel. <b>Wire shape is NOT a flat /// "Recommended" carousel. <b>Wire shape is NOT a flat
@@ -1543,7 +1563,6 @@ public class RoomsController(
.Where(r => r.Id == roomId) .Where(r => r.Id == roomId)
.Select(r => new { r.Id, r.VisitCount, r.CheerCount, r.FavoriteCount }) .Select(r => new { r.Id, r.VisitCount, r.CheerCount, r.FavoriteCount })
.FirstOrDefaultAsync(); .FirstOrDefaultAsync();
if (room is null) return NotFound();
var favorited = await db.RoomBookmarks var favorited = await db.RoomBookmarks
.AnyAsync(b => b.RoomId == roomId && b.PlayerId == pid.Value); .AnyAsync(b => b.RoomId == roomId && b.PlayerId == pid.Value);
@@ -1556,7 +1575,7 @@ public class RoomsController(
return Ok(new return Ok(new
{ {
RoomId = room.Id, RoomId = room?.Id ?? roomId,
PlayerId = pid.Value, PlayerId = pid.Value,
Favorite = favorited, Favorite = favorited,
Favorited = favorited, Favorited = favorited,
@@ -1569,9 +1588,9 @@ public class RoomsController(
VisitCount = visit?.VisitCount ?? 0, VisitCount = visit?.VisitCount ?? 0,
FirstVisitedAt = visit?.FirstVisitAt, FirstVisitedAt = visit?.FirstVisitAt,
LastVisitedAt = visit?.LastVisitAt, LastVisitedAt = visit?.LastVisitAt,
RoomVisitCount = room.VisitCount, RoomVisitCount = room?.VisitCount ?? 0,
RoomCheerCount = room.CheerCount, RoomCheerCount = room?.CheerCount ?? 0,
RoomFavoriteCount = room.FavoriteCount, RoomFavoriteCount = room?.FavoriteCount ?? 0,
}); });
} }
@@ -1613,7 +1632,7 @@ public class RoomsController(
public async Task<IActionResult> CuratedPlaylistsCompat() public async Task<IActionResult> CuratedPlaylistsCompat()
{ {
var curated = await playlists.CuratedAsync(); var curated = await playlists.CuratedAsync();
return Ok(curated.Select(BuildPlaylistUnionEntry).ToList()); return Ok(curated.Select(p => p.Id).ToList());
} }
[HttpGet("clubhousesearch/mostactivenow")] [HttpGet("clubhousesearch/mostactivenow")]
@@ -1725,17 +1744,25 @@ public class RoomsController(
/// <see cref="Featured"/> shape here throws KeyNotFoundException on /// <see cref="Featured"/> shape here throws KeyNotFoundException on
/// the watch's group-id lookup.</summary> /// the watch's group-id lookup.</summary>
[HttpGet("featuredrooms/current")] [HttpGet("featuredrooms/current")]
public async Task<IActionResult> FeaturedRoomsCurrent() => Ok(new public async Task<IActionResult> FeaturedRoomsCurrent()
{ {
FeaturedRoomGroupId = 1L, var ids = await rooms.FeaturedAgRoomIdsAsync(12);
Name = "Featured", var featuredRooms = await db.Rooms
// NMPFCIJPODA.PPGFHEDFBEA (NMPFCIJPODA.txt:100-125) reads .Where(r => ids.Contains(r.Id))
// 3 strict keys, not 2 — FeaturedRoomGroupId, Name, AND .ToListAsync();
// Rooms (List<PPKJFAAAGDO>). Returning without the third var byId = featuredRooms.ToDictionary(r => r.Id);
// key throws KeyNotFoundException on the watch and the var wireRooms = ids
// Featured carousel stays empty. .Select(id => byId.TryGetValue(id, out var room) ? RoomService.ToWireRoom(room) : null)
Rooms = await rooms.FeaturedAgRoomIdsAsync(12), .Where(room => room is not null)
}); .Cast<object>()
.ToList();
return Ok(new
{
FeaturedRoomGroupId = 1L,
Name = "Featured",
Rooms = wireRooms,
});
}
/// <summary>POST <c>api/rooms/v1/bookmark</c> — toggle bookmark /// <summary>POST <c>api/rooms/v1/bookmark</c> — toggle bookmark
/// state for a room. Body: <c>RoomId</c> + optional <c>Bookmark</c> /// state for a room. Body: <c>RoomId</c> + optional <c>Bookmark</c>
@@ -0,0 +1,13 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace DorkNet.Server.Controllers.Commerce;
[ApiController]
public class PurchaseCampaignController : ControllerBase
{
[HttpGet("/purchasecampaign/allcurrent/v2")]
[HttpGet("/purchasecampaign/allcurrent")]
[AllowAnonymous]
public IActionResult AllCurrent() => Ok(Array.Empty<object>());
}
@@ -103,6 +103,28 @@ public class MatchController(
}); });
} }
[HttpGet("/photon_access_token")]
public IActionResult PhotonAccessToken()
{
var pid = this.RequireCurrentPlayerId();
var appId = config["Photon:AppId"] ?? string.Empty;
var voiceAppId = config["Photon:VoiceAppId"] ?? string.Empty;
var region = (config["Photon:CloudRegion"] ?? "us").ToLowerInvariant();
return Ok(new Dictionary<string, object>
{
["ResultCode"] = 1,
["Message"] = string.Empty,
["UserId"] = pid.ToString(System.Globalization.CultureInfo.InvariantCulture),
["PhotonAppId"] = appId,
["PhotonVoiceAppId"] = voiceAppId,
["AppId"] = appId,
["VoiceAppId"] = voiceAppId,
["CloudRegion"] = region,
["Region"] = region,
["Token"] = string.Empty,
});
}
[HttpPost("/matchmake/dorm")] [HttpPost("/matchmake/dorm")]
public async Task<IActionResult> MatchmakeDorm() public async Task<IActionResult> MatchmakeDorm()
{ {