using DeluxeBackend.Extensions; using DeluxeBackend.Models; using DeluxeBackend.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Newtonsoft.Json.Linq; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Numerics; using System.Security.Principal; using System.Text.Json; using System.Text.Json.Serialization; using static DeluxeBackend.Enums; using static DeluxeBackend.Extensions.RoomExtensions; namespace DeluxeBackend.Controllers { [Route("Rooms")] [ApiController] public class RoomsController : ControllerBase { private readonly ILiteDbService db; private readonly IJwtService jwt; private readonly ICdnService cdn; private readonly DiscordBotService discord; private readonly bool setup = false; public RoomsController(ILiteDbService _db, IJwtService _jwtService, DiscordBotService _discord, ICdnService _cdn) { db = _db; jwt = _jwtService; discord = _discord; cdn = _cdn; if (!setup) { setup = true; var json = System.IO.File.ReadAllText(Path.Combine("data", "AGRoomRuntimeConfig.json")); AGRoomRuntimeConfig activityRuntimeConfig = JsonSerializer.Deserialize(json) ?? throw new Exception("Failed to deserialize"); Account mainPlayer = db.Accounts.FindById(1) ?? throw new Exception("player not found"); foreach (AGRoomRuntimeConfig.Room room in activityRuntimeConfig.Rooms) { if (db.Rooms.FindOne(x => x.Name == room.Name) != null) { continue; } Room newRoom = new Room { Accessibility = room.Accessibility, Name = room.Name, Description = room.Description, CloningAllowed = room.CloningAllowed, Creator = mainPlayer, CustomWarning = room.CustomRoomWarning, WarningMask = room.RoomWarningMask, DisableMicAutoMute = room.DisableMicAutoMute, DisableRoomComments = room.DisableRoomComments, EncryptVoiceChat = false, IsDorm = room.Name == "DormRoom", IsRRO = true, }; RoomSupports RoomSup = RoomSupports.None; if (room.SupportsScreens) RoomSup |= RoomSupports.Screens; if (room.SupportsWalkVR) RoomSup |= RoomSupports.WalkVR; if (room.SupportsTeleportVR) RoomSup |= RoomSupports.TeleportVR; RoomSup |= RoomSupports.Juniors; newRoom.SupportedPlayerTypes = RoomSup; db.Rooms.Insert(newRoom); foreach (var scene in room.Scenes) { SubRoom newScene = new() { Room = newRoom, LocationId = scene.RoomSceneLocationId, Name = scene.Name, IsSandbox = scene.IsSandbox, MaxPlayers = scene.MaxPlayers, CanMatchmakeInto = scene.CanMatchmakeInto, SupportsJoinInProgress = scene.SupportsJoinInProgress, UseLevelBasedMatchmaking = scene.UseLevelBasedMatchmaking, UseAgeBasedMatchmaking = scene.UseAgeBasedMatchmaking, UseRecRoyaleMatchmaking = scene.UseRecRoyaleMatchmaking }; db.SubRooms.Insert(newScene); } } } } [HttpGet("rooms/hot")] public async Task Hot([FromQuery] string? tag, [FromQuery] int skip = 0, [FromQuery] int take = 32) { var query = db.Rooms.Query().Where(x => x.Accessibility == RoomAccessibility.Public); if (!string.IsNullOrWhiteSpace(tag)) { if (tag.ToLower() == "rro") { query = query.Where(x => x.IsRRO == true); } } var roomEntities = query.OrderByDescending(x => x.Stats.VisitCount).Skip(skip).Limit(take).ToList(); var tasks = roomEntities.Select(async p => await p.ToDictionary(discord, db)); var rooms = (await Task.WhenAll(tasks)).ToList(); return Ok(new { Results = rooms, TotalResults = rooms.Count }); } [HttpGet("rooms/{roomId}/subrooms/{subroomId}/saves")] [Authorize] public async Task RoomSaves([FromRoute] int roomId, [FromRoute] int subroomId, [FromQuery] int skip, [FromQuery] int take) { Account? account = await jwt.GetLogin(User); if (account == null) return NotFound("User not found in database"); Room? room = db.Rooms.Include(x => x.Creator).FindById(roomId); if (room == null) return NotFound(); if (!room.HasRole(account.Id, RoomRoleType.CoOwner)) { return StatusCode(403); } SubRoom? subRoom = db.SubRooms.Include(x => x.Room).FindById(subroomId); if (subRoom == null) return NotFound(); if (subRoom.Room.Id != roomId) { return NotFound(); } var saves = db.SubRoomSave.Find(x => x.SubRoomId == subroomId).OrderByDescending(x => x.Id); List subRoomSaves = saves.Skip(skip).Take(take).ToList(); return Ok(new { Results = subRoomSaves.Select(x => x.ToDictionary()).ToList(), TotalResults = saves.Count() }); } [HttpPut("rooms/{roomId}/subrooms/{subroomId}/permissions")] [Authorize(Roles = "gameClient")] public async Task Permissions([FromRoute] int roomId, [FromRoute] int subroomId) { return Ok(new { Success = true }); } /*[HttpGet("subroom/{subroomId}/MakeSAVE")] public async Task MakeSave([FromRoute] int subroomId, [FromQuery] string DataBlob, [FromQuery] string? UnityAssetId = null) { SubRoom? room = db.SubRooms.FindById(subroomId); if (room == null) return NotFound(); SubRoomSave Save = new SubRoomSave() { DataBlob = DataBlob, SubRoomId = subroomId, UnityAssetId = UnityAssetId }; db.SubRoomSave.Insert(Save); room.CurrentSave = Save; db.SubRooms.Update(room); return Ok(new { hiii = "meow :3" }); }*/ [HttpGet("rooms/createdby/me")] [Authorize] public async Task CreatedbyMe() { Account? account = await jwt.GetLogin(User); if (account == null) return NotFound("User not found in database"); List> response = []; var foundRooms = db.Rooms.Include(x => x.Creator).Find(x => x.Creator != null && x.Creator.Id == account.Id); foreach (var room in foundRooms) { response.Add(await room.ToDictionary(discord, db, RoomDetailsMask.None)); } return Ok(response); } [HttpGet("rooms/ownedby/me")] [Authorize] public async Task OwnedbybyMe() { Account? account = await jwt.GetLogin(User); if (account == null) return NotFound("User not found in database"); List> response = []; var foundRooms = db.Rooms.Include(x => x.Creator) .FindAll() .Where(x => (x.Creator != null && x.Creator.Id == account.Id) || (x.Roles != null && x.Roles.Any(r => r.AccountId == account.Id && r.Role >= RoomRoleType.CoOwner)) ); foreach (var room in foundRooms) { response.Add(await room.ToDictionary(discord, db, RoomDetailsMask.None)); } return Ok(response); } [HttpGet("photon_access_token")] [Authorize(Roles = "gameClient")] public async Task PhotonAccessToken() { Account? account = await jwt.GetLogin(User); if (account == null) return NotFound("User not found in database"); AccountPresence presence = db.Presences.Include(x => x.Account) .Include(x => x.Instance) .Include(x => x.Instance!.SubRoom) .Include(x => x.Instance!.SubRoom!.Room) .Include(x => x.Instance!.SubRoom!.Room!.Creator) .FindOne(x => x.Account.Id == account.Id); if (presence == null) return NotFound(""); if (presence.Instance == null) return BadRequest("Not in a Instance"); return Ok(new { RoomInstanceId = presence.Instance.Id, PhotonAccessToken = "Meow", Permissions = new List() }); } [HttpGet("unity_assets/{UnityAssetId}/{Target}/{Version}")] [Authorize(Roles = "gameClient")] public async Task UnityAssetss([FromRoute] Guid UnityAssetId, [FromRoute] AssetBundleType Target, AssetBundleVersion Version) { if (Target == AssetBundleType.OculusQuest) { Target = AssetBundleType.MobileAndroid; } UnityAssets unityAssets = db.UnityAssets.FindById(UnityAssetId); if (unityAssets == null) return NotFound(); UnityAsset? unityAsset = unityAssets.Assets.FirstOrDefault(u => u.Target == Target && u.Version == Version); if (unityAsset == null) return NotFound(); return Ok(new { UnityAssetId, Target, Version, unityAsset.Filename }); } [HttpGet("rooms/ownedby/{accId}")] public async Task RoomsOwnedby(long accId) { List> response = []; var foundRooms = db.Rooms.Include(x => x.Creator) .FindAll() .Where(x => x.Accessibility == RoomAccessibility.Public && ( (x.Creator != null && x.Creator.Id == accId) || (x.Roles != null && x.Roles.Any(r => r.AccountId == accId && r.Role >= RoomRoleType.CoOwner)) ) ); foreach (var room in foundRooms) { response.Add(await room.ToDictionary(discord, db, RoomDetailsMask.None)); } return Ok(response); } [HttpGet("rooms/base")] [Authorize(Roles = "gameClient")] public async Task Base() { List> response = []; var foundRooms = db.Rooms.Include(x => x.Creator).Find(x => x.Accessibility == RoomAccessibility.Unlisted && x.CloningAllowed == true); foreach (var room in foundRooms) { response.Add(await room.ToDictionary(discord, db, RoomDetailsMask.None)); } return Ok(response); } [HttpPost("rooms/{roomId}/clone")] [Authorize(Roles = "gameClient")] public async Task Clone(long roomId, [FromForm, Required] string name) { LoginWithInfoResult? login = await jwt.GetLoginWithInfo(User); if (login == null) return Unauthorized(); Account account = login.Account; Room? room = db.Rooms.Include(x => x.Creator).FindById(roomId); if (room == null) return NotFound(); if (string.IsNullOrWhiteSpace(name) || name.Length < 3 || name.Length > 32) { return BadRequest(new { Success = false, Error = "Room name must be between 3 and 32 characters." }); } if (!room.CloningAllowed && !room.HasRole(account.Id, RoomRoleType.CoOwner)) { return Forbid(); } if (db.Rooms.FindOne(x => x.Name.Equals(name, StringComparison.OrdinalIgnoreCase)) != null) { return Conflict(new { Success = false, Error = $"The room name '{name}' is already taken." }); } Room newRoom = new() { Name = name.Trim(), SupportedPlayerTypes = room.SupportedPlayerTypes, Accessibility = RoomAccessibility.Private, ImageName = room.ImageName, Description = room.Description, Creator = account, CustomWarning = room.CustomWarning, WarningMask = room.WarningMask, DataBlob = room.DataBlob, DisableMicAutoMute = room.DisableMicAutoMute, DisableRoomComments = room.DisableRoomComments, EncryptVoiceChat = room.EncryptVoiceChat, MinLevel = room.MinLevel, PersistenceVersion = room.PersistenceVersion }; db.Rooms.Insert(newRoom); foreach (SubRoom subRoom in db.SubRooms.Include(x => x.Room).Include(x => x.CurrentSave).Find(x => x.Room.Id == roomId)) { SubRoom newSubroom = new() { Room = newRoom, LocationId = subRoom.LocationId, Name = subRoom.Name, IsSandbox = subRoom.IsSandbox, MaxPlayers = subRoom.MaxPlayers, CanMatchmakeInto = subRoom.CanMatchmakeInto, SupportsJoinInProgress = subRoom.SupportsJoinInProgress, UseLevelBasedMatchmaking = subRoom.UseLevelBasedMatchmaking, UseAgeBasedMatchmaking = subRoom.UseAgeBasedMatchmaking, UseRecRoyaleMatchmaking = subRoom.UseRecRoyaleMatchmaking }; db.SubRooms.Insert(newSubroom); if (subRoom.CurrentSave != null) { SubRoomSave newSave = new() { SubRoomId = newSubroom.Id, DataBlob = subRoom.CurrentSave.DataBlob, SavedByAccountId = account.Id, SavedOnPlatform = login.Platform, SavedOnDeviceClass = login.DeviceClass, Description = $"Cloned From ^{room.Name}", UnityAssetId = subRoom.CurrentSave.UnityAssetId, PersistenceVersion = subRoom.CurrentSave.PersistenceVersion }; db.SubRoomSave.Insert(newSave); newSubroom.CurrentSave = newSave; db.SubRooms.Update(newSubroom); } } return Ok(new { Success = true, Value = await newRoom.ToDictionary(discord, db, RoomDetailsMask.StandardDetails) }); } [HttpGet("rooms/moderatedby/me")] public async Task ModerateByNe() { Account? account = await jwt.GetLogin(User); if (account == null) return NotFound("User not found in database"); List> response = []; var foundRooms = db.Rooms.Include(x => x.Creator) .FindAll() .Where(x => (x.Creator != null && x.Creator.Id == account.Id) || (x.Roles != null && x.Roles.Any(r => r.AccountId == account.Id && r.Role >= RoomRoleType.Moderator)) ); foreach (var room in foundRooms) { response.Add(await room.ToDictionary(discord, db, RoomDetailsMask.None)); } return Ok(response); } public class UploadDTO { [JsonPropertyName("Filename")] public required string filename { get; set; } [JsonPropertyName("Hash")] public required string hash { get; set; } [JsonPropertyName("OwnershipProof")] public required string ownershipProof { get; set; } } public class SaveSubroomDTO { [JsonPropertyName("AutoPublish")] public bool autoPublish { get; set; } = false; [JsonPropertyName("Description")] public string? description { get; set; } = null; [JsonPropertyName("RoomData")] public required UploadDTO roomData { get; set; } [JsonPropertyName("SubRoomData")] public required UploadDTO SubRoomData { get; set; } [JsonPropertyName("UnityAssetId")] public string? unityAssetId { get; set; } = null; [JsonPropertyName("PersistenceVersion")] public int persistenceVersion { get; set; } } [HttpPost("rooms/{roomId}/subrooms/{subroomId}/data")] [Authorize(Roles = "gameClient")] public async Task SubRoonData(long roomId, long subroomId, [FromBody, Required] SaveSubroomDTO request) { LoginWithInfoResult? login = await jwt.GetLoginWithInfo(User); if (login == null) return Unauthorized(); Account account = login.Account; Room? room = db.Rooms.Include(x => x.Creator).FindById(roomId); if (room == null) return NotFound(); if (!room.HasRole(account.Id, RoomRoleType.CoOwner)) { return StatusCode(403); } SubRoom? subRoom = db.SubRooms.Include(x => x.Room).FindById(subroomId); if (subRoom == null) return NotFound(); if (subRoom.Room.Id != roomId) { return NotFound(); } if (request.roomData.ownershipProof != CdnService.GenerateOwnershipProof(account, request.roomData.filename)) { return Ok(new { Success = false, Error = "Room Ownership Proof Mismatch!" }); } if (request.SubRoomData.ownershipProof != CdnService.GenerateOwnershipProof(account, request.SubRoomData.filename)) { return Ok(new { Success = false, Error = "Subroom Ownership Proof Mismatch!" }); } if (room.Accessibility == RoomAccessibility.Private) { request.autoPublish = true; } SubRoomSave save = new() { SubRoomId = subroomId, DataBlob = request.SubRoomData.filename, SavedByAccountId = account.Id, Description = request.description, UnityAssetId = request.unityAssetId, SavedOnPlatform=login.Platform, SavedOnDeviceClass=login.DeviceClass, PersistenceVersion = request.persistenceVersion, }; db.SubRoomSave.Insert(save); if (request.autoPublish) { subRoom.CurrentSave = save; db.SubRooms.Update(subRoom); } room.DataBlob = request.roomData.filename; room.PersistenceVersion = request.persistenceVersion; db.Rooms.Update(room); return Ok(new { Success = true, Value = new Dictionary { ["Room"] = await room.ToDictionary(discord, db, RoomDetailsMask.StandardDetails), ["SubRoomDataSave"] = save.ToDictionary()} }); } [HttpGet("rooms/visitedby/{accId}")] [Authorize] public async Task VisitedBy(long accId) { Account? account = db.Accounts.FindById(accId); if (account == null) return NotFound(); if (!account.IsRecentHistoryVisible) { return StatusCode(403); } List> response = []; var visitedRoomIds = account.VisitedRooms.Keys.ToList(); if (visitedRoomIds.Count > 0) { var foundRooms = db.Rooms.Include(x => x.Creator) .FindAll() .Where(x => visitedRoomIds.Contains(x.Id)) .ToList().Where(room => room.Accessibility == RoomAccessibility.Public).OrderByDescending(room => account.VisitedRooms.TryGetValue(room.Id, out var date) ? date : DateTime.MinValue); foreach (var room in foundRooms) { response.Add(await room.ToDictionary(discord, db, RoomDetailsMask.None)); } } return Ok(response); } [HttpGet("rooms/visitedby/me")] [Authorize] public async Task VisitedByMe() { Account? account = await jwt.GetLogin(User); if (account == null) return Unauthorized(); List> response = []; var visitedRoomIds = account.VisitedRooms.Keys.ToList(); if (visitedRoomIds.Count > 0) { var foundRooms = db.Rooms.Include(x => x.Creator) .FindAll() .Where(x => visitedRoomIds.Contains(x.Id)) .ToList().Where(room => room.Accessibility == RoomAccessibility.Public || room.HasRole(account.Id, RoomRoleType.CoOwner)).OrderByDescending(room => account.VisitedRooms.TryGetValue(room.Id, out var date) ? date : DateTime.MinValue); foreach (var room in foundRooms) { response.Add(await room.ToDictionary(discord, db, RoomDetailsMask.None)); } } return Ok(response); } } }