823 lines
33 KiB
C#
823 lines
33 KiB
C#
using System.Text.Json;
|
|
using Mocha2023.Auth;
|
|
using Mocha2023.Classes;
|
|
using Mocha2023.Classes.DBs;
|
|
using Mocha2023.Classes.DBs.DBClasses;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
|
|
namespace Mocha2023.Controllers
|
|
{
|
|
[ApiController]
|
|
[ApiProtection]
|
|
public sealed class CreatorFeatureController : ControllerBase
|
|
{
|
|
private const int MaxSmallPayloadBytes = 64 * 1024;
|
|
|
|
[HttpPost("/api/meetupcodes/v1")]
|
|
[HttpPut("/api/meetupcodes/v1")]
|
|
[HttpPost("/api/meetupcodes/v1/create")]
|
|
[HttpPut("/api/meetupcodes/v1/create")]
|
|
[HttpPost("/api/meetups/v1/create")]
|
|
[HttpPut("/api/meetups/v1/create")]
|
|
[HttpPost("/api/meetup/v1/create")]
|
|
[HttpPost("/api/codes/v1/create")]
|
|
[HttpPost("/api/players/v1/meetupcode")]
|
|
[HttpPut("/api/players/v1/meetupcode")]
|
|
[HttpPost("/api/players/v1/meetupcode/create")]
|
|
[RequestSizeLimit(MaxSmallPayloadBytes)]
|
|
public async Task<IActionResult> CreateMeetupCode()
|
|
{
|
|
long? accountId = AuthStuff.GetPlayerId(Request);
|
|
if (!accountId.HasValue)
|
|
return Unauthorized();
|
|
|
|
PlayerDBClasses.Heartbeat heartbeat =
|
|
PlayerDB.GetPlayerHeartbeat(accountId.Value);
|
|
PlayerDBClasses.RoomInstance? instance = heartbeat.roomInstance;
|
|
if (instance == null || instance.roomId <= 0 ||
|
|
instance.roomInstanceId <= 0)
|
|
{
|
|
return BadRequest(new
|
|
{
|
|
Success = false,
|
|
Error = "You must be inside a live room before creating a meetup code."
|
|
});
|
|
}
|
|
|
|
Dictionary<string, string> values = await ReadValuesAsync();
|
|
int maxUses = ReadInt(values, 100,
|
|
"maxUses", "MaxUses", "uses", "Uses");
|
|
int lifetimeMinutes = ReadInt(values, 360,
|
|
"lifetimeMinutes", "LifetimeMinutes", "expiresInMinutes");
|
|
|
|
CreatorFeatureDB.MeetupCodeRecord code =
|
|
CreatorFeatureDB.CreateMeetupCode(
|
|
accountId.Value,
|
|
instance.roomId,
|
|
instance.subRoomId,
|
|
instance.roomInstanceId,
|
|
maxUses,
|
|
lifetimeMinutes);
|
|
|
|
Console.WriteLine(
|
|
$"[MEETUP CODE CREATE] code={code.Code} creator={accountId.Value} " +
|
|
$"room={code.RoomId} instance={code.RoomInstanceId}");
|
|
|
|
object dto = CreatorFeatureDB.ToClientMeetupCode(code);
|
|
return Ok(new
|
|
{
|
|
Success = true,
|
|
Value = dto,
|
|
MeetupCode = code.Code,
|
|
Code = code.Code,
|
|
RoomId = code.RoomId,
|
|
RoomInstanceId = code.RoomInstanceId,
|
|
ExpiresAt = code.ExpiresAtUtc
|
|
});
|
|
}
|
|
|
|
[HttpGet("/api/meetupcodes/v1/{code}")]
|
|
[HttpGet("/api/meetups/v1/{code}")]
|
|
[HttpGet("/api/meetup/v1/{code}")]
|
|
[HttpGet("/api/codes/v1/{code}")]
|
|
[HttpGet("/api/players/v1/meetupcode/{code}")]
|
|
public IActionResult GetMeetupCode(string code)
|
|
{
|
|
if (!AuthStuff.GetPlayerId(Request).HasValue)
|
|
return Unauthorized();
|
|
|
|
CreatorFeatureDB.MeetupCodeRecord? record =
|
|
CreatorFeatureDB.GetMeetupCode(code);
|
|
return record == null
|
|
? NotFound(new { Success = false, Error = "meetup_code_not_found" })
|
|
: Ok(CreatorFeatureDB.ToClientMeetupCode(record));
|
|
}
|
|
|
|
[HttpPost("/api/meetupcodes/v1/redeem")]
|
|
[HttpPut("/api/meetupcodes/v1/redeem")]
|
|
[HttpPost("/api/meetupcodes/v1/join")]
|
|
[HttpPut("/api/meetupcodes/v1/join")]
|
|
[HttpPost("/api/meetups/v1/redeem")]
|
|
[HttpPost("/api/meetups/v1/join")]
|
|
[HttpPost("/api/meetup/v1/redeem")]
|
|
[HttpPost("/api/meetup/v1/join")]
|
|
[HttpPost("/api/codes/v1/redeem")]
|
|
[HttpPost("/api/codes/v1/join")]
|
|
[HttpPost("/api/players/v1/meetupcode/redeem")]
|
|
[HttpPut("/api/players/v1/meetupcode/redeem")]
|
|
[HttpPost("/api/players/v1/meetupcode/join")]
|
|
[HttpPost("/api/meetupcodes/v1/redeem/{code}")]
|
|
[HttpPut("/api/meetupcodes/v1/redeem/{code}")]
|
|
[HttpPost("/api/meetupcodes/v1/join/{code}")]
|
|
[HttpPut("/api/meetupcodes/v1/join/{code}")]
|
|
[HttpPost("/api/meetupcodes/v1/{code}")]
|
|
[HttpPut("/api/meetupcodes/v1/{code}")]
|
|
[HttpPost("/api/meetups/v1/{code}")]
|
|
[HttpPut("/api/meetups/v1/{code}")]
|
|
[RequestSizeLimit(MaxSmallPayloadBytes)]
|
|
public async Task<IActionResult> RedeemMeetupCode(string? code = null)
|
|
{
|
|
long? accountId = AuthStuff.GetPlayerId(Request);
|
|
if (!accountId.HasValue)
|
|
return Unauthorized();
|
|
|
|
Dictionary<string, string> values = await ReadValuesAsync();
|
|
code ??= ReadString(values,
|
|
"code", "Code", "meetupCode", "MeetupCode", "inviteCode");
|
|
|
|
CreatorFeatureDB.MeetupCodeRecord? record =
|
|
CreatorFeatureDB.GetMeetupCode(code);
|
|
if (record == null)
|
|
{
|
|
return NotFound(new
|
|
{
|
|
Success = false,
|
|
Error = "meetup_code_not_found",
|
|
ErrorMessage = "That meetup code is invalid or expired."
|
|
});
|
|
}
|
|
|
|
PlayerDBClasses.Heartbeat? heartbeat = Sessions.JoinInvitedRoomInstance(
|
|
accountId.Value,
|
|
record.RoomInstanceId,
|
|
record.CreatorAccountId,
|
|
record.RoomId);
|
|
if (heartbeat?.roomInstance == null)
|
|
{
|
|
return NotFound(new
|
|
{
|
|
Success = false,
|
|
Error = "meetup_room_unavailable",
|
|
ErrorMessage = "The room for this meetup code is no longer available."
|
|
});
|
|
}
|
|
|
|
CreatorFeatureDB.RecordMeetupCodeUse(record.Code);
|
|
PlayerDB.RecordRoomVisit(accountId.Value, record.RoomId);
|
|
await NotiController.NotifyPlayerPresenceUpdatedAsync(accountId.Value);
|
|
|
|
Console.WriteLine(
|
|
$"[MEETUP CODE JOIN] code={record.Code} player={accountId.Value} " +
|
|
$"room={record.RoomId} instance={record.RoomInstanceId}");
|
|
return Ok(heartbeat);
|
|
}
|
|
|
|
[HttpDelete("/api/meetupcodes/v1/{code}")]
|
|
[HttpDelete("/api/meetups/v1/{code}")]
|
|
public IActionResult RevokeMeetupCode(string code)
|
|
{
|
|
long? accountId = AuthStuff.GetPlayerId(Request);
|
|
if (!accountId.HasValue)
|
|
return Unauthorized();
|
|
return CreatorFeatureDB.RevokeMeetupCode(code, accountId.Value)
|
|
? Ok(new { Success = true })
|
|
: NotFound(new { Success = false, Error = "meetup_code_not_found" });
|
|
}
|
|
|
|
[HttpGet("/api/cloudvariables/v1")]
|
|
[HttpGet("/api/cloudvariables/v1/room/{roomId:long}")]
|
|
[HttpGet("/api/roomvariables/v1")]
|
|
[HttpGet("/api/roomvariables/v1/room/{roomId:long}")]
|
|
[HttpGet("/api/roomvariables/v1/{roomId:long}")]
|
|
[HttpGet("/api/cloudvariables/v1/list/{roomId:long}")]
|
|
[HttpGet("/roomserver/rooms/{roomId:long}/variables")]
|
|
[HttpGet("/roomserver/rooms/{roomId:long}/cloudvariables")]
|
|
public IActionResult GetCloudVariables(long? roomId = null)
|
|
{
|
|
long? accountId = AuthStuff.GetPlayerId(Request);
|
|
if (!accountId.HasValue)
|
|
return Unauthorized();
|
|
|
|
long resolvedRoomId = ResolveRoomId(roomId);
|
|
if (resolvedRoomId <= 0 || RoomDB.GetRoom(resolvedRoomId) == null)
|
|
return NotFound(new { Success = false, Error = "room_not_found" });
|
|
|
|
bool routeDefaultsShared = IsRoomVariableRoute();
|
|
bool sharedOnly = ReadBoolFromQuery(
|
|
routeDefaultsShared,
|
|
"sharedOnly", "SharedOnly", "globalOnly", "GlobalOnly");
|
|
long scopeAccountId = sharedOnly ? 0 : accountId.Value;
|
|
|
|
List<object> values = CreatorFeatureDB
|
|
.GetCloudVariables(
|
|
resolvedRoomId,
|
|
scopeAccountId,
|
|
includeShared: !sharedOnly)
|
|
.Select(CreatorFeatureDB.ToClientCloudVariable)
|
|
.ToList();
|
|
|
|
return Ok(new
|
|
{
|
|
Results = values,
|
|
Variables = values,
|
|
TotalResults = values.Count,
|
|
RoomId = resolvedRoomId
|
|
});
|
|
}
|
|
|
|
[HttpGet("/api/cloudvariables/v1/{key}")]
|
|
[HttpGet("/api/cloudvariables/v1/room/{roomId:long}/{key}")]
|
|
[HttpGet("/api/cloudvariables/v1/{roomId:long}/{key}")]
|
|
[HttpGet("/api/roomvariables/v1/{key}")]
|
|
[HttpGet("/api/roomvariables/v1/room/{roomId:long}/{key}")]
|
|
[HttpGet("/api/roomvariables/v1/{roomId:long}/{key}")]
|
|
[HttpGet("/roomserver/rooms/{roomId:long}/cloudvariables/{key}")]
|
|
public IActionResult GetCloudVariable(string key, long? roomId = null)
|
|
{
|
|
long? accountId = AuthStuff.GetPlayerId(Request);
|
|
if (!accountId.HasValue)
|
|
return Unauthorized();
|
|
|
|
long resolvedRoomId = ResolveRoomId(roomId);
|
|
if (resolvedRoomId <= 0)
|
|
return BadRequest(new { Success = false, Error = "room_id_required" });
|
|
|
|
bool roomScopedRoute = IsRoomVariableRoute();
|
|
CreatorFeatureDB.CloudVariableRecord? value = roomScopedRoute
|
|
? CreatorFeatureDB.GetCloudVariable(resolvedRoomId, 0, key)
|
|
?? CreatorFeatureDB.GetCloudVariable(
|
|
resolvedRoomId,
|
|
accountId.Value,
|
|
key)
|
|
: CreatorFeatureDB.GetCloudVariable(
|
|
resolvedRoomId,
|
|
accountId.Value,
|
|
key)
|
|
?? CreatorFeatureDB.GetCloudVariable(resolvedRoomId, 0, key);
|
|
|
|
return value == null
|
|
? NotFound(new { Success = false, Error = "cloud_variable_not_found" })
|
|
: Ok(CreatorFeatureDB.ToClientCloudVariable(value));
|
|
}
|
|
|
|
[HttpPost("/api/cloudvariables/v1")]
|
|
[HttpPut("/api/cloudvariables/v1")]
|
|
[HttpPatch("/api/cloudvariables/v1")]
|
|
[HttpPost("/api/cloudvariables/v1/{key}")]
|
|
[HttpPut("/api/cloudvariables/v1/{key}")]
|
|
[HttpPost("/api/cloudvariables/v1/room/{roomId:long}")]
|
|
[HttpPut("/api/cloudvariables/v1/room/{roomId:long}")]
|
|
[HttpPost("/api/cloudvariables/v1/room/{roomId:long}/{key}")]
|
|
[HttpPut("/api/cloudvariables/v1/room/{roomId:long}/{key}")]
|
|
[HttpPatch("/api/cloudvariables/v1/room/{roomId:long}/{key}")]
|
|
[HttpPost("/api/cloudvariables/v1/{roomId:long}/{key}")]
|
|
[HttpPut("/api/cloudvariables/v1/{roomId:long}/{key}")]
|
|
[HttpPatch("/api/cloudvariables/v1/{roomId:long}/{key}")]
|
|
[HttpPost("/api/roomvariables/v1")]
|
|
[HttpPut("/api/roomvariables/v1")]
|
|
[HttpPatch("/api/roomvariables/v1")]
|
|
[HttpPost("/api/roomvariables/v1/{roomId:long}")]
|
|
[HttpPut("/api/roomvariables/v1/{roomId:long}")]
|
|
[HttpPost("/api/roomvariables/v1/room/{roomId:long}/{key}")]
|
|
[HttpPut("/api/roomvariables/v1/room/{roomId:long}/{key}")]
|
|
[HttpPatch("/api/roomvariables/v1/room/{roomId:long}/{key}")]
|
|
[HttpPost("/api/roomvariables/v1/{roomId:long}/{key}")]
|
|
[HttpPut("/api/roomvariables/v1/{roomId:long}/{key}")]
|
|
[HttpPatch("/api/roomvariables/v1/{roomId:long}/{key}")]
|
|
[HttpPost("/roomserver/rooms/{roomId:long}/cloudvariables")]
|
|
[HttpPut("/roomserver/rooms/{roomId:long}/cloudvariables")]
|
|
[HttpPost("/roomserver/rooms/{roomId:long}/variables")]
|
|
[HttpPut("/roomserver/rooms/{roomId:long}/variables")]
|
|
[HttpPost("/roomserver/rooms/{roomId:long}/cloudvariables/{key}")]
|
|
[HttpPut("/roomserver/rooms/{roomId:long}/cloudvariables/{key}")]
|
|
[RequestSizeLimit(MaxSmallPayloadBytes)]
|
|
public async Task<IActionResult> SetCloudVariable(
|
|
long? roomId = null,
|
|
string? key = null)
|
|
{
|
|
long? accountId = AuthStuff.GetPlayerId(Request);
|
|
if (!accountId.HasValue)
|
|
return Unauthorized();
|
|
|
|
Dictionary<string, string> values = await ReadValuesAsync();
|
|
long resolvedRoomId = ResolveRoomId(roomId, values);
|
|
RoomDBClasses.Room? room = RoomDB.GetRoom(resolvedRoomId);
|
|
if (room == null)
|
|
return NotFound(new { Success = false, Error = "room_not_found" });
|
|
|
|
key ??= ReadString(values,
|
|
"key", "Key", "name", "Name", "variable", "Variable");
|
|
if (string.IsNullOrWhiteSpace(key))
|
|
return BadRequest(new { Success = false, Error = "cloud_variable_key_required" });
|
|
|
|
bool isShared = ReadBool(values, IsRoomVariableRoute(),
|
|
"shared", "Shared", "isShared", "IsShared",
|
|
"global", "Global", "roomScoped", "RoomScoped") ||
|
|
string.Equals(
|
|
ReadString(values, "scope", "Scope"),
|
|
"room",
|
|
StringComparison.OrdinalIgnoreCase) ||
|
|
string.Equals(
|
|
ReadString(values, "scope", "Scope"),
|
|
"global",
|
|
StringComparison.OrdinalIgnoreCase);
|
|
|
|
if (isShared && !CanWriteSharedCloudVariable(room, accountId.Value))
|
|
{
|
|
return StatusCode(
|
|
StatusCodes.Status403Forbidden,
|
|
new { Success = false, Error = "shared_cloud_variable_requires_room_access" });
|
|
}
|
|
|
|
string valueJson = ReadValueJson(values);
|
|
long scopeAccountId = isShared ? 0 : accountId.Value;
|
|
|
|
try
|
|
{
|
|
CreatorFeatureDB.CloudVariableRecord record =
|
|
CreatorFeatureDB.SetCloudVariable(
|
|
resolvedRoomId,
|
|
scopeAccountId,
|
|
key,
|
|
valueJson,
|
|
accountId.Value);
|
|
|
|
Console.WriteLine(
|
|
$"[CLOUD VARIABLE SET] room={resolvedRoomId} key={record.Key} " +
|
|
$"scope={(isShared ? "room" : "player")} by={accountId.Value} " +
|
|
$"version={record.Version}");
|
|
|
|
return Ok(new
|
|
{
|
|
Success = true,
|
|
Value = CreatorFeatureDB.ToClientCloudVariable(record),
|
|
Variable = CreatorFeatureDB.ToClientCloudVariable(record)
|
|
});
|
|
}
|
|
catch (ArgumentException exception)
|
|
{
|
|
return BadRequest(new { Success = false, Error = exception.Message });
|
|
}
|
|
catch (InvalidOperationException exception)
|
|
{
|
|
return StatusCode(
|
|
StatusCodes.Status409Conflict,
|
|
new { Success = false, Error = exception.Message });
|
|
}
|
|
}
|
|
|
|
[HttpPost("/api/cloudvariables/v1/batch")]
|
|
[HttpPut("/api/cloudvariables/v1/batch")]
|
|
[HttpPost("/api/cloudvariables/v1/room/{roomId:long}/batch")]
|
|
[HttpPut("/api/cloudvariables/v1/room/{roomId:long}/batch")]
|
|
[HttpPost("/api/roomvariables/v1/batch")]
|
|
[HttpPut("/api/roomvariables/v1/batch")]
|
|
[HttpPost("/roomserver/rooms/{roomId:long}/cloudvariables/batch")]
|
|
[HttpPut("/roomserver/rooms/{roomId:long}/cloudvariables/batch")]
|
|
[RequestSizeLimit(MaxSmallPayloadBytes)]
|
|
public async Task<IActionResult> SetCloudVariablesBatch(long? roomId = null)
|
|
{
|
|
long? accountId = AuthStuff.GetPlayerId(Request);
|
|
if (!accountId.HasValue)
|
|
return Unauthorized();
|
|
|
|
JsonElement root;
|
|
try
|
|
{
|
|
using JsonDocument document = await JsonDocument.ParseAsync(
|
|
Request.Body,
|
|
cancellationToken: HttpContext.RequestAborted);
|
|
root = document.RootElement.Clone();
|
|
}
|
|
catch (JsonException)
|
|
{
|
|
return BadRequest(new { Success = false, Error = "invalid_cloud_variable_payload" });
|
|
}
|
|
|
|
long bodyRoomId = root.ValueKind == JsonValueKind.Object
|
|
? ReadJsonLong(root, "roomId", "contextRoomId") ?? 0
|
|
: 0;
|
|
|
|
if (root.ValueKind == JsonValueKind.Object &&
|
|
(TryGetJsonProperty(root, "variables", out JsonElement nested) ||
|
|
TryGetJsonProperty(root, "values", out nested) ||
|
|
TryGetJsonProperty(root, "items", out nested)))
|
|
{
|
|
root = nested;
|
|
}
|
|
|
|
if (root.ValueKind != JsonValueKind.Array)
|
|
return BadRequest(new { Success = false, Error = "cloud_variable_array_required" });
|
|
|
|
long resolvedRoomId = ResolveRoomId(roomId);
|
|
if (resolvedRoomId <= 0 && bodyRoomId > 0)
|
|
resolvedRoomId = bodyRoomId;
|
|
RoomDBClasses.Room? room = RoomDB.GetRoom(resolvedRoomId);
|
|
if (room == null)
|
|
return NotFound(new { Success = false, Error = "room_not_found" });
|
|
|
|
var saved = new List<object>();
|
|
foreach (JsonElement item in root.EnumerateArray().Take(100))
|
|
{
|
|
if (item.ValueKind != JsonValueKind.Object)
|
|
continue;
|
|
|
|
string? key = ReadJsonString(item, "key", "name", "variable");
|
|
if (string.IsNullOrWhiteSpace(key))
|
|
continue;
|
|
|
|
bool shared = ReadJsonNullableBool(item,
|
|
"shared", "isShared", "global", "roomScoped")
|
|
?? IsRoomVariableRoute();
|
|
shared = shared ||
|
|
string.Equals(
|
|
ReadJsonString(item, "scope"),
|
|
"room",
|
|
StringComparison.OrdinalIgnoreCase) ||
|
|
string.Equals(
|
|
ReadJsonString(item, "scope"),
|
|
"global",
|
|
StringComparison.OrdinalIgnoreCase);
|
|
if (shared && !CanWriteSharedCloudVariable(room, accountId.Value))
|
|
{
|
|
return StatusCode(
|
|
StatusCodes.Status403Forbidden,
|
|
new
|
|
{
|
|
Success = false,
|
|
Error = "shared_cloud_variable_requires_room_access",
|
|
Key = key
|
|
});
|
|
}
|
|
|
|
string valueJson = "null";
|
|
if (TryGetJsonProperty(item, "valueJson", out JsonElement valueElement) ||
|
|
TryGetJsonProperty(item, "value", out valueElement) ||
|
|
TryGetJsonProperty(item, "data", out valueElement))
|
|
{
|
|
valueJson = valueElement.ValueKind == JsonValueKind.String
|
|
? JsonSerializer.Serialize(valueElement.GetString())
|
|
: valueElement.GetRawText();
|
|
}
|
|
|
|
try
|
|
{
|
|
CreatorFeatureDB.CloudVariableRecord record =
|
|
CreatorFeatureDB.SetCloudVariable(
|
|
resolvedRoomId,
|
|
shared ? 0 : accountId.Value,
|
|
key,
|
|
valueJson,
|
|
accountId.Value);
|
|
saved.Add(CreatorFeatureDB.ToClientCloudVariable(record));
|
|
}
|
|
catch (ArgumentException exception)
|
|
{
|
|
return BadRequest(new
|
|
{
|
|
Success = false,
|
|
Error = exception.Message,
|
|
Key = key
|
|
});
|
|
}
|
|
catch (InvalidOperationException exception)
|
|
{
|
|
return StatusCode(
|
|
StatusCodes.Status409Conflict,
|
|
new { Success = false, Error = exception.Message, Key = key });
|
|
}
|
|
}
|
|
|
|
return Ok(new
|
|
{
|
|
Success = true,
|
|
Results = saved,
|
|
Variables = saved,
|
|
Count = saved.Count,
|
|
RoomId = resolvedRoomId
|
|
});
|
|
}
|
|
|
|
[HttpDelete("/api/cloudvariables/v1/{key}")]
|
|
[HttpDelete("/api/cloudvariables/v1/room/{roomId:long}/{key}")]
|
|
[HttpDelete("/api/cloudvariables/v1/{roomId:long}/{key}")]
|
|
[HttpDelete("/api/roomvariables/v1/{key}")]
|
|
[HttpDelete("/api/roomvariables/v1/room/{roomId:long}/{key}")]
|
|
[HttpDelete("/api/roomvariables/v1/{roomId:long}/{key}")]
|
|
[HttpDelete("/roomserver/rooms/{roomId:long}/cloudvariables/{key}")]
|
|
public IActionResult DeleteCloudVariable(string key, long? roomId = null)
|
|
{
|
|
long? accountId = AuthStuff.GetPlayerId(Request);
|
|
if (!accountId.HasValue)
|
|
return Unauthorized();
|
|
|
|
long resolvedRoomId = ResolveRoomId(roomId);
|
|
RoomDBClasses.Room? room = RoomDB.GetRoom(resolvedRoomId);
|
|
if (room == null)
|
|
return NotFound(new { Success = false, Error = "room_not_found" });
|
|
|
|
bool shared = ReadBoolFromQuery(
|
|
IsRoomVariableRoute(),
|
|
"shared", "Shared", "global", "Global");
|
|
if (shared && !CanEditRoom(room, accountId.Value))
|
|
return StatusCode(StatusCodes.Status403Forbidden);
|
|
|
|
bool removed = CreatorFeatureDB.DeleteCloudVariable(
|
|
resolvedRoomId,
|
|
shared ? 0 : accountId.Value,
|
|
key);
|
|
return removed
|
|
? Ok(new { Success = true })
|
|
: NotFound(new { Success = false, Error = "cloud_variable_not_found" });
|
|
}
|
|
|
|
private static bool TryGetJsonProperty(
|
|
JsonElement element,
|
|
string name,
|
|
out JsonElement value)
|
|
{
|
|
if (element.ValueKind == JsonValueKind.Object)
|
|
{
|
|
foreach (JsonProperty property in element.EnumerateObject())
|
|
{
|
|
if (property.Name.Equals(name, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
value = property.Value;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
value = default;
|
|
return false;
|
|
}
|
|
|
|
private static string? ReadJsonString(
|
|
JsonElement element,
|
|
params string[] names)
|
|
{
|
|
foreach (string name in names)
|
|
{
|
|
if (!TryGetJsonProperty(element, name, out JsonElement value))
|
|
continue;
|
|
return value.ValueKind == JsonValueKind.String
|
|
? value.GetString()
|
|
: value.ToString();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private static long? ReadJsonLong(
|
|
JsonElement element,
|
|
params string[] names)
|
|
{
|
|
foreach (string name in names)
|
|
{
|
|
if (!TryGetJsonProperty(element, name, out JsonElement value))
|
|
continue;
|
|
if (value.ValueKind == JsonValueKind.Number && value.TryGetInt64(out long number))
|
|
return number;
|
|
if (long.TryParse(value.ToString().Trim('"'), out number))
|
|
return number;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private static bool? ReadJsonNullableBool(
|
|
JsonElement element,
|
|
params string[] names)
|
|
{
|
|
foreach (string name in names)
|
|
{
|
|
if (!TryGetJsonProperty(element, name, out JsonElement value))
|
|
continue;
|
|
if (value.ValueKind == JsonValueKind.True)
|
|
return true;
|
|
if (value.ValueKind == JsonValueKind.False)
|
|
return false;
|
|
if (bool.TryParse(value.ToString(), out bool boolean))
|
|
return boolean;
|
|
if (int.TryParse(value.ToString(), out int number))
|
|
return number != 0;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private async Task<Dictionary<string, string>> ReadValuesAsync()
|
|
{
|
|
var values = new Dictionary<string, string>(
|
|
StringComparer.OrdinalIgnoreCase);
|
|
|
|
foreach (KeyValuePair<string, Microsoft.Extensions.Primitives.StringValues> item
|
|
in Request.Query)
|
|
{
|
|
if (item.Value.Count > 0)
|
|
values[item.Key] = item.Value[item.Value.Count - 1] ?? string.Empty;
|
|
}
|
|
|
|
if (Request.HasFormContentType)
|
|
{
|
|
var form = await Request.ReadFormAsync(HttpContext.RequestAborted);
|
|
foreach (KeyValuePair<string, Microsoft.Extensions.Primitives.StringValues> item
|
|
in form)
|
|
{
|
|
if (item.Value.Count > 0)
|
|
values[item.Key] = item.Value[item.Value.Count - 1] ?? string.Empty;
|
|
}
|
|
return values;
|
|
}
|
|
|
|
if ((Request.ContentLength ?? 0) <= 0 &&
|
|
Request.Headers.TransferEncoding.Count == 0)
|
|
return values;
|
|
|
|
using var reader = new StreamReader(Request.Body);
|
|
string rawBody = await reader.ReadToEndAsync(HttpContext.RequestAborted);
|
|
if (string.IsNullOrWhiteSpace(rawBody))
|
|
return values;
|
|
|
|
values["__raw"] = rawBody;
|
|
try
|
|
{
|
|
using JsonDocument document = JsonDocument.Parse(rawBody);
|
|
JsonElement root = document.RootElement;
|
|
if (root.ValueKind == JsonValueKind.Object)
|
|
{
|
|
foreach (JsonProperty property in root.EnumerateObject())
|
|
{
|
|
values[property.Name] = property.Value.ValueKind == JsonValueKind.String
|
|
? property.Value.GetString() ?? string.Empty
|
|
: property.Value.GetRawText();
|
|
}
|
|
}
|
|
else if (root.ValueKind == JsonValueKind.String)
|
|
{
|
|
values["value"] = root.GetString() ?? string.Empty;
|
|
values["code"] = root.GetString() ?? string.Empty;
|
|
}
|
|
else
|
|
{
|
|
values["value"] = root.GetRawText();
|
|
}
|
|
}
|
|
catch (JsonException)
|
|
{
|
|
values["value"] = rawBody.Trim();
|
|
values["code"] = rawBody.Trim().Trim('"');
|
|
}
|
|
|
|
return values;
|
|
}
|
|
|
|
private long ResolveRoomId(
|
|
long? routeRoomId,
|
|
IReadOnlyDictionary<string, string>? values = null)
|
|
{
|
|
if (routeRoomId.GetValueOrDefault() > 0)
|
|
return routeRoomId!.Value;
|
|
|
|
foreach (string key in new[] { "roomId", "RoomId", "contextRoomId" })
|
|
{
|
|
string? value = values != null && values.TryGetValue(key, out string? bodyValue)
|
|
? bodyValue
|
|
: Request.Query[key].FirstOrDefault();
|
|
if (long.TryParse(value?.Trim('"'), out long parsed) && parsed > 0)
|
|
return parsed;
|
|
}
|
|
|
|
long? accountId = AuthStuff.GetPlayerId(Request);
|
|
if (accountId.HasValue)
|
|
return PlayerDB.GetPlayerHeartbeat(accountId.Value).roomInstance?.roomId ?? 0;
|
|
return 0;
|
|
}
|
|
|
|
private bool IsRoomVariableRoute()
|
|
{
|
|
string path = Request.Path.Value ?? string.Empty;
|
|
return path.Contains("roomvariables", StringComparison.OrdinalIgnoreCase) ||
|
|
path.StartsWith("/roomserver/", StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
private static bool CanWriteSharedCloudVariable(
|
|
RoomDBClasses.Room room,
|
|
long accountId)
|
|
{
|
|
if (CanEditRoom(room, accountId))
|
|
return true;
|
|
|
|
PlayerDBClasses.Heartbeat heartbeat =
|
|
PlayerDB.GetPlayerHeartbeat(accountId);
|
|
return heartbeat.isOnline &&
|
|
heartbeat.roomInstance?.roomId == room.RoomId;
|
|
}
|
|
|
|
private static bool CanEditRoom(RoomDBClasses.Room room, long accountId) =>
|
|
room.CreatorAccountId == accountId ||
|
|
room.Roles?.Any(value =>
|
|
value.AccountId == accountId &&
|
|
value.Role is RoomDBClasses.Role.Creator or
|
|
RoomDBClasses.Role.CoOwner or
|
|
RoomDBClasses.Role.TemporaryCoOwner) == true;
|
|
|
|
private static string ReadValueJson(
|
|
IReadOnlyDictionary<string, string> values)
|
|
{
|
|
if (values.TryGetValue("__raw", out string? requestRaw) &&
|
|
!string.IsNullOrWhiteSpace(requestRaw))
|
|
{
|
|
try
|
|
{
|
|
using JsonDocument document = JsonDocument.Parse(requestRaw);
|
|
JsonElement root = document.RootElement;
|
|
if (root.ValueKind == JsonValueKind.Object)
|
|
{
|
|
foreach (string propertyName in new[]
|
|
{
|
|
"valueJson", "ValueJson", "value",
|
|
"Value", "data", "Data"
|
|
})
|
|
{
|
|
if (TryGetJsonProperty(
|
|
root,
|
|
propertyName,
|
|
out JsonElement propertyValue))
|
|
{
|
|
return propertyValue.GetRawText();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return root.GetRawText();
|
|
}
|
|
}
|
|
catch (JsonException)
|
|
{
|
|
}
|
|
}
|
|
|
|
string? raw = ReadString(values,
|
|
"valueJson", "ValueJson", "value", "Value", "data", "Data");
|
|
if (raw == null && values.TryGetValue("__raw", out requestRaw))
|
|
raw = requestRaw;
|
|
if (string.IsNullOrWhiteSpace(raw))
|
|
return "null";
|
|
|
|
try
|
|
{
|
|
using JsonDocument document = JsonDocument.Parse(raw);
|
|
return document.RootElement.GetRawText();
|
|
}
|
|
catch (JsonException)
|
|
{
|
|
return JsonSerializer.Serialize(raw);
|
|
}
|
|
}
|
|
|
|
private static string? ReadString(
|
|
IReadOnlyDictionary<string, string> values,
|
|
params string[] keys)
|
|
{
|
|
foreach (string key in keys)
|
|
{
|
|
if (!values.TryGetValue(key, out string? value))
|
|
continue;
|
|
string cleaned = value.Trim();
|
|
if (cleaned.Length >= 2 && cleaned[0] == '"' && cleaned[^1] == '"')
|
|
{
|
|
try
|
|
{
|
|
return JsonSerializer.Deserialize<string>(cleaned);
|
|
}
|
|
catch (JsonException)
|
|
{
|
|
}
|
|
}
|
|
return cleaned;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private static int ReadInt(
|
|
IReadOnlyDictionary<string, string> values,
|
|
int fallback,
|
|
params string[] keys) =>
|
|
int.TryParse(ReadString(values, keys)?.Trim('"'), out int result)
|
|
? result
|
|
: fallback;
|
|
|
|
private static bool ReadBool(
|
|
IReadOnlyDictionary<string, string> values,
|
|
bool fallback,
|
|
params string[] keys)
|
|
{
|
|
string? raw = ReadString(values, keys)?.Trim('"');
|
|
if (bool.TryParse(raw, out bool result))
|
|
return result;
|
|
return int.TryParse(raw, out int number) ? number != 0 : fallback;
|
|
}
|
|
|
|
private bool ReadBoolFromQuery(bool fallback, params string[] keys)
|
|
{
|
|
foreach (string key in keys)
|
|
{
|
|
string? raw = Request.Query[key].FirstOrDefault();
|
|
if (bool.TryParse(raw, out bool result))
|
|
return result;
|
|
if (int.TryParse(raw, out int number))
|
|
return number != 0;
|
|
}
|
|
return fallback;
|
|
}
|
|
}
|
|
}
|