mirror of
https://github.com/JogmansonReal/Radicade.git
synced 2026-10-25 23:18:56 -07:00
783 lines
26 KiB
C#
783 lines
26 KiB
C#
using Microsoft.AspNetCore.Http;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using RadicadeServer.DB;
|
|
using RadicadeServer.Extensions;
|
|
using RadicadeServer.RecNet.Config;
|
|
using RadicadeServer.RecNet.Auth;
|
|
using RadicadeServer.RecNet.CDN;
|
|
using RadicadeServer.RecNet.Enums;
|
|
using System.Text.Json;
|
|
using System.Text.Json.Nodes;
|
|
|
|
namespace RadicadeServer.Clubs;
|
|
|
|
public sealed class ClubRecord
|
|
{
|
|
public long clubId { get; set; }
|
|
public string name { get; set; } = "Radicade Club";
|
|
public string description { get; set; } = "";
|
|
public string primaryTag { get; set; } = "Social";
|
|
public string imageName { get; set; } = "default";
|
|
public string bannerImageName { get; set; } = "default";
|
|
public long ownerPlayerId { get; set; }
|
|
public DateTime createdAt { get; set; } = DateTime.UtcNow;
|
|
public long? clubhouseRoomId { get; set; }
|
|
public int visibility { get; set; } = 1;
|
|
public int joinability { get; set; } = 0;
|
|
public bool allowJuniors { get; set; } = true;
|
|
public int minLevel { get; set; }
|
|
public string[] customTags { get; set; } = Array.Empty<string>();
|
|
public string[] additionalImages { get; set; } = Array.Empty<string>();
|
|
public List<ClubMemberRecord> members { get; set; } = new();
|
|
}
|
|
|
|
public sealed class ClubMemberRecord
|
|
{
|
|
public long accountId { get; set; }
|
|
public int membershipType { get; set; }
|
|
public int pendingMembershipType { get; set; }
|
|
public long inviterAccountId { get; set; }
|
|
public DateTime createdAt { get; set; } = DateTime.UtcNow;
|
|
}
|
|
|
|
public static class ClubMembershipTypes
|
|
{
|
|
public const int Banned = -1;
|
|
public const int None = 0;
|
|
public const int PendingRequested = 1;
|
|
public const int PendingInvited = 2;
|
|
public const int PendingDenied = 3;
|
|
public const int Member = 10;
|
|
public const int Moderator = 20;
|
|
public const int Coowner = 30;
|
|
public const int Creator = 100;
|
|
}
|
|
|
|
public sealed record ClubMemberView(long AccountId, int MembershipType, long InviterAccountId, DateTime CreatedAt);
|
|
|
|
public static class ClubStore
|
|
{
|
|
private const string ConfigKey = "Clubs";
|
|
|
|
public const string DefaultImageName = "DefaultImgPurple";
|
|
private const int ActiveMemberThreshold = ClubMembershipTypes.Member;
|
|
|
|
private static readonly string[] CategoryQueryKeys =
|
|
{
|
|
"category", "Category", "categoryTag", "CategoryTag", "categoryName", "CategoryName",
|
|
"primaryTag", "PrimaryTag", "tag", "Tag", "tags", "Tags"
|
|
};
|
|
|
|
public static string? CategoryFilter(IQueryCollection query)
|
|
{
|
|
foreach (var key in CategoryQueryKeys)
|
|
{
|
|
var value = query[key].FirstOrDefault();
|
|
if (!string.IsNullOrWhiteSpace(value)) return value.Trim().TrimStart('#');
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public static bool MatchesCategory(ClubRecord? club, string? category)
|
|
{
|
|
if (club == null) return false;
|
|
|
|
var wanted = category?.Trim().TrimStart('#');
|
|
if (string.IsNullOrWhiteSpace(wanted)) return true;
|
|
|
|
return string.Equals(club.primaryTag?.Trim(), wanted, StringComparison.OrdinalIgnoreCase)
|
|
|| (club.customTags ?? Array.Empty<string>())
|
|
.Any(tag => string.Equals(tag?.Trim(), wanted, StringComparison.OrdinalIgnoreCase));
|
|
}
|
|
|
|
public static List<ClubRecord> FilterByCategory(List<ClubRecord> clubs, string? category)
|
|
=> string.IsNullOrWhiteSpace(category)
|
|
? clubs
|
|
: clubs.Where(club => MatchesCategory(club, category)).ToList();
|
|
|
|
public static async Task<List<ClubRecord>> LoadAsync(RecDBContext db)
|
|
{
|
|
var config = await db.ServerConfig.AsNoTracking().FirstOrDefaultAsync(x => x.Key == ConfigKey);
|
|
if (config == null || string.IsNullOrWhiteSpace(config.Value)) return new List<ClubRecord>();
|
|
|
|
try
|
|
{
|
|
var clubs = config.Value.FromJson<List<ClubRecord>>() ?? new List<ClubRecord>();
|
|
foreach (var club in clubs)
|
|
{
|
|
club.members ??= new List<ClubMemberRecord>();
|
|
club.customTags ??= Array.Empty<string>();
|
|
club.additionalImages ??= Array.Empty<string>();
|
|
}
|
|
return clubs;
|
|
}
|
|
catch
|
|
{
|
|
return new List<ClubRecord>();
|
|
}
|
|
}
|
|
|
|
private static readonly string[] HomeClubConfigPrefixes = ["ClubHome:", "ClubHome.", "HomeClub:"];
|
|
|
|
private static string HomeClubConfigKey(long accountId) => $"ClubHome:{accountId}";
|
|
|
|
private static async Task<long?> GetHomeClubIdAsync(RecDBContext db, long accountId)
|
|
{
|
|
var keys = HomeClubConfigPrefixes.Select(prefix => prefix + accountId.ToString(System.Globalization.CultureInfo.InvariantCulture)).ToArray();
|
|
var value = await db.ServerConfig.AsNoTracking()
|
|
.Where(config => keys.Contains(config.Key))
|
|
.Select(config => config.Value)
|
|
.FirstOrDefaultAsync();
|
|
return long.TryParse(value, out var clubId) && clubId > 0 ? clubId : null;
|
|
}
|
|
|
|
public static async Task<ClubRecord?> GetHomeClubAsync(RecDBContext db, long accountId)
|
|
{
|
|
var clubId = await GetHomeClubIdAsync(db, accountId);
|
|
if (!clubId.HasValue) return null;
|
|
var club = await GetAsync(db, clubId.Value);
|
|
return club?.clubhouseRoomId is > 0 ? club : null;
|
|
}
|
|
|
|
public static async Task<ClubRecord?> SetHomeClubAsync(RecDBContext db, long accountId, long? clubId)
|
|
{
|
|
var configKey = HomeClubConfigKey(accountId);
|
|
var config = await db.ServerConfig.FirstOrDefaultAsync(row => row.Key == configKey);
|
|
if (clubId is > 0)
|
|
{
|
|
if (config == null)
|
|
{
|
|
config = new ServerConfigVal(configKey, clubId.Value.ToString(System.Globalization.CultureInfo.InvariantCulture));
|
|
await db.ServerConfig.AddAsync(config);
|
|
}
|
|
else
|
|
{
|
|
config.Value = clubId.Value.ToString(System.Globalization.CultureInfo.InvariantCulture);
|
|
}
|
|
}
|
|
else if (config != null)
|
|
{
|
|
db.ServerConfig.Remove(config);
|
|
}
|
|
|
|
await db.SaveChangesAsync();
|
|
return clubId is > 0 ? await GetAsync(db, clubId.Value) : null;
|
|
}
|
|
|
|
public static async Task ClearHomeClubsAsync(RecDBContext db, long clubId)
|
|
{
|
|
var clubIdValue = clubId.ToString(System.Globalization.CultureInfo.InvariantCulture);
|
|
var matching = await db.ServerConfig.Where(config => config.Value == clubIdValue).ToListAsync();
|
|
matching.RemoveAll(config => !HomeClubConfigPrefixes.Any(prefix => config.Key.StartsWith(prefix, StringComparison.Ordinal)));
|
|
if (matching.Count == 0) return;
|
|
db.ServerConfig.RemoveRange(matching);
|
|
await db.SaveChangesAsync();
|
|
}
|
|
|
|
public static async Task<bool> DeleteAsync(RecDBContext db, long clubId)
|
|
{
|
|
var clubs = await LoadAsync(db);
|
|
if (clubs.RemoveAll(club => club.clubId == clubId) == 0) return false;
|
|
await SaveAsync(db, clubs);
|
|
await ClearHomeClubsAsync(db, clubId);
|
|
return true;
|
|
}
|
|
|
|
public static async Task<ClubRecord?> UpdateAsync(RecDBContext db, long clubId, JsonObject body)
|
|
{
|
|
var clubs = await LoadAsync(db);
|
|
var club = clubs.FirstOrDefault(item => item.clubId == clubId);
|
|
if (club == null) return null;
|
|
|
|
if (GetNonEmptyString(body, "name") is { } name) club.name = name.Trim();
|
|
if (GetNonEmptyString(body, "description") is { } description) club.description = description;
|
|
if (GetNonEmptyString(body, "category", "primaryTag") is { } category) club.primaryTag = category.Trim();
|
|
if (GetNonEmptyString(body, "mainImageName") is { } imageName) club.imageName = imageName.Trim();
|
|
if (GetNonEmptyString(body, "bannerImageName") is { } bannerName) club.bannerImageName = bannerName.Trim();
|
|
if (GetInt(body, "visibility") is { } visibility) club.visibility = visibility;
|
|
if (GetInt(body, "joinability") is { } joinability) club.joinability = joinability;
|
|
if (GetInt(body, "minLevel") is { } minLevel && minLevel >= 0) club.minLevel = minLevel;
|
|
if (GetBool(body, "allowJuniors") is { } allowJuniors) club.allowJuniors = allowJuniors;
|
|
if (GetStringArray(body, "customTags") is { } customTags) club.customTags = NormalizeTags(customTags);
|
|
|
|
await SaveAsync(db, clubs);
|
|
return club;
|
|
}
|
|
|
|
public static async Task<ClubRecord?> SetClubhouseAsync(RecDBContext db, long clubId, long? roomId)
|
|
{
|
|
var clubs = await LoadAsync(db);
|
|
var club = clubs.FirstOrDefault(item => item.clubId == clubId);
|
|
if (club == null) return null;
|
|
club.clubhouseRoomId = roomId;
|
|
await SaveAsync(db, clubs);
|
|
return club;
|
|
}
|
|
|
|
public static async Task<ClubRecord?> SetAdditionalImageAsync(RecDBContext db, long clubId, int index, string imageName)
|
|
{
|
|
var clubs = await LoadAsync(db);
|
|
var club = clubs.FirstOrDefault(item => item.clubId == clubId);
|
|
if (club == null) return null;
|
|
|
|
var images = (club.additionalImages ?? Array.Empty<string>()).ToList();
|
|
if (string.IsNullOrWhiteSpace(imageName))
|
|
{
|
|
if (index < images.Count) images.RemoveAt(index);
|
|
}
|
|
else if (index < images.Count)
|
|
{
|
|
images[index] = imageName.Trim();
|
|
}
|
|
else if (images.Count < 3)
|
|
{
|
|
images.Add(imageName.Trim());
|
|
}
|
|
club.additionalImages = images.ToArray();
|
|
await SaveAsync(db, clubs);
|
|
return club;
|
|
}
|
|
|
|
public static async Task<ClubRecord?> InviteMemberAsync(RecDBContext db, long clubId, long accountId, int membershipType, long inviterAccountId)
|
|
{
|
|
var clubs = await LoadAsync(db);
|
|
var club = clubs.FirstOrDefault(item => item.clubId == clubId);
|
|
if (club == null || club.ownerPlayerId == accountId) return null;
|
|
|
|
club.members ??= new List<ClubMemberRecord>();
|
|
var membership = club.members.FirstOrDefault(item => item.accountId == accountId);
|
|
if (membership == null)
|
|
{
|
|
membership = new ClubMemberRecord { accountId = accountId, createdAt = DateTime.UtcNow };
|
|
club.members.Add(membership);
|
|
}
|
|
|
|
membership.membershipType = ClubMembershipTypes.PendingInvited;
|
|
membership.pendingMembershipType = membershipType;
|
|
membership.inviterAccountId = inviterAccountId;
|
|
await SaveAsync(db, clubs);
|
|
return club;
|
|
}
|
|
|
|
public static async Task<ClubRecord?> AcceptInvitationAsync(RecDBContext db, long clubId, long accountId)
|
|
{
|
|
var clubs = await LoadAsync(db);
|
|
var club = clubs.FirstOrDefault(item => item.clubId == clubId);
|
|
var membership = club?.members?.FirstOrDefault(item => item.accountId == accountId);
|
|
if (club == null || membership?.membershipType != ClubMembershipTypes.PendingInvited) return null;
|
|
|
|
var grantedType = membership.pendingMembershipType is ClubMembershipTypes.Member or ClubMembershipTypes.Moderator or ClubMembershipTypes.Coowner
|
|
? membership.pendingMembershipType
|
|
: ClubMembershipTypes.Member;
|
|
membership.membershipType = grantedType;
|
|
membership.pendingMembershipType = 0;
|
|
membership.createdAt = DateTime.UtcNow;
|
|
await SaveAsync(db, clubs);
|
|
return club;
|
|
}
|
|
|
|
public static async Task<ClubRecord?> RequestToJoinAsync(RecDBContext db, long clubId, long accountId)
|
|
{
|
|
var clubs = await LoadAsync(db);
|
|
var club = clubs.FirstOrDefault(item => item.clubId == clubId);
|
|
if (club == null) return null;
|
|
var current = GetMembershipType(club, accountId);
|
|
if (current == ClubMembershipTypes.Banned || current >= ClubMembershipTypes.Member || current == ClubMembershipTypes.PendingRequested)
|
|
return club;
|
|
if (club.joinability == 1) return club;
|
|
return await SetMemberTypeAsync(db, clubId, accountId,
|
|
club.joinability == 0 ? ClubMembershipTypes.Member : ClubMembershipTypes.PendingRequested,
|
|
accountId);
|
|
}
|
|
|
|
private static string? GetString(JsonObject body, params string[] keys)
|
|
{
|
|
foreach (var pair in body)
|
|
{
|
|
if (!keys.Any(key => pair.Key.Equals(key, StringComparison.OrdinalIgnoreCase)) || pair.Value == null) continue;
|
|
if (pair.Value is JsonValue jsonValue && jsonValue.TryGetValue<string>(out var value)) return value;
|
|
return pair.Value.ToString();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private static string? GetNonEmptyString(JsonObject body, params string[] keys)
|
|
{
|
|
var value = GetString(body, keys);
|
|
return string.IsNullOrWhiteSpace(value) ? null : value;
|
|
}
|
|
|
|
private static int? GetInt(JsonObject body, params string[] keys)
|
|
{
|
|
var raw = GetString(body, keys)?.Trim();
|
|
if (int.TryParse(raw, out var value)) return value;
|
|
return raw?.ToLowerInvariant() switch
|
|
{
|
|
"private" => 0,
|
|
"public" => 1,
|
|
"open" => 0,
|
|
"inviteonly" or "invite_only" => 1,
|
|
"asktojoin" or "requesttojoin" or "ask_to_join" => 2,
|
|
_ => null
|
|
};
|
|
}
|
|
|
|
private static bool? GetBool(JsonObject body, params string[] keys)
|
|
{
|
|
var raw = GetString(body, keys)?.Trim();
|
|
if (bool.TryParse(raw, out var value)) return value;
|
|
return raw?.ToLowerInvariant() switch
|
|
{
|
|
"1" or "yes" => true,
|
|
"0" or "no" => false,
|
|
_ => null
|
|
};
|
|
}
|
|
|
|
private static string[]? GetStringArray(JsonObject body, params string[] keys)
|
|
{
|
|
foreach (var pair in body)
|
|
{
|
|
if (!keys.Any(key => pair.Key.Equals(key, StringComparison.OrdinalIgnoreCase))) continue;
|
|
if (pair.Value is JsonArray array)
|
|
return NormalizeTags(array.Select(item => item?.ToString() ?? string.Empty));
|
|
return NormalizeTags(new[] { pair.Value?.ToString() ?? string.Empty });
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private static string[] NormalizeTags(IEnumerable<string> tags)
|
|
{
|
|
var output = new List<string>();
|
|
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
|
foreach (var raw in tags)
|
|
{
|
|
var tag = raw.Trim();
|
|
if (tag.Length > 0 && seen.Add(tag)) output.Add(tag);
|
|
}
|
|
return output.ToArray();
|
|
}
|
|
|
|
public static async Task SaveAsync(RecDBContext db, List<ClubRecord> clubs)
|
|
{
|
|
var config = await db.ServerConfig.FirstOrDefaultAsync(x => x.Key == ConfigKey);
|
|
if (config == null)
|
|
{
|
|
config = new ServerConfigVal(ConfigKey, "[]");
|
|
await db.ServerConfig.AddAsync(config);
|
|
}
|
|
|
|
config.Value = clubs.ToJson();
|
|
await db.SaveChangesAsync();
|
|
}
|
|
|
|
public static async Task<ClubRecord?> GetAsync(RecDBContext db, long clubId)
|
|
{
|
|
var clubs = await LoadAsync(db);
|
|
return clubs.FirstOrDefault(x => x.clubId == clubId);
|
|
}
|
|
|
|
public static async Task<ClubRecord> CreateAsync(
|
|
RecDBContext db,
|
|
long ownerPlayerId,
|
|
string? name,
|
|
string? description,
|
|
string? primaryTag = null,
|
|
int visibility = 1,
|
|
int joinability = 0,
|
|
bool allowJuniors = true,
|
|
int minLevel = 0,
|
|
string? mainImageName = null)
|
|
{
|
|
var clubs = await LoadAsync(db);
|
|
var clubName = string.IsNullOrWhiteSpace(name) ? "Radicade Club" : name.Trim();
|
|
var tag = string.IsNullOrWhiteSpace(primaryTag) ? "Social" : primaryTag.Trim();
|
|
var now = DateTime.UtcNow;
|
|
var clubId = Math.Abs((long)HashCode.Combine(ownerPlayerId, clubName, now.Ticks));
|
|
|
|
while (clubId == 0 || clubs.Any(x => x.clubId == clubId))
|
|
{
|
|
clubId = Math.Abs((long)HashCode.Combine(ownerPlayerId, clubName, Guid.NewGuid()));
|
|
}
|
|
|
|
var club = new ClubRecord
|
|
{
|
|
clubId = clubId,
|
|
name = clubName,
|
|
description = description?.Trim() ?? "",
|
|
primaryTag = tag,
|
|
imageName = string.IsNullOrWhiteSpace(mainImageName) ? DefaultImageName : mainImageName.Trim(),
|
|
visibility = visibility,
|
|
joinability = joinability,
|
|
allowJuniors = allowJuniors,
|
|
minLevel = minLevel,
|
|
ownerPlayerId = ownerPlayerId,
|
|
createdAt = now
|
|
};
|
|
club.members.Add(new ClubMemberRecord
|
|
{
|
|
accountId = ownerPlayerId,
|
|
membershipType = ClubMembershipTypes.Creator,
|
|
inviterAccountId = ownerPlayerId,
|
|
createdAt = now
|
|
});
|
|
|
|
clubs.Add(club);
|
|
await SaveAsync(db, clubs);
|
|
return club;
|
|
}
|
|
|
|
public static int GetMembershipType(ClubRecord? club, long accountId)
|
|
{
|
|
if (club == null || club.clubId <= 0 || accountId <= 0) return ClubMembershipTypes.None;
|
|
if (club.ownerPlayerId == accountId) return ClubMembershipTypes.Creator;
|
|
return club.members?.FirstOrDefault(x => x.accountId == accountId)?.membershipType ?? ClubMembershipTypes.None;
|
|
}
|
|
|
|
public static int GetMemberCount(ClubRecord? club)
|
|
{
|
|
if (club == null || club.clubId <= 0) return 0;
|
|
return 1 + (club.members ?? new List<ClubMemberRecord>())
|
|
.Where(x => x.accountId != club.ownerPlayerId && x.membershipType >= ActiveMemberThreshold)
|
|
.Select(x => x.accountId)
|
|
.Distinct()
|
|
.Count();
|
|
}
|
|
|
|
public static IReadOnlyList<ClubMemberView> GetMembers(ClubRecord? club)
|
|
{
|
|
if (club == null || club.clubId <= 0) return Array.Empty<ClubMemberView>();
|
|
|
|
var members = new List<ClubMemberView>
|
|
{
|
|
new(club.ownerPlayerId, ClubMembershipTypes.Creator, club.ownerPlayerId, club.createdAt)
|
|
};
|
|
members.AddRange((club.members ?? new List<ClubMemberRecord>())
|
|
.Where(x => x.accountId != club.ownerPlayerId)
|
|
.GroupBy(x => x.accountId)
|
|
.Select(group => group.Last())
|
|
.Select(x => new ClubMemberView(
|
|
x.accountId,
|
|
x.membershipType,
|
|
x.inviterAccountId,
|
|
NormalizeMembershipDate(x.createdAt, club.createdAt))));
|
|
return members;
|
|
}
|
|
|
|
private static DateTime NormalizeMembershipDate(DateTime memberCreatedAt, DateTime clubCreatedAt)
|
|
{
|
|
if (memberCreatedAt > DateTime.MinValue) return memberCreatedAt;
|
|
if (clubCreatedAt > DateTime.MinValue) return clubCreatedAt;
|
|
return DateTime.UnixEpoch;
|
|
}
|
|
|
|
public static async Task<ClubRecord?> SetMemberTypeAsync(
|
|
RecDBContext db,
|
|
long clubId,
|
|
long accountId,
|
|
int membershipType,
|
|
long inviterAccountId)
|
|
{
|
|
var clubs = await LoadAsync(db);
|
|
var club = clubs.FirstOrDefault(x => x.clubId == clubId);
|
|
if (club == null || club.ownerPlayerId == accountId) return null;
|
|
|
|
club.members ??= new List<ClubMemberRecord>();
|
|
var membership = club.members.FirstOrDefault(x => x.accountId == accountId);
|
|
if (membership == null)
|
|
{
|
|
club.members.Add(new ClubMemberRecord
|
|
{
|
|
accountId = accountId,
|
|
membershipType = membershipType,
|
|
inviterAccountId = inviterAccountId,
|
|
createdAt = DateTime.UtcNow
|
|
});
|
|
}
|
|
else
|
|
{
|
|
if (membership.membershipType < ClubMembershipTypes.Member && membershipType >= ClubMembershipTypes.Member)
|
|
membership.createdAt = DateTime.UtcNow;
|
|
membership.membershipType = membershipType;
|
|
membership.inviterAccountId = inviterAccountId;
|
|
}
|
|
|
|
await SaveAsync(db, clubs);
|
|
return club;
|
|
}
|
|
|
|
public static async Task<ClubRecord?> RemoveMemberAsync(RecDBContext db, long clubId, long accountId)
|
|
{
|
|
var clubs = await LoadAsync(db);
|
|
var club = clubs.FirstOrDefault(x => x.clubId == clubId);
|
|
if (club == null || club.ownerPlayerId == accountId) return null;
|
|
|
|
club.members ??= new List<ClubMemberRecord>();
|
|
club.members.RemoveAll(member => member.accountId == accountId && member.membershipType != ClubMembershipTypes.Banned);
|
|
await SaveAsync(db, clubs);
|
|
return club;
|
|
}
|
|
|
|
private static long ToUnix(DateTime value) => new DateTimeOffset(value).ToUnixTimeSeconds();
|
|
|
|
public static object BuildClub(ClubRecord? club, long viewerId = 0)
|
|
{
|
|
club ??= new ClubRecord { clubId = 0 };
|
|
var memberCount = GetMemberCount(club);
|
|
|
|
return new Dictionary<string, object?>
|
|
{
|
|
["ClubId"] = club.clubId,
|
|
["Name"] = club.name,
|
|
["Description"] = club.description,
|
|
["MainImageName"] = club.imageName,
|
|
["State"] = 0,
|
|
["CreatorAccountId"] = club.ownerPlayerId,
|
|
["Category"] = club.primaryTag,
|
|
["Visibility"] = club.visibility,
|
|
["Joinability"] = club.joinability,
|
|
["AllowJuniors"] = club.allowJuniors,
|
|
["MinLevel"] = club.minLevel,
|
|
["MemberCount"] = memberCount,
|
|
["IsRRO"] = false,
|
|
["ClubhouseRoomId"] = club.clubhouseRoomId,
|
|
["ClubType"] = 0
|
|
};
|
|
}
|
|
|
|
public static async Task<object> BuildClubDetailsAsync(RecDBContext db, ClubRecord? club, long viewerId = 0)
|
|
{
|
|
club ??= new ClubRecord { clubId = 0 };
|
|
var details = (Dictionary<string, object?>)BuildClubDetails(club, viewerId);
|
|
details["AdditionalImages"] = await GetAdditionalImagesAsync(db, club.additionalImages ?? Array.Empty<string>());
|
|
return details;
|
|
}
|
|
|
|
public static object BuildClubDetails(ClubRecord? club, long viewerId = 0)
|
|
{
|
|
club ??= new ClubRecord { clubId = 0 };
|
|
return new Dictionary<string, object?>
|
|
{
|
|
["Club"] = BuildClub(club, viewerId),
|
|
["ClubId"] = club.clubId,
|
|
["CustomTags"] = club.customTags ?? Array.Empty<string>(),
|
|
["AdditionalImages"] = Array.Empty<object>(),
|
|
["CoownerPermissions"] = BuildPermissions(club.clubId, 0),
|
|
["ModeratorPermissions"] = BuildPermissions(club.clubId, 1),
|
|
["MemberPermissions"] = BuildPermissions(club.clubId, 2),
|
|
["MyMembershipType"] = GetMembershipType(club, viewerId)
|
|
};
|
|
}
|
|
|
|
private static async Task<object[]> GetAdditionalImagesAsync(RecDBContext db, string[] imageNames)
|
|
{
|
|
if (imageNames.Length == 0) return Array.Empty<object>();
|
|
var records = await db.Images.AsNoTracking()
|
|
.Where(image => imageNames.Contains(image.Name))
|
|
.ToDictionaryAsync(image => image.Name);
|
|
var imageIds = records.Values.Select(image => image.Id).ToList();
|
|
var cheers = await db.ImageInteractions.AsNoTracking()
|
|
.Where(interaction => interaction.IsCheered && imageIds.Contains(interaction.SavedImageId))
|
|
.GroupBy(interaction => interaction.SavedImageId)
|
|
.Select(group => new { ImageId = group.Key, Count = group.Count() })
|
|
.ToDictionaryAsync(group => group.ImageId, group => group.Count);
|
|
|
|
return imageNames.Select(name =>
|
|
{
|
|
if (!records.TryGetValue(name, out var image))
|
|
return (object)new Dictionary<string, object?>
|
|
{
|
|
["Id"] = 0L, ["Type"] = 1,
|
|
["Accessibility"] = (int)SavedImageAccessibility.Public,
|
|
["AccessibilityLocked"] = false, ["ImageName"] = name,
|
|
["Description"] = null, ["PlayerId"] = 0L,
|
|
["TaggedPlayerIds"] = Array.Empty<long>(), ["RoomId"] = null,
|
|
["PlayerEventId"] = null, ["CreatedAt"] = DateTime.UnixEpoch,
|
|
["CheerCount"] = 0, ["CommentCount"] = 0
|
|
};
|
|
return (object)new Dictionary<string, object?>
|
|
{
|
|
["Id"] = image.Id, ["Type"] = (int)image.ImageType,
|
|
["Accessibility"] = (int)image.Accessibility, ["AccessibilityLocked"] = false,
|
|
["ImageName"] = image.Name, ["Description"] = null, ["PlayerId"] = image.OwnerId,
|
|
["TaggedPlayerIds"] = ParseImagePlayerIds(image.PlayerIds), ["RoomId"] = image.RoomId,
|
|
["PlayerEventId"] = image.EventId, ["CreatedAt"] = image.CreatedAt,
|
|
["CheerCount"] = cheers.GetValueOrDefault(image.Id), ["CommentCount"] = 0
|
|
};
|
|
}).ToArray();
|
|
}
|
|
|
|
private static long[] ParseImagePlayerIds(string? playerIds)
|
|
{
|
|
try { return JsonSerializer.Deserialize<long[]>(playerIds ?? "[]") ?? Array.Empty<long>(); }
|
|
catch { return Array.Empty<long>(); }
|
|
}
|
|
|
|
public static object BuildPermissions(long clubId, int type)
|
|
{
|
|
var isCoowner = type == 0;
|
|
var isModerator = type == 1;
|
|
return new Dictionary<string, object?>
|
|
{
|
|
["Type"] = type switch
|
|
{
|
|
0 => ClubMembershipTypes.Coowner,
|
|
1 => ClubMembershipTypes.Moderator,
|
|
_ => ClubMembershipTypes.Member
|
|
},
|
|
["ClubId"] = clubId,
|
|
["ApproveMember"] = isCoowner || isModerator,
|
|
["BanUnban"] = isCoowner || isModerator,
|
|
["CreateEvent"] = isCoowner,
|
|
["EditDetails"] = isCoowner,
|
|
["EditPermissionSettings"] = isCoowner,
|
|
["PostAnnouncement"] = isCoowner
|
|
};
|
|
}
|
|
|
|
public static object BuildMemberListItem(long playerId, int role, bool isOwner, long clubId = 0, long created = 0)
|
|
{
|
|
var type = isOwner ? ClubMembershipTypes.Creator : role;
|
|
var createdAt = created > 0 ? DateTimeOffset.FromUnixTimeSeconds(created).UtcDateTime.ToString("O") : null;
|
|
return new Dictionary<string, object?>
|
|
{
|
|
["ClubMemberId"] = playerId,
|
|
["ClubId"] = clubId,
|
|
["AccountId"] = playerId,
|
|
["MembershipType"] = type,
|
|
["CreatedAt"] = createdAt
|
|
};
|
|
}
|
|
|
|
public static object BuildMemberListItem(ClubMemberView member, long clubId) =>
|
|
BuildMemberListItem(
|
|
member.AccountId,
|
|
member.MembershipType,
|
|
member.MembershipType == ClubMembershipTypes.Creator,
|
|
clubId,
|
|
ToUnix(member.CreatedAt));
|
|
|
|
public static object BuildMember(long playerId, int role, bool isOwner, long clubId = 0, long created = 0) => new Dictionary<string, object?>
|
|
{
|
|
["AccountId"] = playerId,
|
|
["ClubId"] = clubId,
|
|
["CreatedAt"] = created > 0 ? DateTimeOffset.FromUnixTimeSeconds(created).UtcDateTime.ToString("O") : null,
|
|
["MembershipType"] = isOwner ? ClubMembershipTypes.Creator : role,
|
|
["membershipRequestId"] = 0L,
|
|
["inviterAccountId"] = 0L,
|
|
["subscriptionId"] = clubId,
|
|
["playerId"] = playerId,
|
|
["username"] = string.Empty,
|
|
["displayName"] = string.Empty,
|
|
["level"] = 0,
|
|
["role"] = isOwner ? ClubMembershipTypes.Creator : role,
|
|
["membershipStatus"] = role >= ActiveMemberThreshold || isOwner ? 1 : 0,
|
|
["isOwner"] = isOwner,
|
|
["created"] = created,
|
|
["createdAt"] = created
|
|
};
|
|
|
|
public static object BuildMember(ClubMemberView membership, ClubRecord? club, Profile? profile)
|
|
{
|
|
var role = membership.MembershipType;
|
|
var created = ToUnix(membership.CreatedAt);
|
|
var isOwner = role == ClubMembershipTypes.Creator;
|
|
return new Dictionary<string, object?>
|
|
{
|
|
["AccountId"] = membership.AccountId,
|
|
["ClubId"] = club?.clubId ?? 0L,
|
|
["CreatedAt"] = membership.CreatedAt.ToString("O"),
|
|
["MembershipType"] = role,
|
|
["membershipRequestId"] = 0L,
|
|
["inviterAccountId"] = membership.InviterAccountId,
|
|
["subscriptionId"] = club?.clubId ?? 0L,
|
|
["playerId"] = membership.AccountId,
|
|
["username"] = profile?.username ?? string.Empty,
|
|
["displayName"] = profile?.displayName ?? string.Empty,
|
|
["level"] = 0,
|
|
["role"] = role,
|
|
["membershipStatus"] = role >= ActiveMemberThreshold || isOwner ? 1 : 0,
|
|
["isOwner"] = isOwner,
|
|
["created"] = created,
|
|
["createdAt"] = created
|
|
};
|
|
}
|
|
|
|
public static object BuildMembership(ClubRecord? club, long viewerId = 0)
|
|
{
|
|
club ??= new ClubRecord { clubId = 0 };
|
|
var role = GetMembershipType(club, viewerId);
|
|
var isMember = role >= ActiveMemberThreshold;
|
|
|
|
return new Dictionary<string, object?>
|
|
{
|
|
["accountId"] = viewerId,
|
|
["clubId"] = club.clubId,
|
|
["creatorAccountId"] = club.ownerPlayerId,
|
|
["membershipType"] = role,
|
|
["LastAnnouncementId"] = 0L,
|
|
["LastReadAnnouncementId"] = 0L,
|
|
["announcements"] = Array.Empty<object>(),
|
|
["subscriptionId"] = club.clubId
|
|
};
|
|
}
|
|
|
|
public static object BuildSubscriptionEnvelope(ClubRecord? club, long viewerId = 0) => new Dictionary<string, object?>
|
|
{
|
|
["success"] = club != null,
|
|
["error"] = string.Empty,
|
|
["value"] = club == null ? null : BuildMembership(club, viewerId)
|
|
};
|
|
|
|
public static object BuildSearchResponse(object[] clubs, int skip, int take) => new Dictionary<string, object?>
|
|
{
|
|
["Clubs"] = clubs,
|
|
["TotalClubs"] = clubs.Length,
|
|
["ContinuationToken"] = null
|
|
};
|
|
|
|
public static object BuildHome(ClubRecord? homeClub, long viewerId, IReadOnlyList<ClubRecord> allClubs, IReadOnlyList<ClubRecord> mine)
|
|
{
|
|
return BuildClub(homeClub, viewerId);
|
|
}
|
|
|
|
public static object BuildEmptyHome(long viewerId)
|
|
{
|
|
return BuildClub(null, viewerId);
|
|
}
|
|
|
|
public static object BuildModerationBlockDetails() => new Dictionary<string, object?>
|
|
{
|
|
["Duration"] = 0L,
|
|
["GameSessionId"] = 0L,
|
|
["PlayerIdReporter"] = 0L,
|
|
["IsBan"] = false,
|
|
["IsHostKick"] = false,
|
|
["Message"] = string.Empty,
|
|
["ReportCategory"] = 0,
|
|
["VoteKickReason"] = string.Empty,
|
|
["bannedUntil"] = string.Empty,
|
|
["displayReason"] = string.Empty,
|
|
["banType"] = string.Empty,
|
|
["banAppealCode"] = string.Empty,
|
|
["banAppealText"] = string.Empty
|
|
};
|
|
|
|
public static object BuildEligibility() => new Dictionary<string, object?>
|
|
{
|
|
["canCreateClub"] = true,
|
|
["canCreate"] = true,
|
|
["creationAllowed"] = true,
|
|
["isAllowed"] = true,
|
|
["isCreationRestricted"] = false,
|
|
["creationRestricted"] = false,
|
|
["moderationRestricted"] = false,
|
|
["moderationIncidentCount"] = 0,
|
|
["myCreatedClubsCount"] = 0,
|
|
["maxCreatedClubs"] = 100,
|
|
["cooldownSeconds"] = 0,
|
|
["restrictionReason"] = string.Empty
|
|
};
|
|
}
|