mirror of
https://github.com/JogmansonReal/Radicade.git
synced 2026-10-25 23:18:56 -07:00
Radicade Public Code
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Security.Claims;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using RadicadeServer.DB;
|
||||
using RadicadeServer.Extensions;
|
||||
using RadicadeServer.RecNet.Enums.Rooms;
|
||||
using RadicadeServer.Utils;
|
||||
|
||||
namespace RadicadeServer.API.Studio
|
||||
{
|
||||
[Route("studio")]
|
||||
public class StudioAPI : Controller
|
||||
{
|
||||
private sealed record StoredCloudBuild(long RoomId, long SubRoomId, StudioCloudBuild Build);
|
||||
private static readonly ConcurrentDictionary<string, StoredCloudBuild> CloudBuilds = new(StringComparer.Ordinal);
|
||||
|
||||
private readonly ILogger<StudioAPI> logger;
|
||||
private readonly StudioBuildWorker worker;
|
||||
private readonly IDbContextFactory<RecDBContext> dbFactory;
|
||||
|
||||
public StudioAPI(ILogger<StudioAPI> logger, StudioBuildWorker worker, IDbContextFactory<RecDBContext> dbFactory)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.worker = worker;
|
||||
this.dbFactory = dbFactory;
|
||||
}
|
||||
|
||||
[HttpGet("cloud-builds/for-room"), Authorize]
|
||||
public async Task<IActionResult> CloudBuildsForRoom(
|
||||
[FromQuery] long roomId,
|
||||
[FromQuery] long subRoomId,
|
||||
[FromQuery] int skip = 0,
|
||||
[FromQuery] int take = 30)
|
||||
{
|
||||
if (roomId <= 0 || subRoomId <= 0) return BadRequest();
|
||||
var accountId = CurrentAccountId();
|
||||
if (accountId == null) return Unauthorized();
|
||||
if (!await CanBuildInRoomAsync(roomId, accountId.Value, HttpContext.RequestAborted)) return Forbid();
|
||||
|
||||
logger.LogInformation("Studio cloud builds requested: room={RoomId}, subRoom={SubRoomId}, skip={Skip}, take={Take}.",
|
||||
roomId, subRoomId, skip, take);
|
||||
|
||||
return Ok(BuildPage(roomId, subRoomId, skip, take));
|
||||
}
|
||||
|
||||
private static object BuildPage(long roomId, long subRoomId, int skip, int take)
|
||||
{
|
||||
var builds = CloudBuilds.Values
|
||||
.Where(x => (roomId == 0 || x.RoomId == roomId) && (subRoomId == 0 || x.SubRoomId == subRoomId))
|
||||
.OrderByDescending(x => x.Build.StartedAt)
|
||||
.ToList();
|
||||
var page = builds.Skip(Math.Max(0, skip)).Take(Math.Clamp(take, 0, 100)).Select(x => x.Build).ToList();
|
||||
return new { Results = page, TotalResults = builds.Count, TotalCount = builds.Count };
|
||||
}
|
||||
|
||||
[HttpPost("cloud-builds/create"), Authorize]
|
||||
public async Task<IActionResult> CreateCloudBuild(
|
||||
[FromQuery] long? roomId = null,
|
||||
[FromQuery] long? subRoomId = null,
|
||||
[FromQuery] long? subRoomDataSaveId = null,
|
||||
[FromQuery] bool? autoPublish = null,
|
||||
[FromQuery] string? buildBranch = null)
|
||||
{
|
||||
|
||||
|
||||
var form = Request.HasFormContentType
|
||||
? await Request.ReadFormAsync(HttpContext.RequestAborted)
|
||||
: null;
|
||||
long? FormLong(string key) => long.TryParse(form?[key].ToString(), System.Globalization.NumberStyles.Integer,
|
||||
System.Globalization.CultureInfo.InvariantCulture, out var value) ? value : null;
|
||||
bool? FormBool(string key) => bool.TryParse(form?[key].ToString(), out var value) ? value : null;
|
||||
roomId ??= FormLong("roomId");
|
||||
subRoomId ??= FormLong("subRoomId");
|
||||
subRoomDataSaveId ??= FormLong("subRoomDataSaveId");
|
||||
autoPublish ??= FormBool("autoPublish");
|
||||
buildBranch ??= form?["buildBranch"].ToString();
|
||||
|
||||
if (roomId is not > 0 || subRoomId is not > 0 || subRoomDataSaveId is not > 0)
|
||||
return BadRequest(new { Success = false, Error = "roomId, subRoomId and subRoomDataSaveId are required", Value = (object?)null });
|
||||
|
||||
var resolvedRoomId = roomId.Value;
|
||||
var resolvedSubRoomId = subRoomId.Value;
|
||||
var resolvedSaveId = subRoomDataSaveId.Value;
|
||||
var resolvedAutoPublish = autoPublish ?? false;
|
||||
var resolvedBuildBranch = buildBranch;
|
||||
|
||||
var accountId = CurrentAccountId();
|
||||
if (accountId == null || accountId > int.MaxValue)
|
||||
return Unauthorized(new { Success = false, Error = "The Studio account could not be resolved.", Value = (object?)null });
|
||||
|
||||
await using (var db = await dbFactory.CreateDbContextAsync(HttpContext.RequestAborted))
|
||||
{
|
||||
var room = await db.Rooms.AsNoTracking().FirstOrDefaultAsync(x => x.RoomId == resolvedRoomId, HttpContext.RequestAborted);
|
||||
var canBuild = room != null && (room.CreatorAccountId == accountId.Value ||
|
||||
await db.RoomRoles.AsNoTracking().AnyAsync(x => x.RoomId == resolvedRoomId && x.AccountId == accountId.Value && x.Role >= RoleType.CoOwner,
|
||||
HttpContext.RequestAborted));
|
||||
if (!canBuild)
|
||||
return Forbid();
|
||||
}
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
var build = new StudioCloudBuild(
|
||||
Guid.NewGuid().ToString("N"), now, null,
|
||||
null, string.Empty, (int)accountId);
|
||||
CloudBuilds[build.CloudBuildId] = new StoredCloudBuild(resolvedRoomId, resolvedSubRoomId, build);
|
||||
var job = new StudioBuildWorker.BuildJob(build.CloudBuildId, resolvedRoomId, resolvedSubRoomId,
|
||||
resolvedSaveId, accountId.Value, dbFactory, build, updated =>
|
||||
CloudBuilds.AddOrUpdate(build.CloudBuildId,
|
||||
_ => new StoredCloudBuild(resolvedRoomId, resolvedSubRoomId, updated),
|
||||
(_, existing) => existing with { Build = updated }));
|
||||
worker.Enqueue(job);
|
||||
logger.LogInformation("Queued local Studio Windows build: room={RoomId}, subRoom={SubRoomId}, save={SaveId}, autoPublish={AutoPublish}, branch={BuildBranch}, cloudBuild={CloudBuildId}.",
|
||||
resolvedRoomId, resolvedSubRoomId, resolvedSaveId, resolvedAutoPublish, resolvedBuildBranch, build.CloudBuildId);
|
||||
|
||||
return Ok(new { Success = true, Error = (string?)null, Value = build });
|
||||
}
|
||||
|
||||
[HttpGet("cloud-builds"), Authorize]
|
||||
public async Task<IActionResult> CloudBuild([FromQuery] string cloudBuildId)
|
||||
{
|
||||
if (!CloudBuilds.TryGetValue(cloudBuildId, out var stored)) return NotFound();
|
||||
var accountId = CurrentAccountId();
|
||||
if (accountId == null) return Unauthorized();
|
||||
if (!await CanBuildInRoomAsync(stored.RoomId, accountId.Value, HttpContext.RequestAborted)) return Forbid();
|
||||
return Ok(stored.Build);
|
||||
}
|
||||
|
||||
private long? CurrentAccountId()
|
||||
{
|
||||
if (long.TryParse(User.FindFirstValue("userId"), out var userId) && userId > 0) return userId;
|
||||
if (long.TryParse(User.FindFirstValue(ClaimTypes.NameIdentifier), out var identifier) && identifier > 0) return identifier;
|
||||
return null;
|
||||
}
|
||||
|
||||
private async Task<bool> CanBuildInRoomAsync(long roomId, long accountId, CancellationToken cancellationToken)
|
||||
{
|
||||
await using var db = await dbFactory.CreateDbContextAsync(cancellationToken);
|
||||
var room = await db.Rooms.AsNoTracking().FirstOrDefaultAsync(x => x.RoomId == roomId, cancellationToken);
|
||||
return room != null && (room.CreatorAccountId == accountId ||
|
||||
await db.RoomRoles.AsNoTracking().AnyAsync(x => x.RoomId == roomId && x.AccountId == accountId && x.Role >= RoleType.CoOwner,
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("collaboration/owners-in-room")]
|
||||
public IActionResult OwnersInRoom([FromQuery] long roomId)
|
||||
{
|
||||
logger.LogInformation("Studio owners-in-room requested: room={RoomId}; answering an empty owner list.", roomId);
|
||||
return Ok(new { Success = true, Error = (string?)null, Value = Array.Empty<int>() });
|
||||
}
|
||||
|
||||
[HttpGet("analytics/rudderstack-api-key")]
|
||||
public IActionResult RudderstackApiKey()
|
||||
{
|
||||
|
||||
|
||||
|
||||
return Ok(new { key = string.Empty, value = string.Empty });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed record StudioCloudBuild(
|
||||
string CloudBuildId,
|
||||
DateTime StartedAt,
|
||||
DateTime? CompletedAt,
|
||||
string? Error,
|
||||
string UnityAssetId,
|
||||
int CreatedByAccountId);
|
||||
Reference in New Issue
Block a user