mirror of
https://github.com/JogmansonReal/Radicade.git
synced 2026-10-26 07:29:00 -07:00
281 lines
8.7 KiB
C#
281 lines
8.7 KiB
C#
using System.Collections.Concurrent;
|
|
using System.Text.Json;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using RadicadeServer.DB;
|
|
using RadicadeServer.Extensions;
|
|
using RadicadeServer.RecNet.Config;
|
|
using RadicadeServer.RecNet.Notifications;
|
|
|
|
namespace RadicadeServer.Clubs.Announcements
|
|
{
|
|
[Route("clubs/announcements/")]
|
|
[Route("api/clubs/v1/announcements/")]
|
|
[Route("api/clubs/v2/announcements/")]
|
|
public class ClubAnnouncementsAPI : Controller
|
|
{
|
|
private const string StorageKey = "ClubAnnouncements";
|
|
private static readonly ConcurrentDictionary<long, List<ClubAnnouncementRecord>> Store = new();
|
|
|
|
private sealed class ClubAnnouncementRecord
|
|
{
|
|
public long AnnouncementId { get; set; }
|
|
public long ClubId { get; set; }
|
|
public long AccountId { get; set; }
|
|
public string Title { get; set; } = string.Empty;
|
|
public string Body { get; set; } = string.Empty;
|
|
public string ImageName { get; set; } = string.Empty;
|
|
public string Meta { get; set; } = string.Empty;
|
|
public DateTime CreatedAt { get; set; }
|
|
}
|
|
|
|
private readonly RecDBContext? db;
|
|
|
|
public ClubAnnouncementsAPI(RecDBContext? db = null)
|
|
{
|
|
this.db = db;
|
|
}
|
|
|
|
[HttpGet("v2/subscription/mine/unread")]
|
|
[HttpGet("subscription/mine/unread")]
|
|
public List<Announcement> GetUnreadSubscribedAnnouncements()
|
|
{
|
|
return [];
|
|
}
|
|
[HttpGet("v2/mine/unread")]
|
|
[HttpGet("mine/unread")]
|
|
public List<Announcement> GetUnreadAnnouncements()
|
|
{
|
|
return [];
|
|
}
|
|
|
|
[HttpPost("club/{clubId}")]
|
|
[HttpPost("~/announcements/club/{clubId:long}")]
|
|
public async Task<IActionResult> PostClubAnnouncements(long clubId)
|
|
{
|
|
var userId = User.GetUserId();
|
|
if (userId == null) return Unauthorized();
|
|
|
|
var (managedClubId, error) = await ResolveManagedClubAsync(clubId, userId.Value);
|
|
if (error != null) return error;
|
|
clubId = managedClubId;
|
|
|
|
var fields = await ReadBodyFieldsAsync();
|
|
|
|
var announcement = new ClubAnnouncementRecord
|
|
{
|
|
AnnouncementId = NewAnnouncementId(),
|
|
ClubId = clubId,
|
|
AccountId = userId.Value,
|
|
Title = GetField(fields, "title", "Title") ?? string.Empty,
|
|
Body = GetField(fields, "body", "Body") ?? string.Empty,
|
|
ImageName = GetField(fields, "imageName", "ImageName", "image_name") ?? string.Empty,
|
|
Meta = GetField(fields, "meta", "Meta") ?? string.Empty,
|
|
CreatedAt = DateTime.UtcNow
|
|
};
|
|
|
|
Add(clubId, announcement);
|
|
await PersistAsync(clubId);
|
|
|
|
return Ok(new Dictionary<string, object?>
|
|
{
|
|
["error"] = string.Empty,
|
|
["success"] = true,
|
|
["value"] = announcement.AnnouncementId
|
|
});
|
|
}
|
|
|
|
[HttpGet("club/{clubId}")]
|
|
[HttpGet("~/announcements/club/{clubId:long}")]
|
|
public IActionResult GetClubAnnouncements(long clubId)
|
|
{
|
|
return Ok(BuildAnnouncementFeed(clubId));
|
|
}
|
|
|
|
[HttpDelete("club/{clubId:long}/{announcementId:long}")]
|
|
[HttpDelete("~/announcements/club/{clubId:long}/{announcementId:long}")]
|
|
public async Task<IActionResult> DeleteClubAnnouncement(long clubId, long announcementId)
|
|
{
|
|
var userId = User.GetUserId();
|
|
if (userId == null) return Unauthorized();
|
|
|
|
var (managedClubId, error) = await ResolveManagedClubAsync(clubId, userId.Value);
|
|
if (error != null) return error;
|
|
clubId = managedClubId;
|
|
|
|
if (!Store.TryGetValue(clubId, out var announcements)) return NotFound();
|
|
|
|
bool removed;
|
|
lock (announcements)
|
|
removed = announcements.RemoveAll(item => item.AnnouncementId == announcementId) > 0;
|
|
if (!removed) return NotFound();
|
|
|
|
await PersistAsync(clubId);
|
|
return Ok(new { error = string.Empty, success = true, value = announcementId });
|
|
}
|
|
|
|
private async Task<(long ClubId, IActionResult? Error)> ResolveManagedClubAsync(long clubId, long userId)
|
|
{
|
|
if (clubId == 0 || PlayerSubscriptions.IsPersonalClubId(clubId))
|
|
{
|
|
var personalClubId = PlayerSubscriptions.PersonalClubId(userId);
|
|
if (clubId != 0 && clubId != personalClubId)
|
|
return (clubId, StatusCode(StatusCodes.Status403Forbidden, new { error = "Insufficient permissions.", success = false, value = (object?)null }));
|
|
|
|
return (personalClubId, null);
|
|
}
|
|
|
|
if (db == null) return (clubId, StatusCode(StatusCodes.Status500InternalServerError));
|
|
|
|
var club = await ClubStore.GetAsync(db, clubId);
|
|
if (club == null) return (clubId, NotFound());
|
|
if (ClubStore.GetMembershipType(club, userId) < ClubMembershipTypes.Coowner)
|
|
return (clubId, StatusCode(StatusCodes.Status403Forbidden, new { error = "Insufficient permissions.", success = false, value = (object?)null }));
|
|
|
|
return (clubId, null);
|
|
}
|
|
|
|
private void Add(long clubId, ClubAnnouncementRecord announcement)
|
|
{
|
|
var list = Store.GetOrAdd(clubId, _ => new List<ClubAnnouncementRecord>());
|
|
lock (list) list.Insert(0, announcement);
|
|
}
|
|
|
|
private static long NewAnnouncementId()
|
|
{
|
|
return Math.Abs((long)HashCode.Combine(Guid.NewGuid(), Environment.TickCount64));
|
|
}
|
|
|
|
[HttpPost("club/{clubId}/announcement/{announcementId}/read")]
|
|
[HttpPost("~/announcements/club/{clubId:long}/{announcementId:long}/read")]
|
|
[HttpPost("~/clubs/announcements/club/{clubId:long}/{announcementId:long}/read")]
|
|
[HttpPost("~/api/clubs/v1/announcements/club/{clubId:long}/{announcementId:long}/read")]
|
|
[HttpPost("~/api/clubs/v2/announcements/club/{clubId:long}/{announcementId:long}/read")]
|
|
public IActionResult MarkAnnouncementRead(long clubId, long announcementId)
|
|
{
|
|
if (User.GetUserId() == null) return Unauthorized();
|
|
return Ok(new { error = string.Empty, success = true, value = announcementId });
|
|
}
|
|
|
|
private async Task PersistAsync(long clubId)
|
|
{
|
|
if (db == null) return;
|
|
try
|
|
{
|
|
var config = await db.ServerConfig.FirstOrDefaultAsync(x => x.Key == StorageKey);
|
|
if (config == null)
|
|
{
|
|
config = new ServerConfigVal(StorageKey, "{}");
|
|
await db.ServerConfig.AddAsync(config);
|
|
}
|
|
var snapshot = Store.ToDictionary(
|
|
kv => kv.Key.ToString(),
|
|
kv => SerializeAnnouncements(kv.Value));
|
|
config.Value = JsonSerializer.Serialize(snapshot);
|
|
await db.SaveChangesAsync();
|
|
}
|
|
catch (Exception)
|
|
{
|
|
}
|
|
}
|
|
|
|
private static List<object> SerializeAnnouncements(IReadOnlyList<ClubAnnouncementRecord> announcements)
|
|
{
|
|
return announcements.Select(announcement => (object)new Dictionary<string, object?>
|
|
{
|
|
["AnnouncementId"] = announcement.AnnouncementId,
|
|
["ClubId"] = announcement.ClubId,
|
|
["AccountId"] = announcement.AccountId,
|
|
["Title"] = announcement.Title,
|
|
["Body"] = announcement.Body,
|
|
["ImageName"] = announcement.ImageName,
|
|
["Meta"] = announcement.Meta,
|
|
["CreatedAt"] = announcement.CreatedAt
|
|
}).ToList();
|
|
}
|
|
|
|
private static object BuildAnnouncementFeed(long clubId)
|
|
{
|
|
var announcements = Store.TryGetValue(clubId, out var list)
|
|
? SerializeAnnouncements(list)
|
|
: new List<object>();
|
|
var lastAnnouncementId = announcements.Count > 0
|
|
? (long?)((Dictionary<string, object?>)announcements[0])["AnnouncementId"]
|
|
: null;
|
|
|
|
return new Dictionary<string, object?>
|
|
{
|
|
["error"] = string.Empty,
|
|
["success"] = true,
|
|
["value"] = new Dictionary<string, object?>
|
|
{
|
|
["Announcements"] = announcements,
|
|
["ClubId"] = clubId,
|
|
["LastAnnouncementId"] = lastAnnouncementId,
|
|
["LastReadAnnouncementId"] = 0L
|
|
}
|
|
};
|
|
}
|
|
|
|
private static string? GetField(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 async Task<Dictionary<string, string>> ReadBodyFieldsAsync()
|
|
{
|
|
var fields = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
|
if (Request.HasFormContentType)
|
|
{
|
|
var form = await Request.ReadFormAsync();
|
|
foreach (var kv in form)
|
|
fields[kv.Key] = kv.Value.ToString();
|
|
return fields;
|
|
}
|
|
|
|
if (Request.ContentLength is not > 0) return fields;
|
|
|
|
try
|
|
{
|
|
using var reader = new StreamReader(Request.Body);
|
|
var body = await reader.ReadToEndAsync();
|
|
if (string.IsNullOrWhiteSpace(body)) return fields;
|
|
|
|
var trimmed = body.TrimStart();
|
|
if (trimmed.StartsWith("{"))
|
|
{
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
foreach (var pair in trimmed.Split('&', StringSplitOptions.RemoveEmptyEntries))
|
|
{
|
|
var kv = pair.Split('=', 2);
|
|
if (kv.Length < 2) continue;
|
|
fields[Uri.UnescapeDataString(kv[0])] = Uri.UnescapeDataString(kv[1].Replace('+', ' '));
|
|
}
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
|
|
return fields;
|
|
}
|
|
}
|
|
}
|