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,1860 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SixLabors.ImageSharp;
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using RadicadeServer.API.Notifications;
|
||||
using RadicadeServer.DB;
|
||||
using RadicadeServer.Extensions;
|
||||
using RadicadeServer.RecNet;
|
||||
using RadicadeServer.RecNet.Enums;
|
||||
using RadicadeServer.RecNet.Enums.Messages;
|
||||
using RadicadeServer.RecNet.Enums.Rooms;
|
||||
using RadicadeServer.RecNet.Messages;
|
||||
using RadicadeServer.RecNet.Notifications;
|
||||
using RadicadeServer.RecNet.Responses;
|
||||
using RadicadeServer.RecNet.Rooms;
|
||||
using RadicadeServer.API.Studio;
|
||||
using RadicadeServer.Radicade.Discord;
|
||||
using RadicadeServer.Radicade.Discord.Templates;
|
||||
using RadicadeServer.Utils;
|
||||
|
||||
namespace RadicadeServer.RoomServer
|
||||
{
|
||||
[Route("rooms/")]
|
||||
public class RoomServerAPI : Controller
|
||||
{
|
||||
private const int RoomSearchHardLimit = 50;
|
||||
|
||||
RecDBContext db;
|
||||
public RoomServerAPI(RecDBContext context)
|
||||
{
|
||||
this.db = context;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[HttpGet("showcase/{playerId}")]
|
||||
public async Task<ActionResult<long[]>> GetPlayerShowcaseRooms(long playerId)
|
||||
{
|
||||
var roomIds = await db.RoomRoles
|
||||
.Where(x => x.AccountId == playerId && x.Role >= RoleType.CoOwner)
|
||||
.Select(x => x.RoomId)
|
||||
.ToListAsync();
|
||||
|
||||
if (roomIds.Count == 0)
|
||||
{
|
||||
roomIds = await db.Rooms
|
||||
.Where(x => x.CreatorAccountId == playerId && x.Accessibility >= Accessibility.Public)
|
||||
.Select(x => x.RoomId)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
var showcaseRooms = await RoomUtils.VisibleRooms(
|
||||
await db.Rooms.Where(x => roomIds.Contains(x.RoomId)).ToListAsync(),
|
||||
User.GetUserId());
|
||||
|
||||
return Ok(showcaseRooms.Select(x => x.RoomId).Distinct().ToArray());
|
||||
}
|
||||
|
||||
[HttpDelete("rooms/{roomId}"), Authorize]
|
||||
public async Task<Success> DeleteRoom(long roomId)
|
||||
{
|
||||
var userId = User.GetUserId();
|
||||
|
||||
var room = await db.Rooms.FirstOrDefaultAsync(x => x.RoomId == roomId);
|
||||
if (room == null) return new Success(false, "Room does not exist.", null);
|
||||
|
||||
var canModify = await MatchmakingUtils.IsAtLeastRole((long)userId!, roomId, RoleType.Creator);
|
||||
if (!canModify) return Success.Error("Insufficient permissions");
|
||||
|
||||
var roles = await db.RoomRoles.Where(x => x.RoomId == roomId).ToListAsync();
|
||||
foreach (var role in roles)
|
||||
{
|
||||
db.RoomRoles.Remove(role);
|
||||
}
|
||||
|
||||
var interactions = await db.RoomInteractions.Where(x => x.RoomId == roomId).ToListAsync();
|
||||
foreach (var i in interactions)
|
||||
{
|
||||
db.RoomInteractions.Remove(i);
|
||||
}
|
||||
|
||||
var keys = await db.RoomKeys.Where(x => x.RoomId == roomId).ToListAsync();
|
||||
foreach (var key in keys)
|
||||
{
|
||||
db.RoomKeys.Remove(key);
|
||||
}
|
||||
|
||||
var stats = await db.RoomStats.FirstOrDefaultAsync(x => x.RoomId == roomId);
|
||||
if (stats != null)
|
||||
db.RoomStats.Remove(stats);
|
||||
|
||||
var subrooms = await db.SubRooms.Where(x => x.RoomId == roomId).ToListAsync();
|
||||
foreach (var subroom in subrooms)
|
||||
{
|
||||
db.SubRooms.Remove(subroom);
|
||||
}
|
||||
|
||||
var prevSubrooms = await db.SubRoomHistory.Where(x => x.RoomId == roomId).ToListAsync();
|
||||
foreach (var subroom in prevSubrooms)
|
||||
{
|
||||
db.SubRoomHistory.Remove(subroom);
|
||||
}
|
||||
|
||||
db.Rooms.Remove(room);
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
return new Success(true, null, null);
|
||||
}
|
||||
|
||||
[HttpDelete("rooms/{roomId}/subrooms/{subroomId}"), Authorize]
|
||||
public async Task<Success> DeleteSubRoom(long roomId, long subroomId)
|
||||
{
|
||||
var userId = User.GetUserId();
|
||||
|
||||
var room = await db.Rooms.FirstOrDefaultAsync(x => x.RoomId == roomId);
|
||||
if (room == null) return new Success(false, "Room does not exist.", null);
|
||||
|
||||
var subroom = await db.SubRooms.FirstOrDefaultAsync(x => x.SubRoomId == subroomId && x.RoomId == roomId);
|
||||
if (subroom == null) return new Success(false, "Subroom does not exist.", null);
|
||||
|
||||
var canModify = await MatchmakingUtils.IsAtLeastRole((long)userId!, roomId, RoleType.Creator);
|
||||
if (!canModify) return Success.Error("Insufficient permissions");
|
||||
|
||||
var history = await db.SubRoomHistory.Where(x => x.RoomId == roomId && x.SubRoomId == subroomId).ToListAsync();
|
||||
foreach (var historyItem in history)
|
||||
{
|
||||
db.SubRoomHistory.Remove(historyItem);
|
||||
}
|
||||
|
||||
db.SubRooms.Remove(subroom);
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
var fullRoom = await RoomUtils.GetRoom(roomId);
|
||||
await Notify.SendNotify((long)userId!, new Notification { Id = "RoomUpdate", Msg = fullRoom });
|
||||
|
||||
return new Success(true, null, null);
|
||||
}
|
||||
|
||||
[HttpGet("rooms/{roomId}/playerdata/me")]
|
||||
public ActionResult<RoomPlayerData> GetRoomPlayerData()
|
||||
{
|
||||
return Ok(new RoomPlayerData { data = "" });
|
||||
}
|
||||
|
||||
[HttpGet("rooms/curated_playlists")]
|
||||
public async Task<IActionResult> GetCuratedPlaylists()
|
||||
{
|
||||
var playlistIds = await db.Playlists
|
||||
.OrderBy(x => x.PlaylistId)
|
||||
.Select(x => x.PlaylistId)
|
||||
.ToListAsync();
|
||||
|
||||
if (playlistIds.Count == 0)
|
||||
playlistIds.Add(24226488);
|
||||
|
||||
return Ok(playlistIds.Distinct().ToArray());
|
||||
}
|
||||
|
||||
[HttpGet("rooms/moderatedby/me"), Authorize]
|
||||
public async Task<List<RoomData>?> GetRoomsModeratedByMe()
|
||||
{
|
||||
long? userId = User.GetUserId();
|
||||
if (userId == null) return null;
|
||||
|
||||
var roles = await db.RoomRoles.Where(x => x.AccountId == userId && x.Role >= RoleType.Moderator).ToListAsync();
|
||||
var rooms = new List<RoomData>();
|
||||
foreach (var role in roles)
|
||||
{
|
||||
var room = await db.Rooms.FirstOrDefaultAsync(x => x.RoomId == role.RoomId);
|
||||
if (room != null) rooms.Add(room);
|
||||
}
|
||||
|
||||
return await RoomUtils.VisibleRooms(rooms, userId);
|
||||
}
|
||||
|
||||
[HttpGet("rooms/visitedby/{playerId:long}"), Authorize]
|
||||
public async Task<IActionResult> GetVisitedRoomsForPlayer(long playerId, [FromQuery] int skip = 0, [FromQuery] int take = 10)
|
||||
{
|
||||
var viewerId = User.GetUserId();
|
||||
|
||||
skip = Math.Max(skip, 0);
|
||||
take = Math.Clamp(take, 1, 100);
|
||||
|
||||
var roomIds = await db.RoomInteractions
|
||||
.Where(x => x.PlayerId == playerId && x.Visited)
|
||||
.OrderByDescending(x => x.LastVisted)
|
||||
.Select(x => x.RoomId)
|
||||
.Distinct()
|
||||
.Skip(skip)
|
||||
.Take(take)
|
||||
.ToListAsync();
|
||||
|
||||
var rooms = new List<RoomData>();
|
||||
foreach (var roomId in roomIds)
|
||||
{
|
||||
var room = await db.Rooms.FirstOrDefaultAsync(x => x.RoomId == roomId);
|
||||
if (room != null) rooms.Add(room);
|
||||
}
|
||||
|
||||
rooms = await RoomUtils.VisibleRooms(rooms, viewerId);
|
||||
var convert = await RoomUtils.BulkConvertRoomDataToRoomDetail(rooms);
|
||||
return Ok(convert);
|
||||
}
|
||||
|
||||
[HttpGet("rooms/visitedby/me"), Authorize]
|
||||
public async Task<IActionResult> GetVisitedRooms([FromQuery] int skip = 0, [FromQuery] int take = 100)
|
||||
{
|
||||
long? userId = User.GetUserId();
|
||||
if (userId == null) return Unauthorized();
|
||||
|
||||
skip = Math.Max(skip, 0);
|
||||
take = Math.Clamp(take, 1, 100);
|
||||
|
||||
var roomIds = await db.RoomInteractions
|
||||
.Where(x => x.PlayerId == userId && x.Visited)
|
||||
.OrderByDescending(x => x.LastVisted)
|
||||
.Select(x => x.RoomId)
|
||||
.Distinct()
|
||||
.Skip(skip)
|
||||
.Take(take)
|
||||
.ToListAsync();
|
||||
|
||||
var rooms = new List<RoomData>();
|
||||
foreach (var roomId in roomIds)
|
||||
{
|
||||
var room = await db.Rooms.FirstOrDefaultAsync(x => x.RoomId == roomId);
|
||||
if (room != null) rooms.Add(room);
|
||||
}
|
||||
|
||||
rooms = await RoomUtils.VisibleRooms(rooms, userId);
|
||||
var convert = await RoomUtils.BulkConvertRoomDataToRoomDetail(rooms);
|
||||
return Ok(convert);
|
||||
}
|
||||
|
||||
[HttpGet("rooms/topcreators")]
|
||||
public async Task<List<RoomDetail>?> GetRoomsFromTopCreators()
|
||||
{
|
||||
var topCreators = await db.RoomInteractions.Where(i => i.Favorited)
|
||||
.Join(db.Rooms,
|
||||
interaction => interaction.RoomId,
|
||||
room => room.RoomId,
|
||||
(interaction, room) => new
|
||||
{
|
||||
room.CreatorAccountId
|
||||
}).GroupBy(x => x.CreatorAccountId)
|
||||
.Select(g => new
|
||||
{
|
||||
CreatorId = g.Key,
|
||||
FavoriteCount = g.Count()
|
||||
}).OrderByDescending(x => x.FavoriteCount).Take(10).Select(x => x.CreatorId).ToListAsync();
|
||||
|
||||
if (!topCreators.Any())
|
||||
return [];
|
||||
|
||||
var rooms = await db.Rooms.Where(r => topCreators.Contains(r.CreatorAccountId)).OrderByDescending(r => r.RoomId).Take(10 * 3).ToListAsync();
|
||||
rooms.RemoveAll(x => x.Accessibility != Accessibility.Public);
|
||||
|
||||
return await RoomUtils.BulkConvertRoomDataToRoomDetail(rooms);
|
||||
}
|
||||
|
||||
[HttpGet("rooms/bulk")]
|
||||
public async Task <List<RoomData>?> GetBulkRooms([FromQuery] string? name, [FromQuery] List<long>? id)
|
||||
{
|
||||
List<RoomData> rooms = new List<RoomData>();
|
||||
|
||||
if (name != null)
|
||||
{
|
||||
rooms = await db.Rooms.Where(x => x.Name.Contains(name)).ToListAsync();
|
||||
} else if (id != null)
|
||||
{
|
||||
rooms.Clear();
|
||||
foreach (var i in id)
|
||||
{
|
||||
var room = await db.Rooms.FirstOrDefaultAsync(x => x.RoomId == i);
|
||||
if (room != null)
|
||||
rooms.Add(room);
|
||||
}
|
||||
}
|
||||
|
||||
return await RoomUtils.VisibleRooms(rooms, User.GetUserId());
|
||||
}
|
||||
[HttpGet("featuredrooms/current")]
|
||||
public async Task<IActionResult> GetCurrentFeaturedRooms()
|
||||
{
|
||||
var candidates = await db.Rooms.AsNoTracking()
|
||||
.Where(x => x.State == RoomState.Active && x.Tags.ToLower().Contains("featured"))
|
||||
.OrderBy(x => x.RoomId)
|
||||
.ToListAsync();
|
||||
|
||||
var featured = candidates
|
||||
.Where(room => StringTools.ReadTags(room.Tags)
|
||||
.Any(tag => string.Equals(tag.Tag, "featured", StringComparison.OrdinalIgnoreCase)))
|
||||
.ToList();
|
||||
|
||||
featured = await RoomUtils.VisibleRooms(featured, User.GetUserId());
|
||||
|
||||
if (featured.Count == 0)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return Ok(new FeaturedRoomGroup
|
||||
{
|
||||
FeaturedRoomGroupId = 1,
|
||||
Name = "Featured Rooms",
|
||||
Rooms = featured.Select(room => new FeaturedRoom
|
||||
{
|
||||
RoomId = room.RoomId,
|
||||
RoomName = room.Name,
|
||||
ImageName = string.IsNullOrWhiteSpace(room.ImageName) ? room.Name : room.ImageName
|
||||
}).ToList()
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
[HttpGet("roomsandplaylists/search")]
|
||||
public async Task<RoomSearchResult?> GetBulkRoomsOld([FromQuery] string query)
|
||||
{
|
||||
if (query == null) return null;
|
||||
|
||||
query = query.Replace("#", "").Trim();
|
||||
|
||||
var rooms = await db.Rooms.Where(x => (x.Name.ToLower().Contains(query) || x.Tags.ToLower().Contains(query)) && x.Accessibility == Accessibility.Public).ToListAsync();
|
||||
var convert = await RoomUtils.BulkConvertRoomDataToRoomDetail(rooms);
|
||||
return new RoomSearchResult { TotalResults = rooms.Count, Results = convert };
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[HttpGet("rooms/search_rooms/{source}")]
|
||||
public async Task<IActionResult> SearchRoomsBySource(string source, [FromQuery] int skip = -1, [FromQuery] int take = 20)
|
||||
{
|
||||
var (src, pSkip, pTake) = ParseEmbeddedQuery(source, 20);
|
||||
if (skip < 0) skip = pSkip;
|
||||
if (take == 20 && pTake != 20) take = pTake;
|
||||
skip = Math.Max(skip, 0);
|
||||
take = Math.Clamp(take, 1, 100);
|
||||
|
||||
if (string.Equals(src, "MyClubhouses", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(src, "MyClubs", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(src, "Clubhouse", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return Ok(new RoomSearchResult { TotalResults = 0, Results = new List<RoomDetail>() });
|
||||
}
|
||||
|
||||
IQueryable<RoomData> q;
|
||||
if (!string.IsNullOrWhiteSpace(src) && !src.Contains('&'))
|
||||
{
|
||||
var kw = Uri.UnescapeDataString(src).Replace('+', ' ').Trim().Split(' ',
|
||||
StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
|
||||
.Select(k => k.TrimStart('#')).Where(k => k.Length > 0).ToList();
|
||||
q = db.Rooms.Where(x => x.Accessibility == Accessibility.Public && x.State == RoomState.Active
|
||||
&& kw.All(k => x.Name.ToLower().Contains(k) || x.Tags.ToLower().Contains(k)));
|
||||
}
|
||||
else
|
||||
{
|
||||
q = db.Rooms.Where(x => x.Accessibility == Accessibility.Public && x.State == RoomState.Active);
|
||||
}
|
||||
|
||||
var rooms = await q.OrderBy(x => x.RoomId).Skip(skip).Take(take).ToListAsync();
|
||||
var convert = await RoomUtils.BulkConvertRoomDataToRoomDetail(rooms);
|
||||
return Ok(new RoomSearchResult { TotalResults = convert.Count, Results = convert });
|
||||
}
|
||||
|
||||
private static (string Source, int Skip, int Take) ParseEmbeddedQuery(string? source, int defTake)
|
||||
{
|
||||
var src = source ?? string.Empty;
|
||||
int skip = 0, take = defTake;
|
||||
int amp = src.IndexOf('&');
|
||||
if (amp >= 0)
|
||||
{
|
||||
foreach (var pair in src.Substring(amp + 1).Split('&', StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
var kv = pair.Split('=', 2);
|
||||
if (kv.Length == 2 && int.TryParse(kv[1], out var n))
|
||||
{
|
||||
if (kv[0] == "skip") skip = n;
|
||||
else if (kv[0] == "take") take = n;
|
||||
}
|
||||
}
|
||||
src = src.Substring(0, amp);
|
||||
}
|
||||
return (Uri.UnescapeDataString(src), Math.Max(skip, 0), Math.Clamp(take, 1, 100));
|
||||
}
|
||||
|
||||
[HttpGet("rooms/hot_rooms/{*source}")]
|
||||
[HttpGet("hot_rooms/{*source}")]
|
||||
public async Task<IActionResult> GetHotRoomsPaged(string? source)
|
||||
{
|
||||
var (query, skip, take) = ParseEmbeddedQuery(source, 24);
|
||||
query = query.Trim();
|
||||
|
||||
IQueryable<RoomData> q = db.Rooms
|
||||
.Where(x => x.Accessibility == Accessibility.Public && x.State == RoomState.Active);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(query) && query != "all")
|
||||
{
|
||||
var tags = query.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
|
||||
.Select(t => t.TrimStart('#')).Where(t => t.Length > 0).ToList();
|
||||
if (tags.Count > 0)
|
||||
q = q.Where(x => tags.All(t => x.Tags.Contains(t)));
|
||||
}
|
||||
|
||||
var rooms = await q.OrderBy(x => x.RoomId).Skip(skip).Take(take).ToListAsync();
|
||||
if (rooms.Count == 0 && skip == 0)
|
||||
{
|
||||
rooms = await db.Rooms
|
||||
.Where(x => x.Accessibility == Accessibility.Public && x.State == RoomState.Active)
|
||||
.OrderBy(x => x.RoomId).Take(take).ToListAsync();
|
||||
}
|
||||
return Ok(await RoomUtils.BulkConvertRoomDataToRoomDetail(rooms));
|
||||
}
|
||||
|
||||
[HttpGet("rooms/carousel/{*source}")]
|
||||
public async Task<IActionResult> GetCarouselRooms(string? source, [FromQuery] int skip = -1, [FromQuery] int take = 20)
|
||||
{
|
||||
var (src, pSkip, pTake) = ParseEmbeddedQuery(source, 20);
|
||||
if (skip < 0) skip = pSkip;
|
||||
if (take == 20 && pTake != 20) take = pTake;
|
||||
skip = Math.Max(skip, 0);
|
||||
take = Math.Clamp(take, 1, 100);
|
||||
|
||||
var q = db.Rooms.Where(x => x.Accessibility == Accessibility.Public && x.State == RoomState.Active);
|
||||
|
||||
var keyword = src.Trim().TrimStart('#').ToLowerInvariant();
|
||||
if (keyword.Length > 0)
|
||||
q = q.Where(x => x.Name.ToLower().Contains(keyword) || x.Tags.ToLower().Contains(keyword));
|
||||
|
||||
var rooms = await q.OrderBy(x => x.RoomId).Skip(skip).Take(take).ToListAsync();
|
||||
|
||||
if (rooms.Count == 0 && skip == 0)
|
||||
{
|
||||
rooms = await db.Rooms
|
||||
.Where(x => x.Accessibility == Accessibility.Public && x.State == RoomState.Active)
|
||||
.OrderBy(x => x.RoomId).Take(take).ToListAsync();
|
||||
}
|
||||
|
||||
var convert = await RoomUtils.BulkConvertRoomDataToRoomDetail(rooms);
|
||||
return Ok(new RoomSearchResult { Results = convert, TotalResults = convert.Count });
|
||||
}
|
||||
|
||||
|
||||
[HttpGet("rooms/my_visited_rooms/{*source}"), Authorize]
|
||||
[HttpGet("my_visited_rooms/{*source}"), Authorize]
|
||||
public async Task<IActionResult> GetMyVisitedRoomsPaged(string? source)
|
||||
{
|
||||
var userId = User.GetUserId();
|
||||
if (!userId.HasValue) return Unauthorized();
|
||||
var (_, skip, take) = ParseEmbeddedQuery(source, 24);
|
||||
|
||||
var rooms = await db.Rooms
|
||||
.Where(x => db.RoomInteractions.Any(i => i.PlayerId == userId && i.RoomId == x.RoomId)
|
||||
&& x.Accessibility == Accessibility.Public)
|
||||
.OrderByDescending(x => x.RoomId).Skip(skip).Take(take).ToListAsync();
|
||||
return Ok(await RoomUtils.BulkConvertRoomDataToRoomDetail(rooms));
|
||||
}
|
||||
|
||||
[HttpGet("rooms/roomsbycreators/{*source}")]
|
||||
[HttpGet("roomsbycreators/{*source}")]
|
||||
public async Task<IActionResult> GetRoomsByCreatorsPaged(string? source)
|
||||
{
|
||||
var (ids, skip, take) = ParseEmbeddedQuery(source, 24);
|
||||
var creatorIds = ids.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
|
||||
.Select(s => long.TryParse(s, out var n) ? n : -1)
|
||||
.Where(n => n > 0).ToList();
|
||||
|
||||
var rooms = creatorIds.Count == 0
|
||||
? new List<RoomData>()
|
||||
: await db.Rooms.Where(x => creatorIds.Contains(x.CreatorAccountId)
|
||||
&& x.Accessibility == Accessibility.Public && x.State == RoomState.Active)
|
||||
.OrderBy(x => x.RoomId).Skip(skip).Take(take).ToListAsync();
|
||||
return Ok(await RoomUtils.BulkConvertRoomDataToRoomDetail(rooms));
|
||||
}
|
||||
|
||||
[HttpGet("rooms/rro_ids")]
|
||||
public async Task<IActionResult> GetRroRoomIds()
|
||||
{
|
||||
var ids = await db.Rooms.Where(x => x.IsRRO && x.Accessibility == Accessibility.Public)
|
||||
.OrderBy(x => x.RoomId).Select(x => x.RoomId).ToListAsync();
|
||||
return Ok(ids);
|
||||
}
|
||||
|
||||
[HttpGet("rooms/search")]
|
||||
public async Task<RoomSearchResult?> SearchRooms([FromQuery] string query)
|
||||
{
|
||||
if (query == null) return null;
|
||||
|
||||
query = query.ToLower();
|
||||
query = query.Replace("+", " ").Trim();
|
||||
query = query.Replace("#", "").Trim();
|
||||
|
||||
var keywords = query.Split(' ', StringSplitOptions.TrimEntries);
|
||||
|
||||
var rooms = await db.Rooms.Where(x =>
|
||||
keywords.All(k => (x.Name.ToLower().Contains(k) || x.Tags.ToLower().Contains(k)) && x.Accessibility == Accessibility.Public)
|
||||
).ToListAsync();
|
||||
var convert = await RoomUtils.BulkConvertRoomDataToRoomDetail(rooms);
|
||||
|
||||
return new RoomSearchResult { TotalResults = rooms.Count, Results = convert };
|
||||
}
|
||||
|
||||
[HttpGet("rooms/createdby/{who}"), Authorize]
|
||||
public async Task<IActionResult> GetRoomsBy(string who)
|
||||
{
|
||||
long? userId;
|
||||
|
||||
if (who == "me")
|
||||
userId = User.GetUserId();
|
||||
else
|
||||
userId = db.Profiles.FirstOrDefault(x => x.username == who)?.accountId;
|
||||
if (userId == null)
|
||||
userId = db.Profiles.FirstOrDefault(x => x.accountId.ToString() == who)?.accountId;
|
||||
if (userId == null) return Unauthorized();
|
||||
|
||||
var roles = await db.RoomRoles.Where(x => x.AccountId == userId && x.Role >= RoleType.CoOwner).ToListAsync();
|
||||
var rooms = new List<RoomData>();
|
||||
foreach (var role in roles)
|
||||
{
|
||||
var room = await db.Rooms.FirstOrDefaultAsync(x => x.RoomId == role.RoomId);
|
||||
if (room != null)
|
||||
rooms.Add(room);
|
||||
}
|
||||
|
||||
return Ok(await RoomUtils.BulkConvertRoomDataToRoomDetail(
|
||||
await RoomUtils.VisibleRooms(rooms, User.GetUserId())));
|
||||
}
|
||||
|
||||
[HttpGet("rooms/ownedby/{who}"), Authorize]
|
||||
public async Task<IActionResult> GetRoomsByOld(string who)
|
||||
{
|
||||
long? userId;
|
||||
|
||||
if (who == "me")
|
||||
userId = User.GetUserId();
|
||||
else
|
||||
userId = db.Profiles.FirstOrDefault(x => x.username == who)?.accountId;
|
||||
if (userId == null)
|
||||
userId = db.Profiles.FirstOrDefault(x => x.accountId.ToString() == who)?.accountId;
|
||||
if (userId == null) return Unauthorized();
|
||||
|
||||
var roomIds = await db.RoomRoles
|
||||
.Where(x => x.AccountId == userId && x.Role >= RoleType.CoOwner)
|
||||
.Select(x => x.RoomId)
|
||||
.Distinct()
|
||||
.ToListAsync();
|
||||
|
||||
var rooms = new List<RoomData>();
|
||||
foreach (var roomId in roomIds)
|
||||
{
|
||||
var room = await db.Rooms.FirstOrDefaultAsync(x => x.RoomId == roomId);
|
||||
if (room != null) rooms.Add(room);
|
||||
}
|
||||
|
||||
return Ok(await RoomUtils.BulkConvertRoomDataToRoomDetail(
|
||||
await RoomUtils.VisibleRooms(rooms, User.GetUserId())));
|
||||
}
|
||||
|
||||
[HttpGet("rooms/hot")]
|
||||
public async Task<RoomSearchResult?> GetHotRooms(string? tag)
|
||||
{
|
||||
|
||||
if (tag == null)
|
||||
{
|
||||
var list = await db.Rooms.Where(x => x.Accessibility == Accessibility.Public && x.Name != "DormRoom").ToListAsync();
|
||||
var bulkConvert = await RoomUtils.BulkConvertRoomDataToRoomDetail(list);
|
||||
return new RoomSearchResult { Results = bulkConvert, TotalResults = bulkConvert.Count };
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (tag)
|
||||
{
|
||||
default:
|
||||
var list1 = await db.Rooms.Where(x => x.Tags.Contains(tag) && x.Accessibility == Accessibility.Public && !x.IsDorm).ToListAsync();
|
||||
if (list1.Count == 0)
|
||||
{
|
||||
list1 = await db.Rooms.Where(x => x.Accessibility == Accessibility.Public && !x.IsDorm).ToListAsync();
|
||||
}
|
||||
var bulkConvert1 = await RoomUtils.BulkConvertRoomDataToRoomDetail(list1);
|
||||
return new RoomSearchResult { Results = bulkConvert1, TotalResults = bulkConvert1.Count };
|
||||
case "new":
|
||||
var list2 = await db.Rooms.Where(x => x.Accessibility == Accessibility.Public && !x.IsDorm).OrderByDescending(x => x.PublishedAt).ToListAsync();
|
||||
var bulkConvert2 = await RoomUtils.BulkConvertRoomDataToRoomDetail(list2);
|
||||
return new RoomSearchResult { Results = bulkConvert2, TotalResults = bulkConvert2.Count };
|
||||
case "rro":
|
||||
var list3 = await db.Rooms.Where(x => x.IsRRO && !x.IsDorm).OrderByDescending(x => x.PublishedAt).ToListAsync();
|
||||
var bulkConvert3 = await RoomUtils.BulkConvertRoomDataToRoomDetail(list3);
|
||||
return new RoomSearchResult { Results = bulkConvert3, TotalResults = bulkConvert3.Count };
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[HttpGet("roomsandplaylists/hot")]
|
||||
public async Task<RoomSearchResult?> GetHotRoomsOld()
|
||||
{
|
||||
|
||||
var list = await db.Rooms.Where(x => x.Accessibility == Accessibility.Public && x.Name != "DormRoom").ToListAsync();
|
||||
var convert = await RoomUtils.BulkConvertRoomDataToRoomDetail(list);
|
||||
|
||||
return new RoomSearchResult { Results = convert, TotalResults = convert.Count };
|
||||
}
|
||||
|
||||
[HttpPut("rooms/{id}/interactionby/me/favorite"), Authorize]
|
||||
public async Task<PlayerInteraction?> FavouriteRoom(long id)
|
||||
{
|
||||
var userId = User.GetUserId();
|
||||
|
||||
PlayerInteractionDB? interaction = await db.RoomInteractions.FirstOrDefaultAsync(x => x.PlayerId == userId && x.RoomId == id);
|
||||
if (interaction == null)
|
||||
{
|
||||
var favoriteResult = new PlayerInteractionDB { PlayerId = (long)userId!, RoomId = id, Cheered = false, Favorited = true };
|
||||
|
||||
await db.RoomInteractions.AddAsync(favoriteResult);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
interaction = favoriteResult;
|
||||
} else
|
||||
{
|
||||
interaction.Favorited = true;
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
return new PlayerInteraction(interaction);
|
||||
}
|
||||
[HttpDelete("rooms/{id}/interactionby/me/favorite"), Authorize]
|
||||
public async Task<PlayerInteraction?> UnfavouriteRoom(long id)
|
||||
{
|
||||
var userId = User.GetUserId();
|
||||
|
||||
PlayerInteractionDB? interaction = await db.RoomInteractions.FirstOrDefaultAsync(x => x.PlayerId == userId && x.RoomId == id);
|
||||
if (interaction == null)
|
||||
{
|
||||
var favoriteResult = new PlayerInteractionDB { PlayerId = (long)userId!, RoomId = id, Cheered = false, Favorited = false };
|
||||
|
||||
await db.RoomInteractions.AddAsync(favoriteResult);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
interaction = favoriteResult;
|
||||
}
|
||||
else
|
||||
{
|
||||
interaction.Favorited = false;
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
return new PlayerInteraction(interaction);
|
||||
}
|
||||
|
||||
[HttpPut("rooms/{id}/interactionby/me/cheer"), Authorize]
|
||||
public async Task<PlayerInteraction?> CheerRoom(long id)
|
||||
{
|
||||
var userId = User.GetUserId();
|
||||
|
||||
var interaction = await db.RoomInteractions.FirstOrDefaultAsync(x => x.PlayerId == userId && x.RoomId == id);
|
||||
if (interaction == null)
|
||||
{
|
||||
var favoriteResult = new PlayerInteractionDB { PlayerId = (long)userId!, RoomId = id, Cheered = true, Favorited = false };
|
||||
|
||||
await db.RoomInteractions.AddAsync(favoriteResult);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
interaction = favoriteResult;
|
||||
}
|
||||
else
|
||||
{
|
||||
interaction.Cheered = true;
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
return new PlayerInteraction(interaction);
|
||||
}
|
||||
[HttpDelete("rooms/{id}/interactionby/me/cheer"), Authorize]
|
||||
public async Task<PlayerInteraction?> UncheerRoom(long id)
|
||||
{
|
||||
var userId = User.GetUserId();
|
||||
|
||||
var interaction = await db.RoomInteractions.FirstOrDefaultAsync(x => x.PlayerId == userId && x.RoomId == id);
|
||||
if (interaction == null)
|
||||
{
|
||||
var favoriteResult = new PlayerInteractionDB { PlayerId = (long)userId!, RoomId = id, Cheered = false, Favorited = false };
|
||||
|
||||
await db.RoomInteractions.AddAsync(favoriteResult);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
interaction = favoriteResult;
|
||||
}
|
||||
else
|
||||
{
|
||||
interaction.Cheered = false;
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
return new PlayerInteraction(interaction);
|
||||
}
|
||||
|
||||
[HttpGet("rooms/favoritedby/{who}"), Authorize]
|
||||
public async Task<IActionResult> GetFavouritedBy(string who)
|
||||
{
|
||||
long? userId;
|
||||
if (who == "me")
|
||||
userId = User.GetUserId();
|
||||
else
|
||||
userId = db.Profiles.FirstOrDefault(x => x.username == who)?.accountId;
|
||||
if (userId == null)
|
||||
userId = db.Profiles.FirstOrDefault(x => x.accountId.ToString() == who)?.accountId;
|
||||
if (userId == null) return Unauthorized();
|
||||
|
||||
var roomIds = await db.RoomInteractions
|
||||
.Where(x => x.PlayerId == userId && x.Favorited)
|
||||
.OrderByDescending(x => x.FavoritedAt)
|
||||
.Select(x => x.RoomId)
|
||||
.Distinct()
|
||||
.ToListAsync();
|
||||
|
||||
var rooms = new List<RoomData>();
|
||||
foreach (var roomId in roomIds)
|
||||
{
|
||||
var room = await db.Rooms.FirstOrDefaultAsync(x => x.RoomId == roomId);
|
||||
if (room != null) rooms.Add(room);
|
||||
}
|
||||
|
||||
return Ok(await RoomUtils.BulkConvertRoomDataToRoomDetail(
|
||||
await RoomUtils.VisibleRooms(rooms, User.GetUserId())));
|
||||
}
|
||||
|
||||
[HttpGet("rooms/{id}/interactionby/{who}"), Authorize]
|
||||
public async Task<PlayerInteraction?> GetInteractionBy(long id, string who)
|
||||
{
|
||||
long? userId;
|
||||
if (who == "me")
|
||||
userId = User.GetUserId();
|
||||
else
|
||||
userId = db.Profiles.FirstOrDefault(x => x.username == who)?.accountId;
|
||||
if (userId == null)
|
||||
userId = db.Profiles.FirstOrDefault(x => x.accountId.ToString() == who)?.accountId;
|
||||
if (userId == null) return null;
|
||||
|
||||
var interaction = await db.RoomInteractions.FirstOrDefaultAsync(x => x.PlayerId == userId && x.RoomId == id);
|
||||
if (interaction == null) return new PlayerInteraction { Cheered = false, Favorited = false };
|
||||
|
||||
return new PlayerInteraction(interaction);
|
||||
}
|
||||
|
||||
[HttpGet("rooms/base")]
|
||||
public async Task<List<FullRoom>?> GetBaseRooms()
|
||||
{
|
||||
var roomData = await db.Rooms.Where(x => x.CloningAllowed && x.Accessibility != Accessibility.Private && x.CreatorAccountId == 1).ToListAsync();
|
||||
var rooms = new List<FullRoom>();
|
||||
|
||||
foreach (var room in roomData)
|
||||
{
|
||||
var fullRoom = await RoomUtils.GetRoom(room.RoomId);
|
||||
if (fullRoom == null) continue;
|
||||
rooms.Add(fullRoom);
|
||||
}
|
||||
|
||||
return rooms;
|
||||
}
|
||||
|
||||
[HttpPost("rooms/{id}/clone"), Authorize]
|
||||
public async Task<Success?> CloneRoom(long id, [FromForm] string name)
|
||||
{
|
||||
var userId = User.GetUserId();
|
||||
if (userId == null) return new Success(false, "You are not signed in.", null);
|
||||
|
||||
if (!name.IsPure()) return new Success(false, "Name contains offensive language.", null);
|
||||
|
||||
var source = await db.Rooms.AsNoTracking().FirstOrDefaultAsync(x => x.RoomId == id);
|
||||
if (source == null) return new Success(false, "Unknown error", null);
|
||||
|
||||
if (!source.CloningAllowed)
|
||||
{
|
||||
return new Success(false, "This room cannot be cloned.", null);
|
||||
}
|
||||
|
||||
if (await RoomUtils.IsHiddenFrom(source, userId))
|
||||
{
|
||||
return new Success(false, "This room cannot be cloned.", null);
|
||||
}
|
||||
|
||||
var doesRoomExist = (await db.Rooms.FirstOrDefaultAsync(x => x.Name == name)) != null;
|
||||
if (doesRoomExist) return new Success(false, "Room already exists.", null);
|
||||
|
||||
var roomData = await RoomUtils.CloneRoom(id, (long)userId!, name);
|
||||
if (roomData == null) return new Success(false, "Unknown error", null);
|
||||
|
||||
return new Success(true, null, roomData);
|
||||
}
|
||||
|
||||
private sealed record RoomPermissionRow(string Permission, long Role, bool Override, int Type, string Value);
|
||||
|
||||
private static List<RoomPermissionRow> DefaultPermissions()
|
||||
{
|
||||
static RoomPermissionRow Row(string permission, long role, bool overridden) =>
|
||||
new(permission, role, overridden, 0, "True");
|
||||
|
||||
return new List<RoomPermissionRow>
|
||||
{
|
||||
Row("CAN_USE_ROOM_RESET_BUTTON", 0, true),
|
||||
Row("CAN_USE_DELETE_ALL_BUTTON", 0, true),
|
||||
Row("CAN_SAVE_INVENTIONS", 0, true),
|
||||
Row("CAN_SPAWN_INVENTIONS", 0, true),
|
||||
Row("CAN_USE_PLAY_GIZMOS_TOGGLE", 0, true),
|
||||
|
||||
|
||||
|
||||
Row("CAN_INVITE", 0, true),
|
||||
Row("CAN_USE_MAKER_PEN", 30, false),
|
||||
Row("CAN_USE_ROOM_RESET_BUTTON", 30, true),
|
||||
Row("CAN_USE_DELETE_ALL_BUTTON", 30, true),
|
||||
Row("CAN_SAVE_INVENTIONS", 30, true),
|
||||
Row("CAN_SPAWN_INVENTIONS", 30, true),
|
||||
Row("CAN_USE_PLAY_GIZMOS_TOGGLE", 30, true),
|
||||
};
|
||||
}
|
||||
|
||||
[HttpGet("photon_access_token")]
|
||||
public async Task<IActionResult> GetPhotonAccessToken()
|
||||
{
|
||||
long? userId = User.GetUserId();
|
||||
if (userId == null) return Unauthorized();
|
||||
|
||||
var presence = await db.Presences
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(x => x.playerId == userId.Value);
|
||||
|
||||
var permissions = DefaultPermissions();
|
||||
|
||||
var profile = await db.Profiles
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(x => x.accountId == userId.Value);
|
||||
if (profile != null && (profile.HasRole("developer") || profile.HasRole("moderator")))
|
||||
permissions.Insert(0, new RoomPermissionRow("CAN_USE_MAKER_PEN", 0, true, 0, "True"));
|
||||
|
||||
long? subRoomId = null;
|
||||
if (presence?.roomInstance != null)
|
||||
{
|
||||
subRoomId = await db.RoomInstances
|
||||
.AsNoTracking()
|
||||
.Where(x => x.roomInstanceId == presence.roomInstance)
|
||||
.Select(x => (long?)x.subRoomId)
|
||||
.FirstOrDefaultAsync();
|
||||
}
|
||||
|
||||
if (subRoomId != null)
|
||||
{
|
||||
var overrides = await db.SubRoomPermissions
|
||||
.AsNoTracking()
|
||||
.Where(x => x.subRoomId == subRoomId)
|
||||
.OrderBy(x => x.Id)
|
||||
.ToListAsync();
|
||||
|
||||
foreach (var stored in overrides)
|
||||
{
|
||||
var entry = new RoomPermissionRow(stored.permission, stored.role, true, stored.type, stored.value);
|
||||
|
||||
var index = permissions.FindIndex(x =>
|
||||
x.Permission == entry.Permission && x.Role == entry.Role);
|
||||
if (index == -1) permissions.Add(entry);
|
||||
else permissions[index] = entry;
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(new Dictionary<string, object?>
|
||||
{
|
||||
["Permissions"] = permissions,
|
||||
["PhotonAccessToken"] = "",
|
||||
["RoomInstanceId"] = presence?.roomInstance
|
||||
});
|
||||
}
|
||||
|
||||
[HttpGet("rooms/{id}")]
|
||||
public async Task<ActionResult<FullRoom>> GetRoomById(long id)
|
||||
{
|
||||
var roomData = db.Rooms.FirstOrDefault(x => x.RoomId == id);
|
||||
if (roomData == null)
|
||||
{
|
||||
var userId = User.GetUserId();
|
||||
if (userId != null)
|
||||
{
|
||||
var dorm = await RoomUtils.GetOrCreateDorm((long)userId!);
|
||||
if (dorm != null)
|
||||
return Ok(dorm);
|
||||
}
|
||||
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
if (!await RoomUtils.CanSeeRoom(roomData, User.GetUserId()))
|
||||
return roomData.IsDorm ? NotFound() : Ok(RoomUtils.MaskHiddenRoom(roomData));
|
||||
|
||||
var fullRoom = await RoomUtils.GetRoom(roomData.RoomId);
|
||||
if (fullRoom == null) return NotFound();
|
||||
|
||||
|
||||
|
||||
return Ok(fullRoom);
|
||||
}
|
||||
|
||||
[HttpGet("rooms/{roomId}/subrooms/{subRoomId}/datahistory"), Authorize]
|
||||
public async Task<ActionResult<List<RestorableSubRoom>>> GetRoomDataHistory(long roomId, long subRoomId)
|
||||
{
|
||||
var userId = User.GetUserId();
|
||||
|
||||
var roomData = await db.Rooms.FirstOrDefaultAsync(x => x.RoomId == roomId);
|
||||
if (roomData == null) return NotFound();
|
||||
|
||||
var subRoom = await db.SubRooms.FirstOrDefaultAsync(x => x.SubRoomId == (long)subRoomId);
|
||||
if (subRoom == null) return NotFound();
|
||||
|
||||
var canModify = await MatchmakingUtils.IsAtLeastRole((long)userId!, roomId, RoleType.CoOwner);
|
||||
if (!canModify) return Forbid();
|
||||
|
||||
var history = await db.SubRoomHistory.Where(x => x.RoomId == roomId && x.SubRoomId == subRoomId).ToListAsync();
|
||||
return Ok(history);
|
||||
}
|
||||
|
||||
[HttpGet("rooms/{roomId}/subrooms/{subRoomId}/saves"), Authorize]
|
||||
public async Task<IActionResult> GetSubRoomSaves(long roomId, long subRoomId, int? skip = null, int? take = null)
|
||||
{
|
||||
var (refusal, saves) = await LoadSubRoomSavesAsync(roomId, subRoomId);
|
||||
if (refusal != null) return refusal;
|
||||
|
||||
var page = PageSaves(saves, skip, take);
|
||||
|
||||
return Ok(new { Results = page, TotalResults = saves.Count, TotalCount = saves.Count });
|
||||
}
|
||||
|
||||
[HttpGet("rooms/{roomId}/subrooms/{subRoomId}/saves/no_unity_assets"), Authorize]
|
||||
public async Task<IActionResult> GetSubRoomSavesWithoutUnityAssets(long roomId, long subRoomId, int? skip = null, int? take = null)
|
||||
{
|
||||
var (refusal, saves) = await LoadSubRoomSavesAsync(roomId, subRoomId);
|
||||
if (refusal != null) return refusal;
|
||||
|
||||
var page = PageSaves(saves, skip, take)
|
||||
.Select(x => new
|
||||
{
|
||||
x.SubRoomDataSaveId,
|
||||
x.SubRoomId,
|
||||
x.DataBlob,
|
||||
x.ReferencedUnityAssetIds,
|
||||
x.PersistenceVersion,
|
||||
x.OMVersion,
|
||||
x.UgcSubVersion,
|
||||
x.SavedByAccountId,
|
||||
x.SavedOnPlatform,
|
||||
x.SavedOnDeviceClass,
|
||||
x.Description,
|
||||
x.ModerationState,
|
||||
x.CreatedAt,
|
||||
x.UnityAssetId
|
||||
})
|
||||
.ToList();
|
||||
|
||||
return Ok(new { Results = page, TotalResults = saves.Count, TotalCount = saves.Count });
|
||||
}
|
||||
|
||||
private async Task<(IActionResult? Refusal, List<SubRoomDataSave> Saves)> LoadSubRoomSavesAsync(long roomId, long subRoomId)
|
||||
{
|
||||
var room = await db.Rooms.AsNoTracking().FirstOrDefaultAsync(x => x.RoomId == roomId);
|
||||
var subRoomExists = await db.SubRooms.AsNoTracking().AnyAsync(x => x.RoomId == roomId && x.SubRoomId == subRoomId);
|
||||
|
||||
if (room == null || !subRoomExists) return (Ok(EmptySavePage()), new List<SubRoomDataSave>());
|
||||
|
||||
var userId = User.GetUserId();
|
||||
if (userId == null) return (Unauthorized(), new List<SubRoomDataSave>());
|
||||
if (!await CanReadSavesAsync((long)userId!, room)) return (StatusCode(StatusCodes.Status403Forbidden), new List<SubRoomDataSave>());
|
||||
|
||||
return (null, await RoomUtils.GetSaveHistory(db, roomId, subRoomId));
|
||||
}
|
||||
|
||||
private async Task<bool> CanReadSavesAsync(long userId, RoomData room)
|
||||
{
|
||||
if (room.CreatorAccountId == userId) return true;
|
||||
if (await MatchmakingUtils.IsAtLeastRole(userId, room.RoomId, RoleType.CoOwner)) return true;
|
||||
|
||||
var presence = await db.Presences.AsNoTracking().FirstOrDefaultAsync(x => x.playerId == userId);
|
||||
if (presence?.roomInstance == null) return false;
|
||||
|
||||
var instance = await db.RoomInstances.AsNoTracking()
|
||||
.FirstOrDefaultAsync(x => x.roomInstanceId == presence.roomInstance);
|
||||
|
||||
return instance != null && instance.roomId == room.RoomId;
|
||||
}
|
||||
|
||||
private static object EmptySavePage() => new { Results = new List<SubRoomDataSave>(), TotalResults = 0, TotalCount = 0 };
|
||||
|
||||
private static List<SubRoomDataSave> PageSaves(List<SubRoomDataSave> saves, int? skip, int? take)
|
||||
{
|
||||
var from = skip == null || skip < 0 ? 0 : skip.Value;
|
||||
var remaining = saves.Skip(from);
|
||||
|
||||
return (take == null || take < 0 ? remaining : remaining.Take(take.Value)).ToList();
|
||||
}
|
||||
|
||||
|
||||
private async Task<Dictionary<string, string>> ReadSaveValuesAsync()
|
||||
{
|
||||
var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
if (Request.HasFormContentType)
|
||||
{
|
||||
foreach (var (key, value) in Request.Form)
|
||||
values[key] = value.ToString();
|
||||
return values;
|
||||
}
|
||||
|
||||
Request.EnableBuffering();
|
||||
|
||||
string raw;
|
||||
using (var reader = new StreamReader(Request.Body, Encoding.UTF8, detectEncodingFromByteOrderMarks: false, leaveOpen: true))
|
||||
raw = (await reader.ReadToEndAsync()).Trim();
|
||||
|
||||
Request.Body.Position = 0;
|
||||
|
||||
if (raw.Length == 0) return values;
|
||||
|
||||
try
|
||||
{
|
||||
if (JsonNode.Parse(raw) is JsonObject json) FlattenSaveJson(json, string.Empty, values);
|
||||
}
|
||||
catch (JsonException) { }
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
private static void FlattenSaveJson(JsonObject json, string prefix, Dictionary<string, string> values)
|
||||
{
|
||||
foreach (var (key, node) in json)
|
||||
{
|
||||
var path = prefix.Length == 0 ? key : prefix + "." + key;
|
||||
|
||||
if (node is JsonObject nested) FlattenSaveJson(nested, path, values);
|
||||
else if (node != null && node is not JsonArray) values[path] = node.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
private static string? ReadSaveValue(Dictionary<string, string> values, params string[] keys)
|
||||
{
|
||||
foreach (var key in keys)
|
||||
if (values.TryGetValue(key, out var raw) && !string.IsNullOrWhiteSpace(raw))
|
||||
return raw;
|
||||
return null;
|
||||
}
|
||||
|
||||
private static JsonObject BuildRoomSaveValue(FullRoom room, SubRoom subRoom, string? dataBlobHash)
|
||||
{
|
||||
var roomNode = JsonSerializer.SerializeToNode(room) as JsonObject ?? new JsonObject();
|
||||
var value = new JsonObject { ["room"] = roomNode.DeepClone() };
|
||||
|
||||
foreach (var property in roomNode)
|
||||
value[property.Key] = property.Value?.DeepClone();
|
||||
|
||||
value["subRoomDataSave"] = JsonSerializer.SerializeToNode(RoomUtils.BuildSaveResponse(subRoom, dataBlobHash));
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private sealed record StudioSaveResult(bool success, string? error, object? value);
|
||||
|
||||
[HttpPost("rooms/{roomId}/subrooms/{subRoomId}/data"), Authorize]
|
||||
public async Task<object?> UploadRoomData(long roomId, long subRoomId)
|
||||
{
|
||||
var userId = User.GetUserId();
|
||||
if (userId == null) return Success.Error("Invalid auth");
|
||||
|
||||
var canModify = await MatchmakingUtils.IsAtLeastRole((long)userId!, roomId, RoleType.CoOwner);
|
||||
if (!canModify) return Success.Error("Insufficient permissions");
|
||||
|
||||
var roomData = await db.Rooms.FirstOrDefaultAsync(x => x.RoomId == roomId);
|
||||
if (roomData == null) return new Success(false, "Invalid room.", null);
|
||||
|
||||
var subRoom = await db.SubRooms.FirstOrDefaultAsync(x => x.SubRoomId == subRoomId);
|
||||
if (subRoom == null) return new Success(false, "Invalid subroom.", null);
|
||||
|
||||
var values = await ReadSaveValuesAsync();
|
||||
|
||||
var blobName = ReadSaveValue(values, "subroomdata.filename", "filename", "datablob", "blob");
|
||||
if (string.IsNullOrWhiteSpace(blobName))
|
||||
{
|
||||
return new Success(false, "No room data was uploaded.", null);
|
||||
}
|
||||
|
||||
var savedBy = (long)userId!;
|
||||
var unityAssetId = ReadSaveValue(values, "unityassetid", "subroomdata.unityassetid");
|
||||
var unityAssetFilename = ReadSaveValue(values, "unityasset.filename", "unityasset");
|
||||
if (!string.IsNullOrWhiteSpace(unityAssetFilename))
|
||||
{
|
||||
if (!StudioUnityAssetRegistry.IsValidAssetId(unityAssetFilename))
|
||||
return new StudioSaveResult(false, "Invalid Unity source-manifest filename.", null);
|
||||
if (await StudioUnityAssetRegistry.ReadAsync(unityAssetFilename, HttpContext.RequestAborted) == null)
|
||||
return new StudioSaveResult(false, "Unity source manifest has not been uploaded.", null);
|
||||
}
|
||||
|
||||
var blobHash = ReadSaveValue(values, "subroomdata.hash", "hash", "datablobhash");
|
||||
|
||||
await db.SubRoomHistory.AddAsync(new RestorableSubRoom(subRoom));
|
||||
|
||||
subRoom.DataBlob = blobName;
|
||||
subRoom.SavedByAccountId = savedBy;
|
||||
subRoom.CreatedAt = DateTime.UtcNow;
|
||||
if (!string.IsNullOrWhiteSpace(unityAssetId)) subRoom.UnityAssetId = unityAssetId;
|
||||
if (!string.IsNullOrWhiteSpace(unityAssetFilename)) subRoom.UnityAssetId = unityAssetFilename;
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
var room = await RoomUtils.GetRoom(roomId);
|
||||
if (room == null) return new Success(false, "Invalid room.", null);
|
||||
|
||||
await Notify.SendNotify((long)userId!, new Notification { Id = "RoomUpdate", Msg = room });
|
||||
|
||||
|
||||
|
||||
|
||||
return new StudioSaveResult(true, null, BuildRoomSaveValue(room, subRoom, blobHash));
|
||||
}
|
||||
|
||||
|
||||
[HttpGet("rooms/{roomId}/subrooms/{subRoomId}/unityasset")]
|
||||
public async Task<IActionResult> GetStudioUnityAsset(long roomId, long subRoomId, [FromQuery] string? unityAssetId = null)
|
||||
{
|
||||
var subRoom = await db.SubRooms.AsNoTracking()
|
||||
.FirstOrDefaultAsync(x => x.RoomId == roomId && x.SubRoomId == subRoomId);
|
||||
if (subRoom == null) return NotFound();
|
||||
var assetId = string.IsNullOrWhiteSpace(unityAssetId) ? subRoom.UnityAssetId : unityAssetId;
|
||||
if (!string.Equals(assetId, subRoom.UnityAssetId, StringComparison.Ordinal)) return NotFound();
|
||||
var userId = User.GetUserId();
|
||||
var room = await db.Rooms.AsNoTracking().FirstOrDefaultAsync(x => x.RoomId == roomId);
|
||||
if (room == null || !await RoomUtils.CanSeeRoom(room, userId)) return NotFound();
|
||||
var asset = await StudioUnityAssetRegistry.ReadAsync(assetId, HttpContext.RequestAborted);
|
||||
return asset == null ? NotFound() : Ok(asset);
|
||||
}
|
||||
|
||||
[HttpPost("rooms/{roomId}/subrooms/{subRoomId}/restoredata"), Authorize]
|
||||
public async Task<Success?> RestoreRoomData(long roomId, long subRoomId)
|
||||
{
|
||||
var userId = User.GetUserId();
|
||||
if (userId == null) return Success.Error("Invalid auth");
|
||||
|
||||
var canModify = await MatchmakingUtils.IsAtLeastRole((long)userId!, roomId, RoleType.CoOwner);
|
||||
if (!canModify) return Success.Error("Insufficient permissions");
|
||||
|
||||
var roomData = await db.Rooms.FirstOrDefaultAsync(x => x.RoomId == roomId);
|
||||
if (roomData == null) return new Success(false, "Invalid room.", null);
|
||||
|
||||
var subRoom = await db.SubRooms.FirstOrDefaultAsync(x => x.SubRoomId == subRoomId);
|
||||
if (subRoom == null) return new Success(false, "Invalid subroom.", null);
|
||||
|
||||
var values = await ReadSaveValuesAsync();
|
||||
var blobName = ReadSaveValue(values, "filename", "subroomdata.filename", "datablob", "blob");
|
||||
if (string.IsNullOrWhiteSpace(blobName)) return new Success(false, "No room data was given.", null);
|
||||
|
||||
if (!System.IO.File.Exists(Path.Join(DBConfig.CDNStorage, blobName)))
|
||||
return new Success(false, "That save's data is no longer available.", null);
|
||||
|
||||
if (!string.IsNullOrEmpty(subRoom.DataBlob) && subRoom.DataBlob != blobName)
|
||||
{
|
||||
await db.SubRoomHistory.AddAsync(new RestorableSubRoom(subRoom));
|
||||
}
|
||||
|
||||
subRoom.DataBlob = blobName;
|
||||
subRoom.SavedByAccountId = (long)userId!;
|
||||
subRoom.CreatedAt = DateTime.UtcNow;
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
var room = await RoomUtils.GetRoom(roomId);
|
||||
if (room == null) return new Success(false, "Invalid room.", null);
|
||||
|
||||
await Notify.SendNotify((long)userId!, new Notification { Id = "RoomUpdate", Msg = room });
|
||||
|
||||
return new Success(true, null, BuildRoomSaveValue(room, subRoom, null));
|
||||
}
|
||||
|
||||
[HttpPost("rooms/{roomId}/subrooms"), Authorize]
|
||||
public async Task<Success?> CreateSubroom(long roomId, [FromForm] string name)
|
||||
{
|
||||
var userId = User.GetUserId();
|
||||
|
||||
|
||||
|
||||
var canModify = await MatchmakingUtils.IsAtLeastRole((long)userId!, roomId, RoleType.CoOwner);
|
||||
if (!canModify) return Success.Error("Insufficient permissions");
|
||||
|
||||
var roomData = await db.Rooms.FirstOrDefaultAsync(x => x.RoomId == roomId);
|
||||
if (roomData == null) return new Success(false, "Invalid room.", null);
|
||||
|
||||
var subRoom = await db.SubRooms.FirstOrDefaultAsync(x => x.Name == name && x.RoomId == roomId);
|
||||
if (subRoom != null) return new Success(false, "A subroom already exists with that name!", null);
|
||||
|
||||
var mainSubRoom = await db.SubRooms.FirstOrDefaultAsync(x => x.RoomId == roomId);
|
||||
if (mainSubRoom == null) return new Success(false, "There is no main subroom?", null);
|
||||
|
||||
var presence = await db.Presences.FirstOrDefaultAsync(x => x.playerId == userId);
|
||||
if (presence == null) return new Success(false, "Invalid auth", null);
|
||||
|
||||
if (!name.IsPure()) return new Success(false, "Name contains offensive language.", null);
|
||||
|
||||
var newSubRoom = new SubRoom
|
||||
{
|
||||
DataBlob = "",
|
||||
CreatedAt = DateTime.Now,
|
||||
Accessibility = Accessibility.Unlised,
|
||||
IsSandbox = true,
|
||||
MaxPlayers = mainSubRoom.MaxPlayers,
|
||||
Name = name,
|
||||
RoomId = roomId,
|
||||
SavedByAccountId = (long)userId!,
|
||||
UnitySceneId = mainSubRoom.UnitySceneId
|
||||
};
|
||||
|
||||
await db.SubRooms.AddAsync(newSubRoom);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
var room = await RoomUtils.GetRoom((long)roomId);
|
||||
if (room == null) return new Success(false, "WHAT THE FUCK???", null);
|
||||
|
||||
|
||||
|
||||
|
||||
return new Success(true, null, room);
|
||||
}
|
||||
|
||||
[HttpPost("rooms/{roomId}/subrooms/{subRoomId}/move"), Authorize]
|
||||
public async Task<Success?> MoveSubroom(long roomId, long subRoomId, [FromForm] long newRoomId)
|
||||
{
|
||||
var userId = User.GetUserId();
|
||||
|
||||
var canModify = await MatchmakingUtils.IsAtLeastRole((long)userId!, roomId, RoleType.CoOwner);
|
||||
if (!canModify) return Success.Error("Insufficient permissions");
|
||||
|
||||
canModify = await MatchmakingUtils.IsAtLeastRole((long)userId!, newRoomId, RoleType.CoOwner);
|
||||
if (!canModify) return Success.Error("Insufficient permissions");
|
||||
|
||||
var room = await db.Rooms.FirstOrDefaultAsync(x => x.RoomId == roomId);
|
||||
var newRoom = await db.Rooms.FirstOrDefaultAsync(x => x.RoomId == newRoomId);
|
||||
var subroom = await db.SubRooms.FirstOrDefaultAsync(x => x.SubRoomId == subRoomId);
|
||||
if (room == null) return Success.Error("Room not found");
|
||||
if (newRoom == null) return new Success(false, "Target room not found", null);
|
||||
if (subroom == null) return new Success(false, "Subroom not found", null);
|
||||
|
||||
subroom.RoomId = newRoomId;
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
var fullRoom = await RoomUtils.GetRoom(roomId);
|
||||
await Notify.SendNotify((long)userId!, new Notification { Id = "RoomUpdate", Msg = fullRoom });
|
||||
|
||||
return new Success(true, null, null);
|
||||
}
|
||||
|
||||
[HttpPut("rooms/{roomId}/subrooms/{subRoomId}/modify")]
|
||||
public async Task<Success> ModifySubroom(long roomId, long subRoomId, string? name, Accessibility? accessibility, int? maxPlayers)
|
||||
{
|
||||
var userId = User.GetUserId();
|
||||
|
||||
var canModify = await MatchmakingUtils.IsAtLeastRole((long)userId!, roomId, RoleType.CoOwner);
|
||||
if (!canModify) return Success.Error("Insufficient permissions");
|
||||
|
||||
var room = await db.Rooms.FirstOrDefaultAsync(x => x.RoomId == roomId);
|
||||
var subroom = await db.SubRooms.FirstOrDefaultAsync(x => x.SubRoomId == subRoomId);
|
||||
if (room == null) return Success.Error("Room not found");
|
||||
if (subroom == null) return new Success(false, "Subroom not found", null);
|
||||
|
||||
if (name != null)
|
||||
subroom.Name = name;
|
||||
if (accessibility != null)
|
||||
subroom.Accessibility = (Accessibility)accessibility;
|
||||
if (maxPlayers != null)
|
||||
subroom.MaxPlayers = (int)maxPlayers;
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
var fullRoom = await RoomUtils.GetRoom(roomId);
|
||||
await Notify.SendNotify((long)userId!, new Notification { Id = "RoomUpdate", Msg = fullRoom });
|
||||
|
||||
return new Success(true, null, null);
|
||||
}
|
||||
|
||||
[HttpPut("rooms/{roomId}/modify"), Authorize]
|
||||
public async Task<Success?> ModifyRoom(long roomId, string name, string? description, Accessibility accessibility, bool supportsScreens, bool supportsWalkVR, bool supportsTeleportVR, bool supportsJuniors, bool cloningAllowed, bool disableMicAutoMute, bool disableRoomComments, bool encryptVoiceChat)
|
||||
{
|
||||
var userId = User.GetUserId();
|
||||
|
||||
var canModify = await MatchmakingUtils.IsAtLeastRole((long)userId!, roomId, RoleType.CoOwner);
|
||||
if (!canModify) return Success.Error("Insufficient permissions");
|
||||
|
||||
var room = await db.Rooms.FirstOrDefaultAsync(x => x.RoomId == roomId);
|
||||
if (room == null) return Success.Error("Room not found");
|
||||
|
||||
if (!name.IsPure()) return new Success(false, "Name contains offensive language.", null);
|
||||
if (!(description ?? string.Empty).IsPure()) return new Success(false, "Description contains offensive language.", null);
|
||||
|
||||
room.Name = name;
|
||||
room.Description = description;
|
||||
room.SupportsScreens = supportsScreens;
|
||||
room.SupportsWalkVR = supportsWalkVR;
|
||||
room.SupportsTeleportVR = supportsTeleportVR;
|
||||
room.SupportsJuniors = supportsJuniors;
|
||||
room.Accessibility = accessibility;
|
||||
room.CloningAllowed = cloningAllowed;
|
||||
room.DisableMicAutoMute = disableMicAutoMute;
|
||||
room.DisableRoomComments = disableRoomComments;
|
||||
room.EncryptVoiceChat = encryptVoiceChat;
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
var fullRoom = await RoomUtils.GetRoom(roomId);
|
||||
await Notify.SendNotify((long)userId!, new Notification { Id = "RoomUpdate", Msg = fullRoom });
|
||||
|
||||
return new Success(true, null, null);
|
||||
}
|
||||
|
||||
[HttpPut("rooms/{roomId}/image"), Authorize]
|
||||
public async Task<Success?> ModifyImage(long roomId, string imageName)
|
||||
{
|
||||
var userId = User.GetUserId();
|
||||
|
||||
var canModify = await MatchmakingUtils.IsAtLeastRole((long)userId!, roomId, RoleType.CoOwner);
|
||||
if (!canModify) return Success.Error("Insufficient permissions");
|
||||
|
||||
var room = await db.Rooms.FirstOrDefaultAsync(x => x.RoomId == roomId);
|
||||
if (room == null) return Success.Error("Room not found");
|
||||
|
||||
room.ImageName = imageName;
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
var fullRoom = await RoomUtils.GetRoom(roomId);
|
||||
await Notify.SendNotify((long)userId!, new Notification { Id = "RoomUpdate", Msg = fullRoom });
|
||||
|
||||
return new Success(true, null, null);
|
||||
}
|
||||
|
||||
private static readonly RoleType[] AssignableRoles =
|
||||
{
|
||||
RoleType.None, RoleType.Host, RoleType.Moderator, RoleType.CoOwner, RoleType.TemporaryCoOwner
|
||||
};
|
||||
|
||||
private async Task<(RoleType? Role, long? ActorId)> ReadRoleRequestAsync()
|
||||
{
|
||||
var userId = User.GetUserId();
|
||||
if (userId == null) return (null, null);
|
||||
|
||||
foreach (var key in new[] { "role", "Role", "roletype", "RoleType", "roleType", "invitedrole", "InvitedRole" })
|
||||
{
|
||||
var raw = Request.HasFormContentType && Request.Form.TryGetValue(key, out var form)
|
||||
? form.ToString()
|
||||
: Request.Query.TryGetValue(key, out var query) ? query.ToString() : null;
|
||||
|
||||
if (raw == null && Request.RouteValues.TryGetValue(key, out var route)) raw = route?.ToString();
|
||||
if (!int.TryParse(raw, out var parsed)) continue;
|
||||
if (!Enum.IsDefined(typeof(RoleType), parsed)) continue;
|
||||
|
||||
return ((RoleType)parsed, userId);
|
||||
}
|
||||
|
||||
return (null, userId);
|
||||
}
|
||||
|
||||
private async Task PushRoomRoleUpdate(long roomId, long actorId, long targetId, FullRoom? room)
|
||||
{
|
||||
room ??= await RoomUtils.GetRoom(roomId);
|
||||
if (room == null) return;
|
||||
|
||||
foreach (var accountId in new[] { targetId, actorId }.Distinct())
|
||||
await Notify.SendNotify(accountId, new Notification { Id = "RoomUpdate", Msg = room });
|
||||
}
|
||||
|
||||
[HttpPut("rooms/{roomId}/roles/{targetId}/invite"), Authorize]
|
||||
public async Task<Success?> InvitePlayerToRole(long roomId, long targetId)
|
||||
{
|
||||
var (role, userId) = await ReadRoleRequestAsync();
|
||||
if (userId == null) return Success.Error("Invalid auth");
|
||||
if (targetId == userId.Value) return Success.Error("You cannot invite yourself");
|
||||
|
||||
var canModify = await MatchmakingUtils.IsAtLeastRole((long)userId!, roomId, RoleType.CoOwner);
|
||||
if (!canModify) return Success.Error("Insufficient permissions");
|
||||
|
||||
var room = await db.Rooms.FirstOrDefaultAsync(x => x.RoomId == roomId);
|
||||
if (room == null) return Success.Error("Room not found");
|
||||
|
||||
var player = await db.Players.FirstOrDefaultAsync(x => x.AccountId == targetId);
|
||||
if (player == null) return Success.Error("Player not found");
|
||||
|
||||
var existingRole = await db.RoomRoles.FirstOrDefaultAsync(x => x.AccountId == targetId && x.RoomId == roomId);
|
||||
if (role == null) return Success.Error("You must provide a valid role");
|
||||
if (!AssignableRoles.Contains(role.Value)) return Success.Error($"Unknown role {role.Value}");
|
||||
if (role.Value == RoleType.None) return Success.Error("You must provide a valid role");
|
||||
if (existingRole?.Role == role.Value && existingRole.InvitedRole == RoleType.None)
|
||||
return Success.Error("Player already has this role");
|
||||
|
||||
if (existingRole != null &&
|
||||
existingRole.Role != RoleType.None &&
|
||||
existingRole.Role != RoleType.Banned)
|
||||
return Success.Error("Player already has a room role");
|
||||
|
||||
if (existingRole == null)
|
||||
{
|
||||
var newRole = new RoomRole
|
||||
{
|
||||
AccountId = targetId,
|
||||
InvitedRole = role.Value,
|
||||
Role = RoleType.None,
|
||||
RoomId = roomId,
|
||||
LastChangedByAccountId = userId.Value
|
||||
};
|
||||
|
||||
await db.RoomRoles.AddAsync(newRole);
|
||||
}
|
||||
else
|
||||
{
|
||||
existingRole.InvitedRole = role.Value;
|
||||
existingRole.LastChangedByAccountId = userId.Value;
|
||||
}
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
if (role.Value == RoleType.CoOwner)
|
||||
{
|
||||
await Notify.SendMessage(targetId, new Message
|
||||
{
|
||||
FromPlayerId = userId.Value,
|
||||
RoomId = roomId,
|
||||
Type = MessageType.RoomCoOwnerInvited
|
||||
});
|
||||
}
|
||||
|
||||
var fullRoom = await RoomUtils.GetRoom(roomId);
|
||||
await PushRoomRoleUpdate(roomId, userId.Value, targetId, fullRoom);
|
||||
|
||||
return new Success(true, null, fullRoom);
|
||||
}
|
||||
|
||||
[HttpPut("rooms/{roomId}/roles/{targetId}"), Authorize]
|
||||
public async Task<Success?> SetPlayerRole(long roomId, long targetId)
|
||||
{
|
||||
var (role, userId) = await ReadRoleRequestAsync();
|
||||
if (userId == null) return Success.Error("Invalid auth");
|
||||
|
||||
var room = await db.Rooms.FirstOrDefaultAsync(x => x.RoomId == roomId);
|
||||
if (room == null) return Success.Error("Room not found");
|
||||
|
||||
if (targetId == room.CreatorAccountId)
|
||||
return Success.Error("The room's creator cannot be given a different role");
|
||||
|
||||
var player = await db.Players.FirstOrDefaultAsync(x => x.AccountId == targetId);
|
||||
if (player == null) return Success.Error("Player not found");
|
||||
|
||||
var existingRole = await db.RoomRoles.FirstOrDefaultAsync(x => x.AccountId == targetId && x.RoomId == roomId);
|
||||
var wasCoOwner = existingRole?.Role == RoleType.CoOwner;
|
||||
var acceptingInvite = targetId == userId.Value &&
|
||||
existingRole is { Role: RoleType.None, InvitedRole: not RoleType.None };
|
||||
if (acceptingInvite && role == null) role = existingRole!.InvitedRole;
|
||||
if (role == null) return Success.Error("You must provide a valid role");
|
||||
if (!AssignableRoles.Contains(role.Value)) return Success.Error($"Unknown role {role.Value}");
|
||||
|
||||
if (acceptingInvite)
|
||||
{
|
||||
if (role.Value != existingRole!.InvitedRole)
|
||||
return Success.Error("The requested role does not match the pending invitation");
|
||||
}
|
||||
else
|
||||
{
|
||||
var canModify = await MatchmakingUtils.IsAtLeastRole(userId.Value, roomId, RoleType.CoOwner);
|
||||
if (!canModify) return Success.Error("Insufficient permissions");
|
||||
}
|
||||
|
||||
if (role.Value == RoleType.None)
|
||||
{
|
||||
if (existingRole == null) return Success.Error("Player already has this role");
|
||||
if (acceptingInvite) return Success.Error("The pending invitation cannot be accepted as None");
|
||||
|
||||
db.RoomRoles.Remove(existingRole);
|
||||
}
|
||||
else if (existingRole == null)
|
||||
{
|
||||
await db.RoomRoles.AddAsync(new RoomRole
|
||||
{
|
||||
AccountId = targetId,
|
||||
InvitedRole = RoleType.None,
|
||||
Role = role.Value,
|
||||
RoomId = roomId,
|
||||
LastChangedByAccountId = userId.Value
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
if (existingRole.Role == role.Value && existingRole.InvitedRole == RoleType.None)
|
||||
return Success.Error("Player already has this role");
|
||||
|
||||
existingRole.Role = role.Value;
|
||||
existingRole.InvitedRole = RoleType.None;
|
||||
existingRole.LastChangedByAccountId = (long)userId!;
|
||||
}
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
if (wasCoOwner && role.Value < RoleType.CoOwner)
|
||||
{
|
||||
await Notify.SendMessage(targetId, new Message
|
||||
{
|
||||
FromPlayerId = userId.Value,
|
||||
RoomId = roomId,
|
||||
Type = MessageType.RoomCoOwnerRemoved
|
||||
});
|
||||
}
|
||||
|
||||
if (role.Value == RoleType.CoOwner && !wasCoOwner)
|
||||
{
|
||||
await Notify.SendMessage(targetId, new Message
|
||||
{
|
||||
FromPlayerId = userId.Value,
|
||||
RoomId = roomId,
|
||||
Type = MessageType.RoomCoOwnerAdded
|
||||
});
|
||||
}
|
||||
|
||||
var fullRoom = await RoomUtils.GetRoom(roomId);
|
||||
await PushRoomRoleUpdate(roomId, userId.Value, targetId, fullRoom);
|
||||
|
||||
return new Success(true, null, fullRoom);
|
||||
}
|
||||
|
||||
[HttpPut("rooms/{roomId}/tags"), Authorize]
|
||||
public async Task<Success?> ModifyTags(long roomId, string[]? tag, string[]? autoTag)
|
||||
{
|
||||
var userId = User.GetUserId();
|
||||
|
||||
var canModify = await MatchmakingUtils.IsAtLeastRole((long)userId!, roomId, RoleType.CoOwner);
|
||||
if (!canModify) return Success.Error("Insufficient permissions");
|
||||
|
||||
var room = await db.Rooms.FirstOrDefaultAsync(x => x.RoomId == roomId);
|
||||
if (room == null) return Success.Error("Room not found");
|
||||
|
||||
foreach (var t in tag ?? Array.Empty<string>())
|
||||
{
|
||||
if (!t.TagAllowed())
|
||||
return new Success(false, "Tag not allowed: " + t, null);
|
||||
}
|
||||
|
||||
if (tag != null || autoTag != null) {
|
||||
List<ItemTag> tagsToAdd = new();
|
||||
|
||||
if (tag != null)
|
||||
foreach (var t in tag)
|
||||
tagsToAdd.Add(new ItemTag { Tag = t, Type = TagType.General });
|
||||
|
||||
if (autoTag != null)
|
||||
foreach (var t in autoTag)
|
||||
tagsToAdd.Add(new ItemTag { Tag = t, Type = TagType.Auto });
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
room.Tags = StringTools.CreateTagString(tagsToAdd);
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
var fullRoom = await RoomUtils.GetRoom(roomId);
|
||||
await Notify.SendNotify((long)userId!, new Notification { Id = "RoomUpdate", Msg = fullRoom });
|
||||
|
||||
return new Success(true, null, null);
|
||||
} else
|
||||
{
|
||||
room.Tags = "";
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
var fullRoom = await RoomUtils.GetRoom(roomId);
|
||||
await Notify.SendNotify((long)userId!, new Notification { Id = "RoomUpdate", Msg = fullRoom });
|
||||
|
||||
return new Success(true, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPut("rooms/{roomId}/accessibility"), Authorize]
|
||||
public async Task<Success?> ModifyAccessibility(long roomId, Accessibility accessibility)
|
||||
{
|
||||
var userId = User.GetUserId();
|
||||
|
||||
var canModify = await MatchmakingUtils.IsAtLeastRole((long)userId!, roomId, RoleType.CoOwner);
|
||||
if (!canModify) return Success.Error("Insufficient permissions");
|
||||
|
||||
var room = await db.Rooms.FirstOrDefaultAsync(x => x.RoomId == roomId);
|
||||
if (room == null) return Success.Error("Room not found");
|
||||
|
||||
bool wasPublished = room.WasPublished;
|
||||
|
||||
room.Accessibility = accessibility;
|
||||
if (!room.WasPublished && accessibility == Accessibility.Public) {
|
||||
room.WasPublished = true;
|
||||
room.PublishedAt = DateTime.Now;
|
||||
}
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
var fullRoom = await RoomUtils.GetRoom(roomId);
|
||||
await Notify.SendNotify((long)userId!, new Notification { Id = "RoomUpdate", Msg = fullRoom });
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
return new Success(true, null, null);
|
||||
}
|
||||
|
||||
[HttpGet("rooms/{roomId}/subrooms/{subRoomId}/permissions"), Authorize]
|
||||
public async Task<IActionResult> GetSubRoomPermissions(long roomId, long subRoomId)
|
||||
{
|
||||
if (User.GetUserId() == null) return Unauthorized();
|
||||
|
||||
var subRoom = await db.SubRooms
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(x => x.RoomId == roomId && x.SubRoomId == subRoomId);
|
||||
if (subRoom == null) return NotFound();
|
||||
|
||||
var stored = await db.SubRoomPermissions
|
||||
.AsNoTracking()
|
||||
.Where(x => x.subRoomId == subRoomId)
|
||||
.OrderBy(x => x.Id)
|
||||
.ToListAsync();
|
||||
|
||||
return Ok(stored
|
||||
.Select(x => new RoomPermissionRow(x.permission, x.role, true, x.type, x.value))
|
||||
.ToList());
|
||||
}
|
||||
|
||||
[HttpPut("rooms/{roomId}/subrooms/{subRoomId}/permissions"), Authorize]
|
||||
public async Task<IActionResult> SetSubRoomPermissions(long roomId, long subRoomId)
|
||||
{
|
||||
long? userId = User.GetUserId();
|
||||
if (userId == null) return Unauthorized();
|
||||
|
||||
var subRoom = await db.SubRooms
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(x => x.RoomId == roomId && x.SubRoomId == subRoomId);
|
||||
if (subRoom == null) return NotFound();
|
||||
|
||||
if (!await MatchmakingUtils.IsAtLeastRole(userId.Value, roomId, RoleType.CoOwner))
|
||||
return Forbid();
|
||||
|
||||
string? body;
|
||||
using (var reader = new StreamReader(Request.Body, Encoding.UTF8))
|
||||
body = await reader.ReadToEndAsync();
|
||||
|
||||
var entries = ParseSubRoomPermissions(body);
|
||||
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
var existing = await db.SubRoomPermissions.FirstOrDefaultAsync(x =>
|
||||
x.subRoomId == subRoomId && x.permission == entry.Permission && x.role == entry.Role);
|
||||
|
||||
if (!entry.Override)
|
||||
{
|
||||
if (existing != null) db.SubRoomPermissions.Remove(existing);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (existing == null)
|
||||
{
|
||||
await db.SubRoomPermissions.AddAsync(new SubRoomPermission
|
||||
{
|
||||
subRoomId = subRoomId,
|
||||
permission = entry.Permission,
|
||||
role = entry.Role,
|
||||
type = entry.Type,
|
||||
value = entry.Value,
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
existing.type = entry.Type;
|
||||
existing.value = entry.Value;
|
||||
}
|
||||
}
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
var logged = body ?? "";
|
||||
if (logged.Length > 800) logged = logged[..800] + "...";
|
||||
|
||||
return Ok(new { Permissions = Array.Empty<object>() });
|
||||
}
|
||||
|
||||
private static List<RoomPermissionRow> ParseSubRoomPermissions(string? body)
|
||||
{
|
||||
var entries = new List<RoomPermissionRow>();
|
||||
if (string.IsNullOrWhiteSpace(body)) return entries;
|
||||
|
||||
JsonDocument document;
|
||||
try
|
||||
{
|
||||
document = JsonDocument.Parse(body);
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
return entries;
|
||||
}
|
||||
|
||||
using (document)
|
||||
{
|
||||
if (document.RootElement.ValueKind != JsonValueKind.Array) return entries;
|
||||
|
||||
foreach (var element in document.RootElement.EnumerateArray())
|
||||
{
|
||||
if (element.ValueKind != JsonValueKind.Object) continue;
|
||||
|
||||
var permission = ReadPermissionName(element);
|
||||
if (permission.Length == 0) continue;
|
||||
|
||||
var role = ReadPermissionRole(element);
|
||||
if (role == null) continue;
|
||||
|
||||
entries.Add(new RoomPermissionRow(
|
||||
permission,
|
||||
role.Value,
|
||||
ReadPermissionBool(element, "Override"),
|
||||
(int)(ReadPermissionLong(element, "Type") ?? 0),
|
||||
ReadPermissionValue(element, "Value")));
|
||||
}
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
private static bool TryGetPermissionField(JsonElement entry, string key, out JsonElement value)
|
||||
{
|
||||
foreach (var property in entry.EnumerateObject())
|
||||
{
|
||||
if (string.Equals(property.Name, key, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
value = property.Value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static string? ReadPermissionString(JsonElement entry, string key)
|
||||
{
|
||||
if (!TryGetPermissionField(entry, key, out var value)) return null;
|
||||
return value.ValueKind == JsonValueKind.String ? value.GetString() : value.ToString();
|
||||
}
|
||||
|
||||
private static long? ReadPermissionLong(JsonElement entry, string key)
|
||||
{
|
||||
if (!TryGetPermissionField(entry, key, out var value)) return null;
|
||||
|
||||
if (value.ValueKind == JsonValueKind.Number && value.TryGetInt64(out var number))
|
||||
return number;
|
||||
|
||||
if (value.ValueKind == JsonValueKind.String &&
|
||||
long.TryParse(value.GetString(), out var parsed))
|
||||
return parsed;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static bool ReadPermissionBool(JsonElement entry, string key)
|
||||
{
|
||||
if (!TryGetPermissionField(entry, key, out var value)) return false;
|
||||
|
||||
if (value.ValueKind == JsonValueKind.True) return true;
|
||||
if (value.ValueKind == JsonValueKind.False) return false;
|
||||
|
||||
return string.Equals(value.ToString(), "true", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static string ReadPermissionValue(JsonElement entry, string key)
|
||||
{
|
||||
if (!TryGetPermissionField(entry, key, out var value)) return "";
|
||||
|
||||
if (value.ValueKind == JsonValueKind.String) return value.GetString() ?? "";
|
||||
if (value.ValueKind == JsonValueKind.True) return "True";
|
||||
if (value.ValueKind == JsonValueKind.False) return "False";
|
||||
return value.ToString();
|
||||
}
|
||||
|
||||
private static string ReadPermissionName(JsonElement entry)
|
||||
{
|
||||
var raw = (ReadPermissionString(entry, "Permission")
|
||||
?? ReadPermissionString(entry, "PermissionType")
|
||||
?? ReadPermissionString(entry, "PermissionTypeOld")
|
||||
?? "").Trim();
|
||||
if (raw.Length == 0) return "";
|
||||
|
||||
if (long.TryParse(raw, out var number) &&
|
||||
Enum.IsDefined(typeof(RolePermissions), (int)number))
|
||||
return ((RolePermissions)(int)number).ToString();
|
||||
|
||||
return raw;
|
||||
}
|
||||
|
||||
private static long? ReadPermissionRole(JsonElement entry)
|
||||
{
|
||||
var numeric = ReadPermissionLong(entry, "Role") ?? ReadPermissionLong(entry, "RoleId");
|
||||
if (numeric != null) return numeric;
|
||||
|
||||
var name = ReadPermissionString(entry, "Role")?.Trim();
|
||||
if (!string.IsNullOrEmpty(name) && Enum.TryParse<RoleType>(name, true, out var parsed))
|
||||
return (long)parsed;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
[HttpPut("rooms/{roomId}/subrooms/{subRoomId}/accessibility"), Authorize]
|
||||
public async Task<Success?> ModifySubRoomAccessibility(long roomId, long subRoomId, Accessibility accessibility)
|
||||
{
|
||||
var userId = User.GetUserId();
|
||||
|
||||
var canModify = await MatchmakingUtils.IsAtLeastRole((long)userId!, roomId, RoleType.CoOwner);
|
||||
if (!canModify) return Success.Error("Insufficient permissions");
|
||||
|
||||
var room = await db.Rooms.FirstOrDefaultAsync(x => x.RoomId == roomId);
|
||||
if (room == null) return Success.Error("Room not found");
|
||||
|
||||
var subRoom = await db.SubRooms.FirstOrDefaultAsync(x => x.RoomId == roomId && x.SubRoomId == subRoomId);
|
||||
if (subRoom == null) return new Success(false, "Subroom not found", null);
|
||||
|
||||
subRoom.Accessibility = accessibility;
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
var fullRoom = await RoomUtils.GetRoom(roomId);
|
||||
await Notify.SendNotify((long)userId!, new Notification { Id = "RoomUpdate", Msg = fullRoom });
|
||||
|
||||
return new Success(true, null, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user