mirror of
https://github.com/JogmansonReal/Radicade.git
synced 2026-10-26 07:29:00 -07:00
1861 lines
62 KiB
C#
1861 lines
62 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|