using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using System.Text.Json.Nodes; using RadicadeServer.API.Notifications; using RadicadeServer.DB; using RadicadeServer.Extensions; using RadicadeServer.RecNet.Enums.Messages; using RadicadeServer.RecNet.Notifications; using RadicadeServer.RecNet.Moderation; using RadicadeServer.RecNet.Relationships; namespace RadicadeServer.API.Relationships.V1; [Route("api/relationships/v1")] public class RelationshipsV1(RecDBContext db) : Controller { [HttpPost("bulkignoreplatformusers")] public IActionResult BulkIgnorePlatformUsers() { return Ok(Array.Empty()); } [HttpPost("~/api/relationships/sendfriendintroductions"), Authorize] public async Task SendFriendIntroductions() { var userId = User.GetUserId(); if (userId == null) return Unauthorized(); var (toPlayerIds, aboutPlayerId) = await ReadIntroductionFieldsAsync(); aboutPlayerId = aboutPlayerId > 0 ? aboutPlayerId : userId.Value; _ = toPlayerIds; return Ok(new OkResponse { Success = true }); } private async Task<(List ToPlayerIds, long AboutPlayerId)> ReadIntroductionFieldsAsync() { var toPlayerIds = new List(); long aboutPlayerId = 0; void AddIds(JsonNode? node) { if (node is JsonValue value && long.TryParse(value.ToString(), out var single)) { toPlayerIds.Add(single); return; } if (node is not JsonArray array) return; foreach (var entry in array) if (long.TryParse(entry?.ToString(), out var id)) toPlayerIds.Add(id); } void ReadObject(JsonObject body) { foreach (var pair in body) { if (pair.Key.Equals("ToPlayerIds", StringComparison.OrdinalIgnoreCase) || pair.Key.Equals("PlayerIds", StringComparison.OrdinalIgnoreCase) || pair.Key.Equals("PlayerId", StringComparison.OrdinalIgnoreCase) || pair.Key.Equals("Id", StringComparison.OrdinalIgnoreCase)) AddIds(pair.Value); else if (pair.Key.Equals("AboutPlayerId", StringComparison.OrdinalIgnoreCase) || pair.Key.Equals("AccountId", StringComparison.OrdinalIgnoreCase)) long.TryParse(pair.Value?.ToString(), out aboutPlayerId); } } var request = Request; if (request != null) { foreach (var key in new[] { "toPlayerIds", "ToPlayerIds", "playerIds", "PlayerIds", "playerId", "PlayerId", "id", "Id" }) if (request.Query.TryGetValue(key, out var query)) foreach (var raw in query) if (long.TryParse(raw, out var queryId)) toPlayerIds.Add(queryId); foreach (var key in new[] { "aboutPlayerId", "AboutPlayerId", "accountId", "AccountId" }) if (aboutPlayerId <= 0 && request.Query.TryGetValue(key, out var queryAbout) && long.TryParse(queryAbout.FirstOrDefault(), out var parsedAbout)) aboutPlayerId = parsedAbout; if (request.ContentLength is > 0 && request.Body != null) { try { request.EnableBuffering(); var node = await JsonNode.ParseAsync(request.Body); if (node is JsonObject bodyObject) ReadObject(bodyObject); else AddIds(node); request.Body.Position = 0; } catch { try { request.Body.Position = 0; } catch { } } } } return (toPlayerIds, aboutPlayerId); } [HttpGet("relationshipwith/{playerId:long}"), Authorize] public async Task RelationshipWith(long playerId) { var userId = User.GetUserId(); if (userId == null) return Unauthorized(); var row = await db.Relationships.AsNoTracking() .FirstOrDefaultAsync(x => x.AccountId == userId && x.PlayerId == playerId); if (row != null) return Ok(new Relationship(row)); return Ok(new Relationship(new RealRelationship { AccountId = (long)userId, PlayerId = playerId })); } [HttpGet("mutualfriends"), Authorize] [HttpGet("~/api/relationships/mutualfriends"), Authorize] public async Task> MutualFriends([FromQuery] long id) { var userId = User.GetUserId(); if (userId == null || id <= 0 || id == userId) return new List(); var mine = await db.Relationships.AsNoTracking() .Where(x => x.AccountId == userId && x.RelationshipType == RelationshipType.Friend) .Select(x => x.PlayerId) .ToListAsync(); var theirs = await db.Relationships.AsNoTracking() .Where(x => x.AccountId == id && x.RelationshipType == RelationshipType.Friend) .Select(x => x.PlayerId) .ToListAsync(); var mutual = mine.Intersect(theirs).OrderBy(x => x).Take(50).ToList(); if (mutual.Count == 0) return new List(); var accounts = await db.Profiles.AsNoTracking() .Where(p => mutual.Contains(p.accountId)) .ToDictionaryAsync(p => p.accountId); return mutual .Where(accounts.ContainsKey) .Select(accountId => (object)new { AccountId = accountId, Username = accounts[accountId].username, DisplayName = accounts[accountId].displayName, ProfileImage = accounts[accountId].profileImage ?? "" }) .ToList(); } [HttpGet("favorite"), Authorize] [HttpPost("favorite"), Authorize] [HttpGet("favorite/{id:long}"), Authorize] [HttpPost("favorite/{id:long}"), Authorize] public async Task Favorite() => await ToggleFlag(await ReadTargetPlayerIdAsync(), r => r.Favorited, (r, v) => r.Favorited = v, true); [HttpGet("unfavorite"), Authorize] [HttpPost("unfavorite"), Authorize] [HttpGet("unfavorite/{id:long}"), Authorize] [HttpPost("unfavorite/{id:long}"), Authorize] public async Task Unfavorite() => await ToggleFlag(await ReadTargetPlayerIdAsync(), r => r.Favorited, (r, v) => r.Favorited = v, false); private static readonly string[] TargetIdKeys = { "id", "Id", "playerId", "PlayerId", "targetPlayerId", "TargetPlayerId", "accountId", "AccountId", "otherPlayerId", "OtherPlayerId" }; private async Task ReadTargetPlayerIdAsync() { var routeData = RouteData; if (routeData != null && routeData.Values.TryGetValue("id", out var route) && long.TryParse(route?.ToString(), out var routeId) && routeId > 0) return routeId; var request = Request; if (request == null) return 0; foreach (var key in TargetIdKeys) if (request.Query.TryGetValue(key, out var query) && long.TryParse(query.FirstOrDefault(), out var queryId) && queryId > 0) return queryId; if (request.ContentLength is > 0 && request.Body != null) { try { request.EnableBuffering(); if (await JsonNode.ParseAsync(request.Body) is JsonObject body) foreach (var pair in body) if (TargetIdKeys.Contains(pair.Key, StringComparer.OrdinalIgnoreCase) && long.TryParse(pair.Value?.ToString(), out var bodyId) && bodyId > 0) { request.Body.Position = 0; return bodyId; } request.Body.Position = 0; } catch { try { request.Body.Position = 0; } catch { } } } if (request.HasFormContentType) { var form = await request.ReadFormAsync(); foreach (var key in TargetIdKeys) if (form.TryGetValue(key, out var formValue) && long.TryParse(formValue.FirstOrDefault(), out var formId) && formId > 0) return formId; } return 0; } [HttpPost("mute"), Authorize] [HttpPost("mute/{id:long}"), Authorize] [HttpGet("mute"), Authorize] [HttpGet("mute/{id:long}"), Authorize] public async Task Mute() => await ToggleFlagActionResult(await ReadTargetPlayerIdAsync(), r => r.Muted, (r, v) => r.Muted = v, true); [HttpPost("unmute"), Authorize] [HttpPost("unmute/{id:long}"), Authorize] [HttpGet("unmute"), Authorize] [HttpGet("unmute/{id:long}"), Authorize] public async Task Unmute() => await ToggleFlagActionResult(await ReadTargetPlayerIdAsync(), r => r.Muted, (r, v) => r.Muted = v, false); [HttpPost("ignore"), Authorize] [HttpPost("ignore/{id:long}"), Authorize] [HttpGet("ignore"), Authorize] [HttpGet("ignore/{id:long}"), Authorize] public async Task Ignore() => await ToggleFlagActionResult(await ReadTargetPlayerIdAsync(), r => r.Ignored, (r, v) => r.Ignored = v, true); [HttpPost("unignore"), Authorize] [HttpPost("unignore/{id:long}"), Authorize] [HttpGet("unignore"), Authorize] [HttpGet("unignore/{id:long}"), Authorize] public async Task Unignore() => await ToggleFlagActionResult(await ReadTargetPlayerIdAsync(), r => r.Ignored, (r, v) => r.Ignored = v, false); private async Task ToggleFlag(long id, Func getter, Action setter, bool enabling) { var userId = (long)User.GetUserId()!; var mine = await RelationshipsShared.GetOrCreate(db, userId, id); var theirs = await RelationshipsShared.GetOrCreate(db, id, userId); RelationshipsShared.SetReciprocalStatus(mine, theirs, getter, setter, enabling); await db.SaveChangesAsync(); await Notify.SendNotify(userId, new Notification { Id = Notifs.RelationshipChanged, Msg = new Relationship(mine) }); await Notify.SendNotify(id, new Notification { Id = Notifs.RelationshipChanged, Msg = new Relationship(theirs) }); return new Relationship(mine); } private async Task ToggleFlagActionResult(long id, Func getter, Action setter, bool enabling) { if (id <= 0) return BadRequest(new { error = "invalid player id" }); return Ok(await ToggleFlag(id, getter, setter, enabling)); } }