diff --git a/extra/admin-api/Models/Spacebar.Models.Config/Config.cs b/extra/admin-api/Models/Spacebar.Models.Config/Config.cs
new file mode 100644
index 00000000..26e9d0fc
--- /dev/null
+++ b/extra/admin-api/Models/Spacebar.Models.Config/Config.cs
@@ -0,0 +1,33 @@
+using System.Text.Json.Serialization;
+
+namespace Spacebar.ConfigModel;
+
+public class Config {
+ [JsonPropertyName("admin")]
+ public EndpointConfig Admin { get; set; } = null!;
+
+ [JsonPropertyName("api")]
+ public EndpointConfig Api { get; set; } = null!;
+
+ [JsonPropertyName("gateway")]
+ public EndpointConfig Gateway { get; set; } = null!;
+
+ [JsonPropertyName("cdn")]
+ public EndpointConfig Cdn { get; set; } = null!;
+}
+
+public class EndpointConfig {
+ [JsonPropertyName("endpointPrivate")]
+ public string? EndpointPrivate { get; set; }
+
+ [JsonPropertyName("endpointPublic")]
+ public string? EndpointPublic { get; set; }
+}
+
+public class ApiConfig : EndpointConfig {
+ [JsonPropertyName("activeVersions")]
+ public List<string> ActiveVersions { get; set; } = null!;
+
+ [JsonPropertyName("defaultVersion")]
+ public string DefaultVersion { get; set; } = null!;
+}
\ No newline at end of file
diff --git a/extra/admin-api/Models/Spacebar.Models.Config/DefaultsConfiguration.cs b/extra/admin-api/Models/Spacebar.Models.Config/DefaultsConfiguration.cs
new file mode 100644
index 00000000..3cac4cbe
--- /dev/null
+++ b/extra/admin-api/Models/Spacebar.Models.Config/DefaultsConfiguration.cs
@@ -0,0 +1,37 @@
+using System.Text.Json.Serialization;
+
+namespace Spacebar.ConfigModel;
+
+public class DefaultsConfiguration
+{
+ [JsonPropertyName("guild")] public GuildDefaults Guild = new();
+ [JsonPropertyName("user")] public ChannelDefaults Channel = new();
+}
+
+public class GuildDefaults
+{
+ [JsonPropertyName("maxPresences")] public int MaxPresences { get; set; } = 250000;
+
+ [JsonPropertyName("maxVideoChannelUsers")]
+ public int MaxVideoChannelUsers { get; set; } = 200;
+
+ [JsonPropertyName("afkTimeout")] public int AfkTimeout { get; set; } = 300;
+
+ [JsonPropertyName("defaultMessageNotifications")]
+ public int DefaultMessageNotifications { get; set; } = 1;
+
+ [JsonPropertyName("explicitContentFilter")]
+ public int ExplicitContentFilter { get; set; } = 0;
+}
+
+public class ChannelDefaults
+{
+ [JsonPropertyName("premium")]
+ public bool Premium { get; set; } = true;
+
+ [JsonPropertyName("premiumType")]
+ public int PremiumType { get; set; } = 2;
+
+ [JsonPropertyName("verified")]
+ public bool Verified { get; set; } = true;
+}
\ No newline at end of file
diff --git a/extra/admin-api/Models/Spacebar.Models.Config/Extensions/JsonExtensions.cs b/extra/admin-api/Models/Spacebar.Models.Config/Extensions/JsonExtensions.cs
new file mode 100644
index 00000000..20ae7331
--- /dev/null
+++ b/extra/admin-api/Models/Spacebar.Models.Config/Extensions/JsonExtensions.cs
@@ -0,0 +1,119 @@
+using System.Text.Json;
+using System.Text.Json.Nodes;
+
+namespace Spacebar.ConfigModel.Extensions;
+
+public static class JsonExtensions
+{
+ extension(Dictionary<string, string?> kv)
+ {
+ public JsonObject ToNestedJsonObject(string path = "$")
+ {
+ JsonObject root = new();
+ // group by prefix
+ var groups = kv.GroupBy(kvItem => kvItem.Key.Split('_', 2)[0]);
+ foreach (var group in groups)
+ {
+ var prefix = group.Key;
+
+ if (group.Count() == 1 && !group.First().Key.Contains('_'))
+ {
+ root[prefix] = group.First().Value == null ? null : JsonNode.Parse(group.First().Value!);
+ Console.WriteLine("[CONFIG] Single Key: {0}.{1}, Value: {2}", path, prefix, root[prefix]?.ToJsonString());
+ continue;
+ }
+
+ var nestedValues = group.Where(x => x.Key.Contains('_')).ToDictionary(kvItem => kvItem.Key[(prefix.Length + 1)..], kvItem => kvItem.Value);
+
+ if (nestedValues.All(x => int.TryParse(x.Key.Split('_')[0], out _)))
+ {
+ Console.WriteLine("[CONFIG] Array Key Detected: {0}.{1}", path, prefix);
+ var arr = new JsonArray();
+ if (nestedValues.All(x => x.Key.Contains('_')))
+ {
+ var objs = nestedValues.GroupBy(x => x.Key.Split('_', 2)[0]);
+ foreach (var objGroup in objs.OrderBy(x => int.Parse(x.Key)))
+ {
+ var i = objGroup.Key;
+ var objValues = objGroup.ToDictionary(kvItem => kvItem.Key[(i.Length + 1)..], kvItem => kvItem.Value);
+ var obj = objValues.ToNestedJsonObject($"{path}.{prefix}[{i}]");
+ arr.Add(obj);
+ Console.WriteLine($" - ${path}.{prefix}[{i}]: {obj.ToJsonString()}");
+ }
+ }
+ else
+ foreach (var (i, arrayItem) in nestedValues.OrderBy(x => int.Parse(x.Key)))
+ {
+ arr.Add(arrayItem == null ? null : JsonNode.Parse(arrayItem));
+ Console.WriteLine($" - {path}.{prefix}[{i}]: {arrayItem}");
+ }
+
+ root[prefix] = arr;
+ }
+ else
+ {
+ root[prefix] = nestedValues.ToNestedJsonObject($"{path}.{prefix}");
+ }
+ }
+
+ return root;
+ }
+ }
+
+ extension(JsonObject jo)
+ {
+ public Dictionary<string, string?> ToFlatKv(string path = "$")
+ {
+ var kv = new Dictionary<string, string?>();
+ var jso = new JsonSerializerOptions()
+ {
+ Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
+ };
+
+ foreach (var (key, value) in jo)
+ {
+ var currentPath = path == "$" ? key : $"{path}_{key}";
+
+ switch (value)
+ {
+ case JsonObject nestedObj:
+ var nestedKv = nestedObj.ToFlatKv(currentPath);
+ foreach (var (nestedKey, nestedValue) in nestedKv)
+ {
+ kv[nestedKey] = nestedValue;
+ }
+
+ break;
+ case JsonArray arr:
+ for (int i = 0; i < arr.Count; i++)
+ {
+ var item = arr[i];
+ var itemPath = $"{currentPath}_{i}";
+ switch (item)
+ {
+ case JsonObject arrObj:
+ var arrObjKv = arrObj.ToFlatKv(itemPath);
+ foreach (var (arrObjKey, arrObjValue) in arrObjKv)
+ {
+ kv[arrObjKey] = arrObjValue;
+ }
+
+ break;
+ default:
+ kv[itemPath] = item?.ToJsonString(jso);
+ break;
+ }
+ }
+
+ break;
+ default:
+ Console.WriteLine(value?.GetType());
+ kv[currentPath] = value?.ToJsonString(jso);
+ break;
+ }
+ }
+
+ return kv;
+ }
+ }
+}
\ No newline at end of file
diff --git a/extra/admin-api/Models/Spacebar.Models.Config/GeneralConfiguration.cs b/extra/admin-api/Models/Spacebar.Models.Config/GeneralConfiguration.cs
new file mode 100644
index 00000000..1e006759
--- /dev/null
+++ b/extra/admin-api/Models/Spacebar.Models.Config/GeneralConfiguration.cs
@@ -0,0 +1,35 @@
+using System.Text.Json.Serialization;
+
+namespace Spacebar.ConfigModel;
+
+public class GeneralConfiguration {
+ [JsonPropertyName("instanceName")]
+ public string InstanceName { get; set; } = "Spacebar Instance";
+
+ [JsonPropertyName("serverName")]
+ public string? ServerName { get; set; } = null;
+
+ [JsonPropertyName("instanceDescription")]
+ public string InstanceDescription { get; set; } = "This is a Spacebar instance made in the pre-release days";
+
+ [JsonPropertyName("frontPage")]
+ public string? FrontPage { get; set; } = null;
+
+ [JsonPropertyName("tosPage")]
+ public string? TosPage { get; set; } = null;
+
+ [JsonPropertyName("correspondenceEmail")]
+ public string? CorrespondenceEmail { get; set; } = null;
+
+ [JsonPropertyName("correspondenceUserID")]
+ public string? CorrespondenceUserID { get; set; } = null;
+
+ [JsonPropertyName("image")]
+ public string? Image { get; set; } = null;
+
+ [JsonPropertyName("instanceId")]
+ public string InstanceId { get; set; } = null!; // {get;set;}=Snowflake.generate();
+
+ [JsonPropertyName("autoCreateBotUsers")]
+ public bool AutoCreateBotUsers { get; set; } = false;
+}
\ No newline at end of file
diff --git a/extra/admin-api/Models/Spacebar.Models.Config/LimitsConfiguration.cs b/extra/admin-api/Models/Spacebar.Models.Config/LimitsConfiguration.cs
new file mode 100644
index 00000000..56648bd3
--- /dev/null
+++ b/extra/admin-api/Models/Spacebar.Models.Config/LimitsConfiguration.cs
@@ -0,0 +1,189 @@
+using System.Text.Json.Serialization;
+
+namespace Spacebar.ConfigModel;
+
+public class LimitsConfiguration {
+ [JsonPropertyName("user")]
+ public UserLimits User { get; set; } = new UserLimits();
+
+ [JsonPropertyName("guild")]
+ public GuildLimits Guild { get; set; } = new GuildLimits();
+
+ [JsonPropertyName("message")]
+ public MessageLimits Message { get; set; } = new MessageLimits();
+
+ [JsonPropertyName("channel")]
+ public ChannelLimits Channel { get; set; } = new ChannelLimits();
+
+ [JsonPropertyName("rate")]
+ public RateLimits Rate { get; set; } = new RateLimits();
+
+ [JsonPropertyName("absoluteRate")]
+ public GlobalRateLimits AbsoluteRate { get; set; } = new GlobalRateLimits();
+}
+
+public class GlobalRateLimits {
+ [JsonPropertyName("register")]
+ public GlobalRateLimit Register { get; set; } = new() {
+ Enabled = true,
+ Count = 25,
+ Window = 60 * 60 * 1000
+ };
+
+ [JsonPropertyName("sendMessage")]
+ public GlobalRateLimit SendMessage { get; set; } = new() {
+ Enabled = true,
+ Count = 200,
+ Window = 60 * 1000
+ };
+
+ public class GlobalRateLimit : RateLimits.RateLimitOptions {
+ [JsonPropertyName("enabled")]
+ public bool Enabled { get; set; } = true;
+ }
+}
+
+public class RateLimits {
+ [JsonPropertyName("enabled")]
+ public bool Enabled { get; set; } = true;
+
+ [JsonPropertyName("ip")]
+ public RateLimitOptions Ip { get; set; } = new RateLimitOptions() {
+ Count = 500,
+ Window = 5
+ };
+
+ [JsonPropertyName("global")]
+ public RateLimitOptions Global { get; set; } = new RateLimitOptions() {
+ Count = 250,
+ Window = 5
+ };
+
+ [JsonPropertyName("error")]
+ public RateLimitOptions Error { get; set; } = new RateLimitOptions() {
+ Count = 50,
+ Window = 5
+ };
+
+ [JsonPropertyName("routes")]
+ public RouteRateLimits Routes { get; set; } = new RouteRateLimits();
+
+ public class RouteRateLimits {
+ [JsonPropertyName("guild")]
+ public RateLimitOptions Guild { get; set; } = new RateLimitOptions() {
+ Count = 5,
+ Window = 5
+ };
+
+ [JsonPropertyName("webhook")]
+ public RateLimitOptions Webhook { get; set; } = new RateLimitOptions() {
+ Count = 10,
+ Window = 5
+ };
+
+ [JsonPropertyName("channel")]
+ public RateLimitOptions Channel { get; set; } = new RateLimitOptions() {
+ Count = 10,
+ Window = 5
+ };
+
+ [JsonPropertyName("auth")]
+ public AuthRateLimits Auth { get; set; } = new AuthRateLimits();
+
+ public class AuthRateLimits {
+ [JsonPropertyName("login")]
+ public RateLimitOptions Login { get; set; } = new RateLimitOptions() {
+ Count = 5,
+ Window = 60
+ };
+
+ [JsonPropertyName("register")]
+ public RateLimitOptions Register { get; set; } = new RateLimitOptions() {
+ Count = 2,
+ Window = 60 * 60 * 12
+ };
+ }
+ }
+
+ public class RateLimitOptions {
+ [JsonPropertyName("count")]
+ public int Count { get; set; }
+
+ [JsonPropertyName("window")]
+ public int Window { get; set; }
+
+ [JsonIgnore]
+ public TimeSpan WindowTimeSpan => TimeSpan.FromSeconds(Window);
+ }
+}
+
+public class ChannelLimits {
+ [JsonPropertyName("maxPins")]
+ public int MaxPins { get; set; } = 500;
+
+ [JsonPropertyName("maxTopic")]
+ public int MaxTopic { get; set; } = 1024;
+
+ [JsonPropertyName("maxWebhooks")]
+ public int MaxWebhooks { get; set; } = 100;
+}
+
+public class MessageLimits {
+ [JsonPropertyName("maxCharacters")]
+ public int MaxCharacters { get; set; } = 1048576;
+
+ [JsonPropertyName("maxTTSCharacters")]
+ public int MaxTTSCharacters { get; set; } = 160;
+
+ [JsonPropertyName("maxReactions")]
+ public int MaxReactions { get; set; } = 2048;
+
+ [JsonPropertyName("maxAttachmentSize")]
+ public int MaxAttachmentSize { get; set; } = 1024 * 1024 * 1024;
+
+ [JsonPropertyName("maxBulkDelete")]
+ public int MaxBulkDelete { get; set; } = 1000;
+
+ [JsonPropertyName("maxEmbedDownloadSize")]
+ public int MaxEmbedDownloadSize { get; set; } = 1024 * 1024 * 1024;
+
+ [JsonPropertyName("maxPreloadCount")]
+ public int MaxPreloadCount { get; set; } = 100;
+}
+
+public class GuildLimits {
+ [JsonPropertyName("maxRoles")]
+ public int MaxRoles { get; set; } = 1000;
+
+ [JsonPropertyName("maxEmojis")]
+ public int MaxEmojis { get; set; } = 2000;
+
+ [JsonPropertyName("maxStickers")]
+ public int MaxStickers { get; set; } = 500;
+
+ [JsonPropertyName("maxMembers")]
+ public int MaxMembers { get; set; } = 25000000;
+
+ [JsonPropertyName("maxChannels")]
+ public int MaxChannels { get; set; } = 65535;
+
+ [JsonPropertyName("maxBulkBanUsers")]
+ public int MaxBulkBanUsers { get; set; } = 200;
+
+ [JsonPropertyName("maxChannelsInCategory")]
+ public int MaxChannelsInCategory { get; set; } = 65536;
+}
+
+public class UserLimits {
+ [JsonPropertyName("maxGuilds")]
+ public int MaxGuilds { get; set; } = 1048576;
+
+ [JsonPropertyName("maxUsername")]
+ public int MaxUsername { get; set; } = 32;
+
+ [JsonPropertyName("maxFriends")]
+ public int MaxFriends { get; set; } = 5000;
+
+ [JsonPropertyName("maxBio")]
+ public int MaxBio { get; set; } = 190;
+}
\ No newline at end of file
diff --git a/extra/admin-api/Models/Spacebar.Models.Config/SecurityConfiguration.cs b/extra/admin-api/Models/Spacebar.Models.Config/SecurityConfiguration.cs
new file mode 100644
index 00000000..e1592c8d
--- /dev/null
+++ b/extra/admin-api/Models/Spacebar.Models.Config/SecurityConfiguration.cs
@@ -0,0 +1,83 @@
+using System.Text.Json.Serialization;
+
+namespace Spacebar.ConfigModel;
+
+public class SecurityConfiguration {
+ [JsonPropertyName("captcha")]
+ public CaptchaConfiguration Captcha { get; set; } = new();
+
+ [JsonPropertyName("twoFactor")]
+ public TwoFactorConfiguration TwoFactor { get; set; } = new();
+
+ [JsonPropertyName("autoUpdate")]
+ public bool AutoUpdate { get; set; } = true;
+
+ [JsonPropertyName("requestSignature")] public string RequestSignature; // {get;set;}=crypto.randomBytes(32).toString("base64");
+
+ [JsonPropertyName("jwtSecret")]
+ public string? JwtSecret { get; set; } = null;
+
+ [JsonPropertyName("forwardedFor")]
+ public string? ForwardedFor { get; set; } = null;
+
+ [JsonPropertyName("trustedProxies")]
+ public string TrustedProxies { get; set; } = null;
+
+ [JsonPropertyName("abuseIpDbApiKey")]
+ public string? AbuseIpDbApiKey { get; set; } = null;
+
+ [JsonPropertyName("abuseipdbBlacklistRatelimit")]
+ public int AbuseipdbBlacklistRatelimit { get; set; } = 5;
+
+ [JsonPropertyName("abuseipdbConfidenceScoreTreshold")]
+ public int AbuseipdbConfidenceScoreTreshold { get; set; } = 50;
+
+ [JsonPropertyName("ipdataApiKey")]
+ public string? IpdataApiKey { get; set; } = null;
+
+ [JsonPropertyName("mfaBackupCodeCount")]
+ public int MfaBackupCodeCount { get; set; } = 10;
+
+ [JsonPropertyName("statsWorldReadable")]
+ public bool StatsWorldReadable { get; set; } = true;
+
+ [JsonPropertyName("defaultRegistrationTokenExpiration")]
+ public int DefaultRegistrationTokenExpiration { get; set; } = 1000 * 60 * 60 * 24 * 7;
+
+ [JsonPropertyName("cdnSignUrls")]
+ public bool CdnSignUrls { get; set; } = false;
+
+ [JsonPropertyName("cdnSignatureKey")]
+ public string CdnSignatureKey { get; set; } = null!; // crypto.randomBytes(32).toString("base64");
+
+ [JsonPropertyName("cdnSignatureDuration")]
+ public string CdnSignatureDuration { get; set; } = "24h";
+
+ [JsonPropertyName("cdnSignatureIncludeIp")]
+ public bool CdnSignatureIncludeIp { get; set; } = true;
+
+ [JsonPropertyName("cdnSignatureIncludeUserAgent")]
+ public bool CdnSignatureIncludeUserAgent { get; set; } = true;
+}
+
+public class CaptchaConfiguration {
+ [JsonPropertyName("enabled")]
+ public bool Enabled { get; set; } = false;
+
+ /// <summary>
+ /// One of: null, recaptcha, hcaptcha
+ /// </summary>
+ [JsonPropertyName("service")]
+ public string Service { get; set; } = "none";
+
+ [JsonPropertyName("sitekey")]
+ public string? SiteKey { get; set; } = null;
+
+ [JsonPropertyName("secret")]
+ public string? Secret { get; set; } = null;
+}
+
+public class TwoFactorConfiguration {
+ [JsonPropertyName("generateBackupCodes")]
+ public bool GenerateBackupCodes { get; set; } = true;
+}
\ No newline at end of file
diff --git a/extra/admin-api/Models/Spacebar.Models.Config/ServerConfiguration.cs b/extra/admin-api/Models/Spacebar.Models.Config/ServerConfiguration.cs
new file mode 100644
index 00000000..2ea107b8
--- /dev/null
+++ b/extra/admin-api/Models/Spacebar.Models.Config/ServerConfiguration.cs
@@ -0,0 +1,371 @@
+using System.Text.Json.Serialization;
+
+namespace Spacebar.ConfigModel;
+
+public class ServerConfiguration {
+ [JsonPropertyName("admin")]
+ public EndpointConfiguration Admin { get; set; } = new();
+
+ [JsonPropertyName("gateway")]
+ public EndpointConfiguration Gateway { get; set; } = new();
+
+ [JsonPropertyName("cdn")]
+ public CdnConfiguration Cdn { get; set; } = new();
+
+ [JsonPropertyName("api")]
+ public ApiConfiguration Api { get; set; } = new();
+
+ [JsonPropertyName("general")]
+ public GeneralConfiguration General { get; set; } = new();
+
+ [JsonPropertyName("limits")]
+ public LimitsConfiguration Limits { get; set; } = new();
+
+ [JsonPropertyName("security")]
+ public SecurityConfiguration Security { get; set; } = new();
+
+ [JsonPropertyName("login")]
+ public LoginConfiguration Login { get; set; } = new();
+
+ [JsonPropertyName("register")]
+ public RegisterConfiguration Register { get; set; } = new RegisterConfiguration();
+
+ [JsonPropertyName("regions")]
+ public RegionConfiguration Regions { get; set; } = new();
+
+ [JsonPropertyName("guild")]
+ public GuildConfiguration Guild { get; set; } = new();
+
+ [JsonPropertyName("gif")]
+ public GifConfiguration Gif { get; set; } = new GifConfiguration();
+
+ [JsonPropertyName("rabbitmq")]
+ public RabbitMQConfiguration Rabbitmq { get; set; } = new RabbitMQConfiguration();
+
+ [JsonPropertyName("templates")]
+ public TemplateConfiguration Templates { get; set; } = new TemplateConfiguration();
+
+ [JsonPropertyName("defaults")]
+ public DefaultsConfiguration Defaults { get; set; } = new();
+
+ [JsonPropertyName("external")]
+ public ExternalTokensConfiguration External { get; set; } = new();
+
+ // TODO: lazy
+ // [JsonPropertyName("email")]
+ // public EmailConfiguration Email { get; set; } = new EmailConfiguration();
+
+ [JsonPropertyName("passwordReset")]
+ public PasswordResetConfiguration PasswordReset { get; set; } = new PasswordResetConfiguration();
+
+ [JsonPropertyName("user")]
+ public UserConfiguration User { get; set; } = new UserConfiguration();
+}
+
+public class RegisterConfiguration {
+ [JsonPropertyName("email")]
+ public RegistrationEmailConfiguration Email { get; set; } = new RegistrationEmailConfiguration();
+
+ [JsonPropertyName("dateOfBirth")]
+ public DateOfBirthConfiguration DateOfBirth { get; set; } = new DateOfBirthConfiguration();
+
+ [JsonPropertyName("password")]
+ public PasswordConfiguration Password { get; set; } = new PasswordConfiguration();
+
+ [JsonPropertyName("disabled")]
+ public bool Disabled { get; set; } = false;
+
+ [JsonPropertyName("requireCaptcha")]
+ public bool RequireCaptcha { get; set; } = true;
+
+ [JsonPropertyName("requireInvite")]
+ public bool RequireInvite { get; set; } = false;
+
+ [JsonPropertyName("guestsRequireInvite")]
+ public bool GuestsRequireInvite { get; set; } = true;
+
+ [JsonPropertyName("allowNewRegistration")]
+ public bool AllowNewRegistration { get; set; } = true;
+
+ [JsonPropertyName("allowMultipleAccounts")]
+ public bool AllowMultipleAccounts { get; set; } = true;
+
+ [JsonPropertyName("blockIpDataCoThreatTypes")]
+ public List<string> BlockIpDataCoThreatTypes { get; set; } = [
+ "tor", "icloud_relay", "proxy", "datacenter", "anonymous", "known_attacker", "known_abuser", "threat"
+ ]; // matching ipdata's threat.is_* fields as of 2025/11/30, minus bogon
+
+ [JsonPropertyName("blockAsnTypes")]
+ public List<string> BlockAsnTypes { get; set; } = [""];
+
+ [JsonPropertyName("blockAsns")]
+ public List<string> BlockAsns { get; set; } = [""];
+
+ [JsonPropertyName("blockAbuseIpDbAboveScore")]
+ public int BlockAbuseIpDbAboveScore { get; set; } = 75; // 0 to disable
+
+ [JsonPropertyName("incrementingDiscriminators")]
+ public bool IncrementingDiscriminators { get; set; } = false; // random otherwise
+
+ [JsonPropertyName("defaultRights")]
+ public string DefaultRights { get; set; } = "875069521787904"; // See `npm run generate:rights`
+
+ [JsonPropertyName("checkIp")]
+ public bool CheckIp { get; set; } = true;
+}
+
+public class PasswordConfiguration {
+ [JsonPropertyName("required")]
+ public bool Required { get; set; } = true;
+
+ [JsonPropertyName("minLength")]
+ public int MinLength { get; set; } = 8;
+
+ [JsonPropertyName("minNumbers")]
+ public int MinNumbers { get; set; } = 2;
+
+ [JsonPropertyName("minUpperCase")]
+ public int MinUpperCase { get; set; } = 2;
+
+ [JsonPropertyName("minSymbols")]
+ public int MinSymbols { get; set; } = 0;
+}
+
+public class DateOfBirthConfiguration {
+ [JsonPropertyName("required")]
+ public bool Required { get; set; } = true;
+
+ [JsonPropertyName("minimum")]
+ public int Minimum { get; set; } = 13;
+}
+
+public class RegistrationEmailConfiguration {
+ [JsonPropertyName("required")]
+ public bool Required { get; set; } = false;
+
+ [JsonPropertyName("allowlist")]
+ public bool Allowlist { get; set; } = false;
+
+ [JsonPropertyName("blocklist")]
+ public bool Blocklist { get; set; } = false;
+
+ [JsonPropertyName("domains")]
+ public List<string> Domains { get; set; } = [];
+}
+
+public class TemplateConfiguration {
+ [JsonPropertyName("enabled")]
+ public bool Enabled { get; set; } = true;
+
+ [JsonPropertyName("allowTemplateCreation")]
+ public bool AllowTemplateCreation { get; set; } = true;
+
+ [JsonPropertyName("allowDiscordTemplates")]
+ public bool AllowDiscordTemplates { get; set; } = true;
+
+ [JsonPropertyName("allowRaws")]
+ public bool AllowRaws { get; set; } = true;
+}
+
+public class PasswordResetConfiguration {
+ [JsonPropertyName("requireCaptcha")]
+ public bool RequireCaptcha { get; set; } = false;
+}
+
+public class UserConfiguration {
+ [JsonPropertyName("blockedContains")]
+ public List<string> BlockedContains { get; set; } = [];
+
+ [JsonPropertyName("blockedEquals")]
+ public List<string> BlockedEquals { get; set; } = [];
+}
+
+public class RabbitMQConfiguration {
+ [JsonPropertyName("host")]
+ public string? Host { get; set; } = null;
+}
+
+public class GifConfiguration {
+ [JsonPropertyName("enabled")]
+ public bool Enabled { get; set; } = true;
+
+ [JsonPropertyName("provider")]
+ public string Provider { get; set; } = "tenor";
+
+ [JsonPropertyName("apiKey")]
+ public string? ApiKey { get; set; } = "LIVDSRZULELA";
+}
+
+public class EndpointConfiguration {
+ [JsonPropertyName("endpointPrivate")]
+ public string? EndpointPrivate { get; set; }
+
+ [JsonPropertyName("endpointPublic")]
+ public string? EndpointPublic { get; set; }
+}
+
+public class ApiConfiguration : EndpointConfiguration {
+ [JsonPropertyName("activeVersions")]
+ public List<string> ActiveVersions { get; set; } = null!;
+
+ [JsonPropertyName("defaultVersion")]
+ public string DefaultVersion { get; set; } = null!;
+}
+
+public class CdnConfiguration : EndpointConfiguration {
+ [JsonPropertyName("resizeHeightMax")]
+ public int ResizeHeightMax { get; set; } = 1000;
+
+ [JsonPropertyName("resizeWidthMax")]
+ public int ResizeWidthMax { get; set; } = 1000;
+
+ [JsonPropertyName("imagorServerUrl")]
+ public string? ImagorServerUrl { get; set; } = null;
+
+ [JsonPropertyName("proxyCacheHeaderSeconds")]
+ public int ProxyCacheHeaderSeconds { get; set; } = 60 * 60 * 24;
+
+ [JsonPropertyName("maxAttachmentSize")]
+ public int MaxAttachmentSize { get; set; } = 25 * 1024 * 1024; // 25 MB
+
+ // limits: CdnLimitsConfiguration {get;set;}=new CdnLimitsConfiguration();
+}
+
+public class CdnLimitsConfiguration {
+ [JsonPropertyName("icon")]
+ public CdnImageLimitsConfiguration Icon { get; set; } = new();
+
+ [JsonPropertyName("roleIcon")]
+ public CdnImageLimitsConfiguration RoleIcon { get; set; } = new();
+
+ [JsonPropertyName("emoji")]
+ public CdnImageLimitsConfiguration Emoji { get; set; } = new();
+
+ [JsonPropertyName("sticker")]
+ public CdnImageLimitsConfiguration Sticker { get; set; } = new();
+
+ [JsonPropertyName("banner")]
+ public CdnImageLimitsConfiguration Banner { get; set; } = new();
+
+ [JsonPropertyName("splash")]
+ public CdnImageLimitsConfiguration Splash { get; set; } = new();
+
+ [JsonPropertyName("avatar")]
+ public CdnImageLimitsConfiguration Avatar { get; set; } = new();
+
+ [JsonPropertyName("discoverySplash")]
+ public CdnImageLimitsConfiguration DiscoverySplash { get; set; } = new();
+
+ [JsonPropertyName("appIcon")]
+ public CdnImageLimitsConfiguration AppIcon { get; set; } = new();
+
+ [JsonPropertyName("discoverSplash")]
+ public CdnImageLimitsConfiguration DiscoverSplash { get; set; } = new(); //what even is this?
+
+ [JsonPropertyName("teamIcon")]
+ public CdnImageLimitsConfiguration TeamIcon { get; set; } = new();
+
+ [JsonPropertyName("channelIcon")]
+ public CdnImageLimitsConfiguration ChannelIcon { get; set; } = new(); // is this even used?
+
+ [JsonPropertyName("guildAvatar")]
+ public CdnImageLimitsConfiguration GuildAvatar { get; set; } = new();
+}
+
+public class CdnImageLimitsConfiguration {
+ [JsonPropertyName("maxHeight")]
+ public int MaxHeight { get; set; } = 8192;
+
+ [JsonPropertyName("maxWidth")]
+ public int MaxWidth { get; set; } = 8192;
+
+ [JsonPropertyName("maxSize")]
+ public int MaxSize { get; set; } = 10 * 1024 * 1024; // 10 MB
+
+ // "always" | "never" | "premium"
+ [JsonPropertyName("allowAnimated")]
+ public string AllowAnimated { get; set; } = "always";
+}
+
+public class LoginConfiguration {
+ [JsonPropertyName("requireCaptcha")]
+ public bool RequireCaptcha { get; set; } = false;
+
+ [JsonPropertyName("requireVerification")]
+ public bool RequireVerification { get; set; } = false;
+}
+
+public class RegionConfiguration {
+ [JsonPropertyName("default")]
+ public string Default { get; set; } = "spacebar-central";
+
+ [JsonPropertyName("useDefaultAsOptimal")]
+ public bool UseDefaultAsOptimal { get; set; } = true;
+
+ [JsonPropertyName("available")]
+ public List<Region> Available { get; set; } = [];
+
+ public class Region {
+ [JsonPropertyName("id")]
+ public string Id { get; set; } = null!;
+
+ [JsonPropertyName("name")]
+ public string Name { get; set; } = null!;
+
+ [JsonPropertyName("endpoint")]
+ public string Endpoint { get; set; } = null!;
+
+ [JsonPropertyName("vip")]
+ public bool Vip { get; set; } = false;
+
+ [JsonPropertyName("custom")]
+ public bool Custom { get; set; } = false;
+
+ [JsonPropertyName("deprecated")]
+ public bool Deprecated { get; set; } = false;
+ }
+}
+
+public class ExternalTokensConfiguration {
+ [JsonPropertyName("twitter")]
+ public string? Twitter { get; set; } = null;
+}
+
+public class GuildConfiguration {
+ [JsonPropertyName("defaultFeatures")]
+ public List<string> DefaultFeatures { get; set; } = [];
+
+ [JsonPropertyName("autoJoin")]
+ public GuildAutoJoinConfiguration AutoJoin { get; set; } = new();
+
+ [JsonPropertyName("discovery")]
+ public GuildDiscoveryConfiguration Discovery { get; set; } = new();
+
+ public class GuildDiscoveryConfiguration {
+ [JsonPropertyName("showAllGuilds")]
+ public bool ShowAllGuilds { get; set; } = false;
+
+ [JsonPropertyName("useRecommendation")]
+ public bool UseRecommendation { get; set; } = false;
+
+ [JsonPropertyName("offset")]
+ public int Offset { get; set; } = 0;
+
+ [JsonPropertyName("limit")]
+ public int Limit { get; set; } = 24;
+ }
+
+ public class GuildAutoJoinConfiguration {
+ [JsonPropertyName("enabled")]
+ public bool Enabled { get; set; } = false;
+
+ [JsonPropertyName("guilds")]
+ public List<string> Guilds { get; set; } = [];
+
+ [JsonPropertyName("canLeave")]
+ public bool CanLeave { get; set; } = true;
+
+ [JsonPropertyName("bots")]
+ public bool Bots { get; set; } = false;
+ }
+}
\ No newline at end of file
diff --git a/extra/admin-api/Models/Spacebar.Models.Config/Spacebar.Models.Config.csproj b/extra/admin-api/Models/Spacebar.Models.Config/Spacebar.Models.Config.csproj
new file mode 100644
index 00000000..237d6616
--- /dev/null
+++ b/extra/admin-api/Models/Spacebar.Models.Config/Spacebar.Models.Config.csproj
@@ -0,0 +1,9 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>net10.0</TargetFramework>
+ <ImplicitUsings>enable</ImplicitUsings>
+ <Nullable>enable</Nullable>
+ </PropertyGroup>
+
+</Project>
|