mirror of
https://github.com/DorkSquadRR/DorkNet.git
synced 2026-10-27 07:58:45 -07:00
feat: Enhance API endpoints with new methods, improved response structures, and added Photon access token endpoint
This commit is contained in:
@@ -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
|
||||||
|
|||||||
+12
-6
@@ -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()
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user