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,1259 @@
|
||||
using System.Text.Json;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using RadicadeServer.API.Notifications;
|
||||
using RadicadeServer.Clubs;
|
||||
using RadicadeServer.DB;
|
||||
using RadicadeServer.Extensions;
|
||||
using RadicadeServer.RecNet.Enums;
|
||||
using RadicadeServer.RecNet.Events;
|
||||
using RadicadeServer.RecNet.Enums.Messages;
|
||||
using RadicadeServer.RecNet.Messages;
|
||||
using RadicadeServer.RecNet.Responses;
|
||||
using RadicadeServer.RecNet.Rooms;
|
||||
using RadicadeServer.Utils;
|
||||
using ReportCategory = RadicadeServer.RecNet.Enums.Moderation.ReportCategory;
|
||||
|
||||
namespace RadicadeServer.API.Events
|
||||
{
|
||||
[Route("api/playerevents")]
|
||||
public class EventsAPI(RecDBContext db) : Controller
|
||||
{
|
||||
private const string DefaultEventImageName = "default";
|
||||
|
||||
private const int EventResultSuccess = 0;
|
||||
|
||||
|
||||
|
||||
private const int EventResultBroadcastNotMultiInstance = 27;
|
||||
|
||||
private static List<PlayerEventTag> ParseEventTags(string? tagsJson)
|
||||
{
|
||||
try
|
||||
{
|
||||
return JsonSerializer.Deserialize<List<PlayerEventTag>>(tagsJson ?? "[]") ?? [];
|
||||
}
|
||||
catch
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
private static PlayerEventTag[] ParsePostedEventTags(Dictionary<string, string> fields)
|
||||
{
|
||||
var raw = GetEventField(fields, "Tags", "tags");
|
||||
if (string.IsNullOrWhiteSpace(raw)) return [];
|
||||
|
||||
try
|
||||
{
|
||||
using var document = JsonDocument.Parse(raw);
|
||||
if (document.RootElement.ValueKind != JsonValueKind.Array) return [];
|
||||
return document.RootElement.EnumerateArray()
|
||||
.Select(entry =>
|
||||
{
|
||||
if (entry.ValueKind == JsonValueKind.String)
|
||||
return new PlayerEventTag { Tag = entry.GetString() ?? string.Empty };
|
||||
if (entry.ValueKind != JsonValueKind.Object)
|
||||
return new PlayerEventTag();
|
||||
|
||||
var tag = entry.TryGetProperty("Tag", out var tagValue) || entry.TryGetProperty("tag", out tagValue)
|
||||
? tagValue.GetString() ?? string.Empty
|
||||
: string.Empty;
|
||||
var type = entry.TryGetProperty("Type", out var typeValue) || entry.TryGetProperty("type", out typeValue)
|
||||
? typeValue
|
||||
: default;
|
||||
return new PlayerEventTag
|
||||
{
|
||||
Tag = tag,
|
||||
Type = type.ValueKind == JsonValueKind.Number && type.TryGetInt32(out var parsedType) ? parsedType : 0
|
||||
};
|
||||
})
|
||||
.Where(tag => !string.IsNullOrWhiteSpace(tag.Tag))
|
||||
.ToArray();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
private static PlayerEvent ToPlayerEvent(PlayerEventDB ev)
|
||||
{
|
||||
return new PlayerEvent
|
||||
{
|
||||
PlayerEventId = ev.PlayerEventId,
|
||||
Name = ev.Name,
|
||||
Description = ev.Description,
|
||||
ImageName = string.IsNullOrWhiteSpace(ev.ImageName) ? DefaultEventImageName : ev.ImageName,
|
||||
StartTime = ev.StartTime,
|
||||
EndTime = ev.EndTime,
|
||||
CreatorPlayerId = ev.CreatorPlayerId,
|
||||
AttendeeCount = ev.AttendeeCount,
|
||||
RoomId = ev.RoomId,
|
||||
SubRoomId = ev.SubRoomId,
|
||||
Accessibility = ev.Accessibility,
|
||||
ClubId = ev.ClubId,
|
||||
State = ev.ModerationState,
|
||||
Tags = ParseEventTags(ev.TagsJson),
|
||||
CreatedAt = ev.CreatedAt,
|
||||
PlayerCount = ev.AttendeeCount,
|
||||
IsFull = false,
|
||||
IsMultiInstance = ev.IsMultiInstance,
|
||||
SupportMultiInstanceRoomChat = ev.SupportMultiInstanceRoomChat,
|
||||
DefaultBroadcastPermissions = ev.DefaultBroadcastPermissions,
|
||||
CanRequestBroadcastPermissions = ev.CanRequestBroadcastPermissions,
|
||||
BroadcastingRoomInstanceId = ev.BroadcastingRoomInstanceId
|
||||
};
|
||||
}
|
||||
|
||||
private static EventResponse ToEventResponse(PlayerEventResponse r)
|
||||
{
|
||||
return new EventResponse
|
||||
{
|
||||
PlayerEventResponseId = r.PlayerEventResponseId,
|
||||
PlayerEventId = r.PlayerEventId,
|
||||
PlayerId = r.PlayerId,
|
||||
CreatedAt = r.CreatedAt,
|
||||
Type = r.Type
|
||||
};
|
||||
}
|
||||
|
||||
private static object ToEventEnvelope(PlayerEventDB ev)
|
||||
{
|
||||
var playerEvent = ToPlayerEvent(ev);
|
||||
return new Dictionary<string, object?>
|
||||
{
|
||||
["PlayerEventId"] = playerEvent.PlayerEventId,
|
||||
["Id"] = playerEvent.PlayerEventId,
|
||||
["EventId"] = playerEvent.PlayerEventId,
|
||||
["CreatorPlayerId"] = playerEvent.CreatorPlayerId,
|
||||
["ImageName"] = playerEvent.ImageName,
|
||||
["RoomId"] = playerEvent.RoomId,
|
||||
["SubRoomId"] = playerEvent.SubRoomId,
|
||||
["ClubId"] = playerEvent.ClubId,
|
||||
["Name"] = playerEvent.Name,
|
||||
["Description"] = playerEvent.Description,
|
||||
["StartTime"] = EventClock.ToWire(playerEvent.StartTime),
|
||||
["EndTime"] = EventClock.ToWire(playerEvent.EndTime),
|
||||
["AttendeeCount"] = playerEvent.AttendeeCount,
|
||||
["Accessibility"] = playerEvent.Accessibility,
|
||||
["IsMultiInstance"] = playerEvent.IsMultiInstance,
|
||||
["SupportMultiInstanceRoomChat"] = playerEvent.SupportMultiInstanceRoomChat,
|
||||
["DefaultBroadcastPermissions"] = playerEvent.DefaultBroadcastPermissions,
|
||||
["CanRequestBroadcastPermissions"] = playerEvent.CanRequestBroadcastPermissions,
|
||||
["BroadcastingRoomInstanceId"] = playerEvent.BroadcastingRoomInstanceId,
|
||||
["Tags"] = playerEvent.Tags.Select(tag => new Dictionary<string, object?>
|
||||
{
|
||||
["Tag"] = tag.Tag,
|
||||
["Type"] = tag.Type
|
||||
}).ToArray(),
|
||||
};
|
||||
}
|
||||
|
||||
private static object ToEventResult(PlayerEventDB ev, int result = EventResultSuccess) => new
|
||||
{
|
||||
PlayerEvent = ToEventEnvelope(ev),
|
||||
Result = result,
|
||||
TagModifyResult = new { Result = result, Tags = Array.Empty<string>() }
|
||||
};
|
||||
|
||||
[HttpGet("v1/searchlive"), Authorize]
|
||||
[HttpGet("v1/search"), Authorize]
|
||||
[HttpGet("v2/search"), Authorize]
|
||||
public async Task<List<PlayerEvent>> SearchEvents([FromQuery] string? search = null, [FromQuery] string? query = null)
|
||||
{
|
||||
var events = await db.PlayerEvents.ToListAsync();
|
||||
var results = PlayerEventQueries.Search(events, search ?? query, DateTime.UtcNow);
|
||||
|
||||
return results.Select(ToPlayerEvent).ToList();
|
||||
}
|
||||
|
||||
[HttpGet("v1"), Authorize]
|
||||
public async Task<List<PlayerEvent>> GetBrowseFeed([FromQuery] int skip = 0, [FromQuery] int take = 50)
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
var events = await db.PlayerEvents
|
||||
.Where(x => x.EndTime > now)
|
||||
.OrderBy(x => x.StartTime)
|
||||
.Skip(Math.Max(0, skip))
|
||||
.Take(Math.Clamp(take, 0, 100))
|
||||
.ToListAsync();
|
||||
|
||||
return events.Select(ToPlayerEvent).ToList();
|
||||
}
|
||||
|
||||
[HttpGet("v1/bulk"), Authorize]
|
||||
public async Task<List<PlayerEvent>> GetEventsBulk([FromQuery] List<long>? id)
|
||||
{
|
||||
if (id == null || id.Count == 0) return [];
|
||||
|
||||
var events = await db.PlayerEvents
|
||||
.Where(x => id.Contains(x.PlayerEventId))
|
||||
.ToDictionaryAsync(x => x.PlayerEventId);
|
||||
|
||||
return id.Where(events.ContainsKey).Select(e => ToPlayerEvent(events[e])).ToList();
|
||||
}
|
||||
|
||||
[HttpGet("v1/creator/{playerId}"), Authorize]
|
||||
public async Task<List<PlayerEvent>> GetEventsByCreator(long playerId)
|
||||
{
|
||||
var created = await db.PlayerEvents
|
||||
.Where(x => x.CreatorPlayerId == playerId)
|
||||
.OrderByDescending(x => x.StartTime)
|
||||
.ToListAsync();
|
||||
|
||||
return created.Select(ToPlayerEvent).ToList();
|
||||
}
|
||||
|
||||
[HttpGet("v1/tagfilters"), Authorize]
|
||||
public RoomFilters GetEventTagFilters()
|
||||
{
|
||||
var pinned = new List<string>
|
||||
{
|
||||
"workshops", "celebration", "game", "meetup", "performance",
|
||||
"coop", "grandopening", "class", "competition"
|
||||
};
|
||||
var popular = new List<string>
|
||||
{
|
||||
"workshops", "celebration", "class", "coop", "competition",
|
||||
"game", "grandopening", "meetup", "performance"
|
||||
};
|
||||
return new RoomFilters
|
||||
{
|
||||
PinnedFilters = pinned,
|
||||
PopularFilters = popular,
|
||||
TrendingFilters = null
|
||||
};
|
||||
}
|
||||
|
||||
[HttpGet("v1/all"), Authorize]
|
||||
[HttpGet("v2/all"), Authorize]
|
||||
[HttpGet("v2"), Authorize]
|
||||
public async Task<AllEvents> GetAllEvents()
|
||||
{
|
||||
var userId = User.GetUserId() ?? 0;
|
||||
|
||||
var created = await db.PlayerEvents
|
||||
.Where(x => x.CreatorPlayerId == userId)
|
||||
.OrderByDescending(x => x.StartTime)
|
||||
.ToListAsync();
|
||||
|
||||
return new AllEvents
|
||||
{
|
||||
Created = created.Select(ToPlayerEvent).ToList(),
|
||||
Responses = []
|
||||
};
|
||||
}
|
||||
|
||||
[HttpGet("v1/all/{playerId:long}"), Authorize]
|
||||
[HttpGet("v2/all/{playerId:long}"), Authorize]
|
||||
public async Task<AllEvents> GetAllEventsForPlayer(long playerId)
|
||||
{
|
||||
var created = await db.PlayerEvents
|
||||
.Where(x => x.CreatorPlayerId == playerId)
|
||||
.OrderByDescending(x => x.StartTime)
|
||||
.ToListAsync();
|
||||
|
||||
return new AllEvents
|
||||
{
|
||||
Created = created.Select(ToPlayerEvent).ToList(),
|
||||
Responses = []
|
||||
};
|
||||
}
|
||||
|
||||
[HttpGet("v1/created"), Authorize]
|
||||
[HttpGet("v2/created"), Authorize]
|
||||
public async Task<List<PlayerEvent>> GetCreatedEvents()
|
||||
{
|
||||
var userId = User.GetUserId() ?? 0;
|
||||
|
||||
var created = await db.PlayerEvents
|
||||
.Where(x => x.CreatorPlayerId == userId)
|
||||
.OrderByDescending(x => x.StartTime)
|
||||
.ToListAsync();
|
||||
|
||||
return created.Select(ToPlayerEvent).ToList();
|
||||
}
|
||||
|
||||
[HttpGet("v1/room/{roomId}"), Authorize]
|
||||
public async Task<List<PlayerEvent>> GetEventsInRoom(long roomId)
|
||||
{
|
||||
var events = await db.PlayerEvents.ToListAsync();
|
||||
return PlayerEventQueries.InRoom(events, roomId).Select(ToPlayerEvent).ToList();
|
||||
}
|
||||
|
||||
[HttpGet("v1/clubs"), Authorize]
|
||||
public async Task<List<PlayerEvent>> GetEventsByClubs([FromQuery] List<long>? id)
|
||||
{
|
||||
if (id == null || id.Count == 0) return [];
|
||||
|
||||
var events = await db.PlayerEvents
|
||||
.Where(x => x.ClubId != null && id.Contains(x.ClubId.Value))
|
||||
.OrderBy(x => x.StartTime)
|
||||
.ToListAsync();
|
||||
return events.Select(ToPlayerEvent).ToList();
|
||||
}
|
||||
|
||||
[HttpGet("v1/club/{clubId:long}"), Authorize]
|
||||
[HttpGet("v1/club_events/{clubId:long}"), Authorize]
|
||||
public async Task<IActionResult> GetEventsByClub(long clubId)
|
||||
{
|
||||
var events = await db.PlayerEvents
|
||||
.Where(x => x.ClubId == clubId)
|
||||
.OrderBy(x => x.StartTime)
|
||||
.ToListAsync();
|
||||
return Ok(new { Events = events.Select(ToPlayerEvent).ToList(), ContinuationToken = string.Empty });
|
||||
}
|
||||
|
||||
[HttpGet("v1/events_by_club_ids/{clubIds}"), Authorize]
|
||||
public IActionResult GetEventsByClubIdsLegacy(string clubIds)
|
||||
{
|
||||
return Ok(Array.Empty<object>());
|
||||
}
|
||||
|
||||
[HttpGet("v1/remote_player_event_list/{accountId}"), Authorize]
|
||||
public IActionResult GetRemotePlayerEventList(long accountId)
|
||||
{
|
||||
return Ok(new
|
||||
{
|
||||
Events = Array.Empty<object>(),
|
||||
Responses = Array.Empty<object>()
|
||||
});
|
||||
}
|
||||
|
||||
[HttpGet("v1/upcoming_events"), Authorize]
|
||||
public async Task<List<PlayerEvent>> GetUpcomingEvents()
|
||||
{
|
||||
var events = await db.PlayerEvents
|
||||
.Where(x => x.EndTime > DateTime.UtcNow)
|
||||
.OrderBy(x => x.StartTime)
|
||||
.Take(50)
|
||||
.ToListAsync();
|
||||
return events.Select(ToPlayerEvent).ToList();
|
||||
}
|
||||
|
||||
[HttpGet("v1/filters"), Authorize]
|
||||
public RoomFilters GetEventFilters() => GetEventTagFilters();
|
||||
|
||||
[HttpGet("v1/{eventId:long}"), Authorize]
|
||||
[HttpGet("v1/event/{eventId}"), Authorize]
|
||||
public async Task<ActionResult<PlayerEvent>> GetEvent(long eventId)
|
||||
{
|
||||
var ev = await db.PlayerEvents.FirstOrDefaultAsync(x => x.PlayerEventId == eventId);
|
||||
if (ev == null) return NotFound();
|
||||
return Ok(ToPlayerEvent(ev));
|
||||
}
|
||||
|
||||
[HttpGet("v2/{eventId:long}"), Authorize]
|
||||
public async Task<IActionResult> GetEventV2(long eventId)
|
||||
{
|
||||
var ev = await db.PlayerEvents.FirstOrDefaultAsync(x => x.PlayerEventId == eventId);
|
||||
if (ev == null) return NotFound();
|
||||
return Ok(ToEventResult(ev));
|
||||
}
|
||||
|
||||
[HttpPost("v1/create"), Authorize]
|
||||
[HttpPost("v1/event"), Authorize]
|
||||
[HttpPost("v1/event/create"), Authorize]
|
||||
public async Task<ActionResult<PlayerEvent>> CreateEvent(
|
||||
[FromForm] string name,
|
||||
[FromForm] string? description,
|
||||
[FromForm] long roomId,
|
||||
[FromForm] DateTime? startTime,
|
||||
[FromForm] DateTime? endTime,
|
||||
[FromForm] string? imageName,
|
||||
[FromForm] Accessibility? accessibility)
|
||||
{
|
||||
var userId = User.GetUserId();
|
||||
if (userId == null) return Unauthorized();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
return BadRequest(new { error = "Event name is required." });
|
||||
if (roomId <= 0)
|
||||
return BadRequest(new { error = "A valid room id is required." });
|
||||
|
||||
var room = await db.Rooms.FirstOrDefaultAsync(x => x.RoomId == roomId);
|
||||
if (room == null)
|
||||
return BadRequest(new { error = "Room does not exist." });
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
var ev = new PlayerEventDB
|
||||
{
|
||||
Name = name.Trim(),
|
||||
Description = description ?? string.Empty,
|
||||
ImageName = string.IsNullOrWhiteSpace(imageName) ? string.Empty : imageName,
|
||||
StartTime = EventClock.FromPostedTime(startTime ?? now),
|
||||
EndTime = EventClock.FromPostedTime(endTime ?? now.AddHours(2)),
|
||||
CreatorPlayerId = userId.Value,
|
||||
AttendeeCount = 1,
|
||||
RoomId = roomId,
|
||||
Accessibility = accessibility ?? Accessibility.Public,
|
||||
CreatedAt = now
|
||||
};
|
||||
|
||||
await db.PlayerEvents.AddAsync(ev);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
|
||||
await db.PlayerEventResponses.AddAsync(new PlayerEventResponse
|
||||
{
|
||||
PlayerEventId = ev.PlayerEventId,
|
||||
PlayerId = userId.Value,
|
||||
Type = EventResponses.Yes
|
||||
});
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
await PlayerEventNotifications.PushAsync(userId.Value, PlayerEventNotifications.Created(ev));
|
||||
|
||||
return Ok(ToPlayerEvent(ev));
|
||||
}
|
||||
|
||||
[HttpPost("v2"), Authorize]
|
||||
public async Task<IActionResult> CreateEventV2()
|
||||
{
|
||||
var userId = User.GetUserId();
|
||||
if (userId == null) return Unauthorized();
|
||||
|
||||
var fields = await ReadEventFieldsAsync();
|
||||
|
||||
var name = GetEventField(fields, "Name", "name");
|
||||
var roomId = GetEventLong(fields, "RoomId", "roomId") ?? 0;
|
||||
if (string.IsNullOrWhiteSpace(name) || roomId <= 0)
|
||||
return BadRequest(new { error = "Invalid event request." });
|
||||
|
||||
var room = await db.Rooms.FirstOrDefaultAsync(x => x.RoomId == roomId);
|
||||
if (room == null)
|
||||
return BadRequest(new { error = "Room does not exist." });
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
var ev = new PlayerEventDB
|
||||
{
|
||||
Name = name.Trim(),
|
||||
Description = GetEventField(fields, "Description", "description") ?? string.Empty,
|
||||
ImageName = GetEventField(fields, "ImageName", "imageName") ?? string.Empty,
|
||||
StartTime = ParseEventDate(GetEventField(fields, "StartTime", "startTime"), now),
|
||||
EndTime = ParseEventDate(GetEventField(fields, "EndTime", "endTime"), now.AddHours(2)),
|
||||
CreatorPlayerId = userId.Value,
|
||||
AttendeeCount = 0,
|
||||
RoomId = roomId,
|
||||
SubRoomId = GetEventLong(fields, "SubRoomId", "subRoomId"),
|
||||
ClubId = GetEventLong(fields, "ClubId", "clubId"),
|
||||
Accessibility = ParseEventAccessibility(GetEventField(fields, "Accessibility", "accessibility")),
|
||||
IsMultiInstance = ParseEventBoolean(GetEventField(fields, "IsMultiInstance", "isMultiInstance")),
|
||||
SupportMultiInstanceRoomChat = ParseEventBoolean(GetEventField(fields, "SupportMultiInstanceRoomChat", "supportMultiInstanceRoomChat")),
|
||||
DefaultBroadcastPermissions = GetEventInt(fields, "DefaultBroadcastPermissions", "defaultBroadcastPermissions") ?? 0,
|
||||
CanRequestBroadcastPermissions = GetEventInt(fields, "CanRequestBroadcastPermissions", "canRequestBroadcastPermissions") ?? 0,
|
||||
TagsJson = JsonSerializer.Serialize(ParsePostedEventTags(fields)),
|
||||
CreatedAt = now
|
||||
};
|
||||
|
||||
await db.PlayerEvents.AddAsync(ev);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
await db.PlayerEventResponses.AddAsync(new PlayerEventResponse
|
||||
{
|
||||
PlayerEventId = ev.PlayerEventId,
|
||||
PlayerId = userId.Value,
|
||||
Type = EventResponses.Yes
|
||||
});
|
||||
ev.AttendeeCount = 1;
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
|
||||
|
||||
await PlayerEventNotifications.PushAsync(userId.Value, PlayerEventNotifications.Created(ev));
|
||||
|
||||
return Ok(ToEventResult(ev));
|
||||
}
|
||||
|
||||
[HttpPost("v2/{eventId:long}"), Authorize]
|
||||
[HttpPut("v2/{eventId:long}/accessibility"), Authorize]
|
||||
[HttpPut("v2/{eventId:long}/club"), Authorize]
|
||||
[HttpPut("v2/{eventId:long}/description"), Authorize]
|
||||
[HttpPut("v2/{eventId:long}/image"), Authorize]
|
||||
[HttpPut("v2/{eventId:long}/multiinstance"), Authorize]
|
||||
[HttpPut("v2/{eventId:long}/name"), Authorize]
|
||||
[HttpPut("v2/{eventId:long}/room"), Authorize]
|
||||
[HttpPut("v2/{eventId:long}/tags"), Authorize]
|
||||
[HttpPut("v2/{eventId:long}/time"), Authorize]
|
||||
public async Task<IActionResult> UpdateEventV2(long eventId)
|
||||
{
|
||||
var userId = User.GetUserId();
|
||||
if (userId == null) return Unauthorized();
|
||||
|
||||
var ev = await db.PlayerEvents.FirstOrDefaultAsync(x => x.PlayerEventId == eventId);
|
||||
if (ev == null) return NotFound();
|
||||
if (ev.CreatorPlayerId != userId) return Forbid();
|
||||
|
||||
var fields = await ReadEventFieldsAsync();
|
||||
|
||||
var name = GetEventField(fields, "Name", "name");
|
||||
if (name != null) ev.Name = name.Trim();
|
||||
|
||||
if (fields.TryGetValue("Description", out var description)) ev.Description = description;
|
||||
|
||||
if (fields.TryGetValue("ImageName", out var imageName)) ev.ImageName = imageName;
|
||||
|
||||
var startTime = GetEventField(fields, "StartTime", "startTime");
|
||||
if (startTime != null) ev.StartTime = ParseEventDate(startTime, ev.StartTime);
|
||||
|
||||
var endTime = GetEventField(fields, "EndTime", "endTime");
|
||||
if (endTime != null) ev.EndTime = ParseEventDate(endTime, ev.EndTime);
|
||||
|
||||
var roomId = GetEventLong(fields, "RoomId", "roomId");
|
||||
if (roomId is > 0 && await db.Rooms.AnyAsync(x => x.RoomId == roomId)) ev.RoomId = roomId.Value;
|
||||
|
||||
if (fields.TryGetValue("SubRoomId", out var subRoomIdRaw) &&
|
||||
TryParseNullableEventId(subRoomIdRaw, out var subRoomId))
|
||||
ev.SubRoomId = subRoomId;
|
||||
|
||||
if (fields.TryGetValue("ClubId", out var clubIdRaw) &&
|
||||
TryParseNullableEventId(clubIdRaw, out var clubId))
|
||||
ev.ClubId = clubId;
|
||||
|
||||
|
||||
var accessibility = GetEventField(fields, "Accessibility", "accessibility");
|
||||
if (accessibility != null) ev.Accessibility = ParseEventAccessibility(accessibility);
|
||||
|
||||
var multiInstance = GetEventField(fields, "IsMultiInstance", "isMultiInstance");
|
||||
if (multiInstance != null) ev.IsMultiInstance = ParseEventBoolean(multiInstance);
|
||||
|
||||
var roomChat = GetEventField(fields, "SupportMultiInstanceRoomChat", "supportMultiInstanceRoomChat");
|
||||
if (roomChat != null) ev.SupportMultiInstanceRoomChat = ParseEventBoolean(roomChat);
|
||||
|
||||
var defaultBroadcastPermissions = GetEventInt(fields, "DefaultBroadcastPermissions", "defaultBroadcastPermissions");
|
||||
if (defaultBroadcastPermissions != null) ev.DefaultBroadcastPermissions = defaultBroadcastPermissions.Value;
|
||||
|
||||
var canRequestBroadcastPermissions = GetEventInt(fields, "CanRequestBroadcastPermissions", "canRequestBroadcastPermissions");
|
||||
if (canRequestBroadcastPermissions != null) ev.CanRequestBroadcastPermissions = canRequestBroadcastPermissions.Value;
|
||||
|
||||
if (fields.ContainsKey("Tags"))
|
||||
ev.TagsJson = JsonSerializer.Serialize(ParsePostedEventTags(fields));
|
||||
|
||||
var updatePath = Request?.Path.Value?.TrimEnd('/').Split('/').LastOrDefault()?.ToLowerInvariant();
|
||||
switch (updatePath)
|
||||
{
|
||||
case "accessibility":
|
||||
if (GetEventField(fields, "Accessibility", "accessibility") is { } accessibilityValue)
|
||||
ev.Accessibility = ParseEventAccessibility(accessibilityValue);
|
||||
break;
|
||||
case "club":
|
||||
if (fields.TryGetValue("ClubId", out var clubValue) && TryParseNullableEventId(clubValue, out var parsedClubId))
|
||||
ev.ClubId = parsedClubId;
|
||||
break;
|
||||
case "description":
|
||||
if (fields.TryGetValue("Description", out var descriptionValue)) ev.Description = descriptionValue;
|
||||
break;
|
||||
case "image":
|
||||
if (fields.TryGetValue("ImageName", out var imageValue)) ev.ImageName = imageValue;
|
||||
break;
|
||||
case "multiinstance":
|
||||
if (GetEventField(fields, "IsMultiInstance", "isMultiInstance") is { } multiInstanceValue)
|
||||
ev.IsMultiInstance = ParseEventBoolean(multiInstanceValue);
|
||||
if (GetEventField(fields, "SupportMultiInstanceRoomChat", "supportMultiInstanceRoomChat") is { } multiChatValue)
|
||||
ev.SupportMultiInstanceRoomChat = ParseEventBoolean(multiChatValue);
|
||||
if (GetEventInt(fields, "DefaultBroadcastPermissions", "defaultBroadcastPermissions") is { } defaultPermissions)
|
||||
ev.DefaultBroadcastPermissions = defaultPermissions;
|
||||
if (GetEventInt(fields, "CanRequestBroadcastPermissions", "canRequestBroadcastPermissions") is { } requestPermissions)
|
||||
ev.CanRequestBroadcastPermissions = requestPermissions;
|
||||
break;
|
||||
case "name":
|
||||
if (fields.TryGetValue("Name", out var eventName)) ev.Name = eventName.Trim();
|
||||
break;
|
||||
case "room":
|
||||
var updatedRoomId = GetEventLong(fields, "RoomId", "roomId");
|
||||
if (updatedRoomId is > 0 &&
|
||||
await db.Rooms.AnyAsync(x => x.RoomId == updatedRoomId.Value))
|
||||
ev.RoomId = updatedRoomId.Value;
|
||||
if (fields.TryGetValue("SubRoomId", out var subRoomValue) && TryParseNullableEventId(subRoomValue, out var parsedSubRoomId))
|
||||
ev.SubRoomId = parsedSubRoomId;
|
||||
break;
|
||||
case "tags":
|
||||
if (GetEventField(fields, "Tags", "tags") != null)
|
||||
ev.TagsJson = JsonSerializer.Serialize(ParsePostedEventTags(fields));
|
||||
break;
|
||||
case "time":
|
||||
if (GetEventField(fields, "StartTime", "startTime") is { } startValue)
|
||||
ev.StartTime = ParseEventDate(startValue, ev.StartTime);
|
||||
if (GetEventField(fields, "EndTime", "endTime") is { } endValue)
|
||||
ev.EndTime = ParseEventDate(endValue, ev.EndTime);
|
||||
break;
|
||||
}
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
await PlayerEventNotifications.PushAsync(ev.CreatorPlayerId, PlayerEventNotifications.Updated(ev));
|
||||
|
||||
return Ok(ToEventResult(ev));
|
||||
}
|
||||
|
||||
[HttpPost("v1/respond"), Authorize]
|
||||
public async Task<IActionResult> RespondToEvent()
|
||||
{
|
||||
var userId = User.GetUserId();
|
||||
if (userId == null) return Unauthorized();
|
||||
|
||||
var body = await ReadJsonBodyAsync();
|
||||
if (body == null) return BadRequest();
|
||||
|
||||
var eventId = GetBodyLong(body.Value, "PlayerEventId", "playerEventId");
|
||||
var typeRaw = GetBodyLong(body.Value, "Type", "type");
|
||||
if (eventId == null || typeRaw == null) return BadRequest();
|
||||
|
||||
if (!Enum.IsDefined(typeof(EventResponses), (int)typeRaw.Value) ||
|
||||
(EventResponses)(int)typeRaw.Value is EventResponses.None or EventResponses.Pending)
|
||||
return BadRequest();
|
||||
|
||||
var response = await SetEventResponse((long)eventId, userId.Value, (EventResponses)(int)typeRaw.Value);
|
||||
if (response == null) return NotFound();
|
||||
|
||||
return Ok(ToEventResult(response.Value.ev));
|
||||
}
|
||||
|
||||
[HttpPost("v1/event/{eventId}/response"), Authorize]
|
||||
public async Task<ActionResult<EventResponse>> SetResponse(long eventId, [FromForm] EventResponses response)
|
||||
{
|
||||
var userId = User.GetUserId();
|
||||
if (userId == null) return Unauthorized();
|
||||
|
||||
if (!Enum.IsDefined(typeof(EventResponses), response) || response is EventResponses.None or EventResponses.Pending)
|
||||
return BadRequest();
|
||||
|
||||
var result = await SetEventResponse(eventId, userId.Value, response);
|
||||
if (result == null) return NotFound();
|
||||
|
||||
return Ok(ToEventResponse(result.Value.response));
|
||||
}
|
||||
|
||||
private async Task<(PlayerEventResponse response, PlayerEventDB ev)?> SetEventResponse(
|
||||
long eventId, long playerId, EventResponses type)
|
||||
{
|
||||
var ev = await db.PlayerEvents.FirstOrDefaultAsync(x => x.PlayerEventId == eventId);
|
||||
if (ev == null) return null;
|
||||
|
||||
var existing = await db.PlayerEventResponses
|
||||
.FirstOrDefaultAsync(x => x.PlayerEventId == eventId && x.PlayerId == playerId);
|
||||
|
||||
var hasInvitation = existing?.Type == EventResponses.Pending ||
|
||||
await db.Messages.AsNoTracking().AnyAsync(message =>
|
||||
message.ToPlayerId == playerId &&
|
||||
message.PlayerEventId == eventId &&
|
||||
message.Type == MessageType.PlayerEventInvitation);
|
||||
if (ev.Accessibility == Accessibility.Private &&
|
||||
ev.CreatorPlayerId != playerId &&
|
||||
!hasInvitation)
|
||||
return null;
|
||||
|
||||
if (existing == null)
|
||||
{
|
||||
existing = new PlayerEventResponse
|
||||
{
|
||||
PlayerEventId = eventId,
|
||||
PlayerId = playerId,
|
||||
Type = type
|
||||
};
|
||||
await db.PlayerEventResponses.AddAsync(existing);
|
||||
}
|
||||
else
|
||||
{
|
||||
existing.Type = type;
|
||||
}
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
ev.AttendeeCount = await db.PlayerEventResponses
|
||||
.CountAsync(x => x.PlayerEventId == eventId && x.Type == EventResponses.Yes);
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
return (existing, ev);
|
||||
}
|
||||
|
||||
[HttpPost("v2/rsvp/{eventId:long}"), Authorize]
|
||||
public async Task<ActionResult<EventResponse>> RespondToEventV2(long eventId)
|
||||
{
|
||||
var userId = User.GetUserId();
|
||||
if (userId == null) return Unauthorized();
|
||||
|
||||
var result = await SetEventResponse(eventId, userId.Value, EventResponses.Yes);
|
||||
if (result == null) return NotFound();
|
||||
|
||||
return Ok(ToEventResponse(result.Value.response));
|
||||
}
|
||||
|
||||
[HttpPost("v1/broadcast"), Authorize]
|
||||
[HttpPut("v1/broadcast"), Authorize]
|
||||
public async Task<IActionResult> BroadcastRoomInstance()
|
||||
{
|
||||
var userId = User.GetUserId();
|
||||
if (userId == null) return Unauthorized();
|
||||
|
||||
var fields = await ReadEventFieldsAsync();
|
||||
var eventId = GetEventLong(fields, "PlayerEventId", "playerEventId", "EventId", "eventId", "Id", "id") ?? 0;
|
||||
if (eventId <= 0) eventId = await ReadEventIdFromRequestAsync();
|
||||
if (eventId <= 0) return BadRequest(new { error = "PlayerEventId is required." });
|
||||
|
||||
var ev = await db.PlayerEvents.FirstOrDefaultAsync(x => x.PlayerEventId == eventId);
|
||||
if (ev == null) return NotFound();
|
||||
|
||||
if (ev.CreatorPlayerId != userId && !await db.IsDeveloper(userId.Value))
|
||||
return Forbid();
|
||||
|
||||
|
||||
long? roomInstanceId = null;
|
||||
var rawInstance = GetEventField(fields,
|
||||
"RoomInstanceId", "roomInstanceId", "BroadcastingRoomInstanceId", "broadcastingRoomInstanceId",
|
||||
"BroadcastRoomInstanceId", "broadcastRoomInstanceId");
|
||||
if (!string.IsNullOrWhiteSpace(rawInstance) &&
|
||||
!string.Equals(rawInstance, "null", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (!long.TryParse(rawInstance, out var parsedInstance) || parsedInstance <= 0)
|
||||
return BadRequest(new { error = "RoomInstanceId must be a positive id or null." });
|
||||
roomInstanceId = parsedInstance;
|
||||
}
|
||||
|
||||
if (roomInstanceId.HasValue)
|
||||
{
|
||||
|
||||
|
||||
|
||||
if (!ev.IsMultiInstance)
|
||||
return Ok(ToEventResult(ev, EventResultBroadcastNotMultiInstance));
|
||||
|
||||
var instance = await db.RoomInstances
|
||||
.FirstOrDefaultAsync(x => x.roomInstanceId == roomInstanceId.Value);
|
||||
if (instance == null || instance.roomId != ev.RoomId)
|
||||
return Ok(ToEventResult(ev, EventResultBroadcastNotMultiInstance));
|
||||
}
|
||||
|
||||
ev.BroadcastingRoomInstanceId = roomInstanceId;
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
|
||||
|
||||
await PlayerEventNotifications.PushToEventInstancesAsync(db, ev);
|
||||
|
||||
return Ok(ToEventResult(ev));
|
||||
}
|
||||
|
||||
[HttpPost("v1/deleteResponse"), Authorize]
|
||||
[HttpDelete("v1/deleteResponse"), Authorize]
|
||||
public async Task<IActionResult> DeleteEventResponse()
|
||||
{
|
||||
var userId = User.GetUserId();
|
||||
if (userId == null) return Unauthorized();
|
||||
|
||||
var fields = await ReadEventFieldsAsync();
|
||||
var eventId = GetEventLong(fields, "PlayerEventId", "playerEventId", "EventId", "eventId", "Id", "id") ?? 0;
|
||||
if (eventId <= 0) eventId = await ReadEventIdFromRequestAsync();
|
||||
if (eventId <= 0) return BadRequest(new { error = "PlayerEventId is required." });
|
||||
|
||||
var ev = await db.PlayerEvents.FirstOrDefaultAsync(x => x.PlayerEventId == eventId);
|
||||
if (ev == null) return NotFound();
|
||||
|
||||
var existing = await db.PlayerEventResponses
|
||||
.FirstOrDefaultAsync(x => x.PlayerEventId == eventId && x.PlayerId == userId.Value);
|
||||
if (existing != null)
|
||||
{
|
||||
db.PlayerEventResponses.Remove(existing);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
ev.AttendeeCount = await db.PlayerEventResponses
|
||||
.CountAsync(x => x.PlayerEventId == eventId && x.Type == EventResponses.Yes);
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
return Ok(ToEventResult(ev));
|
||||
}
|
||||
|
||||
[HttpPost("v1/report"), Authorize]
|
||||
public async Task<IActionResult> ReportEvent()
|
||||
{
|
||||
var reporterId = User.GetUserId();
|
||||
if (reporterId == null) return Unauthorized();
|
||||
|
||||
var body = await ReadJsonBodyAsync();
|
||||
var eventId = body == null ? null : GetBodyLong(body.Value, "PlayerEventId", "playerEventId");
|
||||
if (eventId == null)
|
||||
return Ok(new { success = false, error = "PlayerEventId is required" });
|
||||
|
||||
var ev = await db.PlayerEvents.FirstOrDefaultAsync(x => x.PlayerEventId == eventId);
|
||||
if (ev == null)
|
||||
return Ok(new { success = false, error = "No such event" });
|
||||
|
||||
int category = 0;
|
||||
string? details = null;
|
||||
if (body != null)
|
||||
{
|
||||
if (body.Value.TryGetProperty("ReportCategory", out var catRaw) &&
|
||||
long.TryParse(catRaw.ToString(), out var catParsed))
|
||||
category = (int)catParsed;
|
||||
if (body.Value.TryGetProperty("Details", out var detailsRaw) &&
|
||||
detailsRaw.ValueKind == JsonValueKind.String)
|
||||
details = detailsRaw.GetString();
|
||||
}
|
||||
|
||||
return Ok(new { success = true, error = string.Empty });
|
||||
}
|
||||
|
||||
[HttpPost("v1/bulkInvite"), Authorize]
|
||||
public async Task<IActionResult> BulkInvite()
|
||||
{
|
||||
var userId = User.GetUserId();
|
||||
if (userId == null) return Unauthorized();
|
||||
|
||||
var fields = await ReadEventFieldsAsync();
|
||||
var eventIdRaw = GetEventField(fields, "PlayerEventId", "playerEventId", "EventId", "eventId");
|
||||
if (!long.TryParse(eventIdRaw, out var eventId) || eventId <= 0)
|
||||
return BadRequest(new { error = "PlayerEventId is required." });
|
||||
|
||||
var ev = await db.PlayerEvents.FirstOrDefaultAsync(x => x.PlayerEventId == eventId);
|
||||
if (ev == null) return NotFound();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if (!await CanInviteToEventAsync(ev, userId.Value))
|
||||
return StatusCode(StatusCodes.Status403Forbidden, new Dictionary<string, object?>
|
||||
{
|
||||
["error"] = "That event belongs to another player.",
|
||||
["success"] = false,
|
||||
["value"] = null,
|
||||
["PlayerEventId"] = ev.PlayerEventId,
|
||||
["CreatorPlayerId"] = ev.CreatorPlayerId
|
||||
});
|
||||
|
||||
var invitedPlayerIds = await ReadInvitedPlayerIdsAsync();
|
||||
if (invitedPlayerIds.Count == 0)
|
||||
return BadRequest(new { error = "At least one invited player is required." });
|
||||
|
||||
var failedInvites = new List<object>();
|
||||
|
||||
|
||||
var invitedPlayers = new List<long>();
|
||||
var validPlayerIds = invitedPlayerIds
|
||||
.Where(playerId => playerId > 0 && playerId != userId.Value)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
var profiles = await db.Profiles.AsNoTracking()
|
||||
.Where(profile => validPlayerIds.Contains(profile.accountId))
|
||||
.Select(profile => profile.accountId)
|
||||
.ToListAsync();
|
||||
var existingResponses = await db.PlayerEventResponses
|
||||
.Where(response => response.PlayerEventId == eventId && validPlayerIds.Contains(response.PlayerId))
|
||||
.ToDictionaryAsync(response => response.PlayerId);
|
||||
|
||||
foreach (var playerId in invitedPlayerIds.Distinct())
|
||||
{
|
||||
if (playerId <= 0 || playerId == userId.Value || !profiles.Contains(playerId))
|
||||
{
|
||||
failedInvites.Add(new { PlayerId = playerId });
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!existingResponses.ContainsKey(playerId))
|
||||
{
|
||||
existingResponses[playerId] = new PlayerEventResponse
|
||||
{
|
||||
PlayerEventId = eventId,
|
||||
PlayerId = playerId,
|
||||
Type = EventResponses.Pending
|
||||
};
|
||||
await db.PlayerEventResponses.AddAsync(existingResponses[playerId]);
|
||||
invitedPlayers.Add(playerId);
|
||||
}
|
||||
else if (existingResponses[playerId].Type is not (EventResponses.Yes or EventResponses.Pending))
|
||||
{
|
||||
existingResponses[playerId].Type = EventResponses.Pending;
|
||||
}
|
||||
}
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
foreach (var playerId in invitedPlayers)
|
||||
await PlayerEventNotifications.PushAsync(playerId,
|
||||
PlayerEventNotifications.ResponseChanged(ev, existingResponses[playerId]));
|
||||
|
||||
foreach (var playerId in validPlayerIds.Where(profiles.Contains))
|
||||
{
|
||||
await Notify.SendMessage(playerId, new Message
|
||||
{
|
||||
FromPlayerId = userId.Value,
|
||||
Type = MessageType.PlayerEventInvitation,
|
||||
PlayerEventId = eventId
|
||||
});
|
||||
}
|
||||
|
||||
return Ok(new Dictionary<string, object?>
|
||||
{
|
||||
["FailedInvites"] = failedInvites,
|
||||
["Result"] = 0
|
||||
});
|
||||
}
|
||||
|
||||
private async Task<bool> CanInviteToEventAsync(PlayerEventDB ev, long userId)
|
||||
{
|
||||
if (ev.CreatorPlayerId == userId) return true;
|
||||
|
||||
if (await db.PlayerEventResponses
|
||||
.AnyAsync(x => x.PlayerEventId == ev.PlayerEventId && x.PlayerId == userId))
|
||||
return true;
|
||||
|
||||
if (ev.ClubId is > 0)
|
||||
{
|
||||
var club = await ClubStore.GetAsync(db, ev.ClubId.Value);
|
||||
if (club != null && ClubStore.GetMembershipType(club, userId) >= ClubMembershipTypes.Coowner)
|
||||
return true;
|
||||
}
|
||||
|
||||
return !await db.Profiles.AnyAsync(x => x.accountId == ev.CreatorPlayerId);
|
||||
}
|
||||
|
||||
private async Task<List<long>> ReadInvitedPlayerIdsAsync()
|
||||
{
|
||||
var ids = new List<long>();
|
||||
if (Request.Body.CanSeek) Request.Body.Position = 0;
|
||||
if (Request.HasFormContentType)
|
||||
{
|
||||
var form = await Request.ReadFormAsync();
|
||||
foreach (var field in form.Where(field => field.Key.Equals("InvitedPlayerIds", StringComparison.OrdinalIgnoreCase) ||
|
||||
field.Key.Equals("InvitedPlayerIds[]", StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
foreach (var value in field.Value)
|
||||
if (long.TryParse(value, out var id)) ids.Add(id);
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
Request.EnableBuffering();
|
||||
using var reader = new StreamReader(Request.Body, leaveOpen: true);
|
||||
var body = await reader.ReadToEndAsync();
|
||||
if (Request.Body.CanSeek) Request.Body.Position = 0;
|
||||
try
|
||||
{
|
||||
using var document = JsonDocument.Parse(body);
|
||||
if (document.RootElement.ValueKind == JsonValueKind.Object)
|
||||
{
|
||||
foreach (var property in document.RootElement.EnumerateObject()
|
||||
.Where(property => property.Name.Equals("InvitedPlayerIds", StringComparison.OrdinalIgnoreCase) ||
|
||||
property.Name.Equals("InvitedPlayerIds[]", StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
if (property.Value.ValueKind != JsonValueKind.Array) continue;
|
||||
foreach (var value in property.Value.EnumerateArray())
|
||||
if (long.TryParse(value.ToString(), out var id)) ids.Add(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (JsonException) { }
|
||||
return ids;
|
||||
}
|
||||
|
||||
[HttpGet("v1/event/{eventId}/responses"), Authorize]
|
||||
[HttpGet("v1/{eventId:long}/responses"), Authorize]
|
||||
public async Task<ActionResult<List<EventResponse>>> GetResponses(long eventId)
|
||||
{
|
||||
if (eventId == -1) return Ok(Array.Empty<EventResponse>());
|
||||
|
||||
var ev = await db.PlayerEvents.FirstOrDefaultAsync(x => x.PlayerEventId == eventId);
|
||||
if (ev == null) return NotFound();
|
||||
|
||||
var responses = await db.PlayerEventResponses
|
||||
.Where(x => x.PlayerEventId == eventId)
|
||||
.ToListAsync();
|
||||
|
||||
return Ok(responses.Select(ToEventResponse).ToList());
|
||||
}
|
||||
|
||||
[HttpGet("v1/-1/responses"), Authorize]
|
||||
public IActionResult GetLegacyPlaceholderResponses() => Ok(Array.Empty<EventResponse>());
|
||||
|
||||
[HttpGet("v1/event/{eventId}/attendees"), Authorize]
|
||||
[HttpGet("v1/{eventId:long}/attendees"), Authorize]
|
||||
public async Task<ActionResult<List<long>>> GetAttendees(long eventId)
|
||||
{
|
||||
var ev = await db.PlayerEvents.FirstOrDefaultAsync(x => x.PlayerEventId == eventId);
|
||||
if (ev == null) return NotFound();
|
||||
|
||||
var attendees = await db.PlayerEventResponses
|
||||
.Where(x => x.PlayerEventId == eventId && x.Type == EventResponses.Yes)
|
||||
.Select(x => x.PlayerId)
|
||||
.ToListAsync();
|
||||
|
||||
return Ok(attendees);
|
||||
}
|
||||
|
||||
[HttpPost("v2/delete/{eventId:long}"), Authorize]
|
||||
[HttpDelete("v2/delete/{eventId:long}"), Authorize]
|
||||
[HttpPost("v2/delete"), Authorize]
|
||||
[HttpDelete("v2/delete"), Authorize]
|
||||
[HttpPost("v2/{eventId:long}/delete"), Authorize]
|
||||
[HttpDelete("v2/{eventId:long}/delete"), Authorize]
|
||||
public async Task<IActionResult> DeleteEventV2(long eventId = 0)
|
||||
{
|
||||
var userId = User.GetUserId();
|
||||
if (userId == null) return Unauthorized();
|
||||
|
||||
var id = eventId > 0 ? eventId : await ReadEventIdFromRequestAsync();
|
||||
if (id <= 0) return NotFound();
|
||||
|
||||
var ev = await db.PlayerEvents.FirstOrDefaultAsync(x => x.PlayerEventId == id);
|
||||
|
||||
if (ev != null && ev.CreatorPlayerId != userId) return Forbid();
|
||||
|
||||
if (ev == null)
|
||||
{
|
||||
await PlayerEventNotifications.PushAsync(userId.Value, PlayerEventNotifications.Deleted(id));
|
||||
return Ok(ToEventResult(AlreadyGone(id, userId.Value)));
|
||||
}
|
||||
|
||||
await DeleteEventRow(ev);
|
||||
|
||||
await PlayerEventNotifications.PushAsync(userId.Value, PlayerEventNotifications.Deleted(ev.PlayerEventId));
|
||||
|
||||
return Ok(ToEventResult(ev));
|
||||
}
|
||||
|
||||
private static PlayerEventDB AlreadyGone(long eventId, long creatorPlayerId) => new()
|
||||
{
|
||||
PlayerEventId = eventId,
|
||||
Name = string.Empty,
|
||||
Description = string.Empty,
|
||||
ImageName = string.Empty,
|
||||
CreatorPlayerId = creatorPlayerId,
|
||||
TagsJson = "[]"
|
||||
};
|
||||
|
||||
private async Task<long> ReadEventIdFromRequestAsync()
|
||||
{
|
||||
foreach (var key in new[] { "PlayerEventId", "playerEventId", "EventId", "eventId", "Id", "id" })
|
||||
if (Request.Query.TryGetValue(key, out var query) &&
|
||||
long.TryParse(query.FirstOrDefault(), out var queryId) && queryId > 0)
|
||||
return queryId;
|
||||
|
||||
if (Request.ContentLength is > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
Request.EnableBuffering();
|
||||
var fields = await ReadEventFieldsAsync();
|
||||
var bodyId = GetEventLong(fields, "PlayerEventId", "playerEventId", "EventId", "eventId", "Id", "id") ?? 0;
|
||||
Request.Body.Position = 0;
|
||||
if (bodyId > 0) return bodyId;
|
||||
}
|
||||
catch
|
||||
{
|
||||
try { Request.Body.Position = 0; } catch { }
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
[HttpDelete("v1/event/{eventId}"), Authorize]
|
||||
[HttpDelete("v1/{eventId:long}"), Authorize]
|
||||
public async Task<IActionResult> DeleteEvent(long eventId)
|
||||
{
|
||||
var userId = User.GetUserId();
|
||||
if (userId == null) return Unauthorized();
|
||||
|
||||
var ev = await db.PlayerEvents.FirstOrDefaultAsync(x => x.PlayerEventId == eventId);
|
||||
if (ev == null) return NotFound();
|
||||
|
||||
if (ev.CreatorPlayerId != userId)
|
||||
return Forbid();
|
||||
|
||||
await DeleteEventRow(ev);
|
||||
|
||||
await PlayerEventNotifications.PushAsync(userId.Value, PlayerEventNotifications.Deleted(ev.PlayerEventId));
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
private async Task DeleteEventRow(PlayerEventDB ev)
|
||||
{
|
||||
var responses = await db.PlayerEventResponses.Where(x => x.PlayerEventId == ev.PlayerEventId).ToListAsync();
|
||||
db.PlayerEventResponses.RemoveRange(responses);
|
||||
db.PlayerEvents.Remove(ev);
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
private async Task<Dictionary<string, string>> ReadEventFieldsAsync()
|
||||
{
|
||||
var fields = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
Request.EnableBuffering();
|
||||
|
||||
foreach (var kv in Request.Query)
|
||||
fields[kv.Key] = kv.Value.ToString();
|
||||
|
||||
if (Request.HasFormContentType)
|
||||
{
|
||||
var form = await Request.ReadFormAsync();
|
||||
foreach (var kv in form)
|
||||
fields[kv.Key] = kv.Value.ToString();
|
||||
if (Request.Body.CanSeek) Request.Body.Position = 0;
|
||||
return fields;
|
||||
}
|
||||
|
||||
if (Request.ContentLength == 0) return fields;
|
||||
|
||||
try
|
||||
{
|
||||
using var reader = new StreamReader(Request.Body, leaveOpen: true);
|
||||
var body = await reader.ReadToEndAsync();
|
||||
if (Request.Body.CanSeek) Request.Body.Position = 0;
|
||||
if (string.IsNullOrWhiteSpace(body)) return fields;
|
||||
|
||||
var trimmed = body.TrimStart();
|
||||
if (trimmed.StartsWith("{", StringComparison.Ordinal))
|
||||
{
|
||||
using var doc = JsonDocument.Parse(body);
|
||||
if (doc.RootElement.ValueKind == JsonValueKind.Object)
|
||||
{
|
||||
foreach (var prop in doc.RootElement.EnumerateObject())
|
||||
{
|
||||
fields[prop.Name] = prop.Value.ValueKind == JsonValueKind.String
|
||||
? prop.Value.GetString() ?? ""
|
||||
: prop.Value.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
if (doc.RootElement.TryGetProperty("PlayerEvent", out var nested) &&
|
||||
nested.ValueKind == JsonValueKind.Object)
|
||||
{
|
||||
foreach (var prop in nested.EnumerateObject())
|
||||
{
|
||||
if (fields.ContainsKey(prop.Name)) continue;
|
||||
fields[prop.Name] = prop.Value.ValueKind == JsonValueKind.String
|
||||
? prop.Value.GetString() ?? ""
|
||||
: prop.Value.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
private async Task<JsonElement?> ReadJsonBodyAsync()
|
||||
{
|
||||
if (Request.ContentLength is not > 0) return null;
|
||||
|
||||
try
|
||||
{
|
||||
using var reader = new StreamReader(Request.Body);
|
||||
var body = await reader.ReadToEndAsync();
|
||||
if (string.IsNullOrWhiteSpace(body)) return null;
|
||||
|
||||
using var doc = JsonDocument.Parse(body);
|
||||
return doc.RootElement.Clone();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ConsumeRequestBodyAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Request.HasFormContentType)
|
||||
{
|
||||
await Request.ReadFormAsync();
|
||||
}
|
||||
else if (Request.ContentLength is > 0)
|
||||
{
|
||||
using var reader = new StreamReader(Request.Body);
|
||||
await reader.ReadToEndAsync();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private static long? GetBodyLong(JsonElement body, params string[] names)
|
||||
{
|
||||
foreach (var name in names)
|
||||
{
|
||||
if (!body.TryGetProperty(name, out var value)) continue;
|
||||
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 string? GetEventField(Dictionary<string, string> fields, params string[] names)
|
||||
{
|
||||
foreach (var name in names)
|
||||
{
|
||||
if (fields.TryGetValue(name, out var value) && !string.IsNullOrWhiteSpace(value))
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static long? GetEventLong(Dictionary<string, string> fields, params string[] names)
|
||||
{
|
||||
var raw = GetEventField(fields, names);
|
||||
return long.TryParse(raw, out var value) ? value : null;
|
||||
}
|
||||
|
||||
private static bool TryParseNullableEventId(string raw, out long? value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(raw) || string.Equals(raw, "null", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
value = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (long.TryParse(raw, out var parsed) && parsed > 0)
|
||||
{
|
||||
value = parsed;
|
||||
return true;
|
||||
}
|
||||
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static int? GetEventInt(Dictionary<string, string> fields, params string[] names)
|
||||
{
|
||||
var raw = GetEventField(fields, names);
|
||||
return int.TryParse(raw, out var value) ? value : null;
|
||||
}
|
||||
|
||||
private static bool ParseEventBoolean(string? raw)
|
||||
=> bool.TryParse(raw, out var value) && value;
|
||||
|
||||
private static DateTime ParseEventDate(string? raw, DateTime fallback)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(raw)) return fallback;
|
||||
return DateTime.TryParse(raw, out var parsed) ? EventClock.FromPostedTime(parsed) : fallback;
|
||||
}
|
||||
|
||||
private static Accessibility ParseEventAccessibility(string? raw)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(raw)) return Accessibility.Public;
|
||||
|
||||
if (int.TryParse(raw, out var intValue) && Enum.IsDefined(typeof(Accessibility), intValue))
|
||||
return (Accessibility)intValue;
|
||||
|
||||
return raw.Trim().ToLowerInvariant() switch
|
||||
{
|
||||
"public" => Accessibility.Public,
|
||||
"private" => Accessibility.Private,
|
||||
"unlisted" or "unlised" => Accessibility.Unlised,
|
||||
_ => Accessibility.Public
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user