diff --git a/extra/admin-api/Spacebar.ConfigModel/Class1.cs b/extra/admin-api/Spacebar.ConfigModel/Class1.cs
new file mode 100644
index 000000000..948ca91a2
--- /dev/null
+++ b/extra/admin-api/Spacebar.ConfigModel/Class1.cs
@@ -0,0 +1,64 @@
+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 Config ReadFromKv(Dictionary<string, object?> kv)
+ {
+ // to object
+
+
+ foreach (var (key, value) in kv)
+ {
+ switch (key.Split('_', 2)[0])
+ {
+ default:
+ Console.WriteLine($"Unrecognized config key prefix: {key}");
+ continue;
+ }
+ }
+
+ return this;
+ }
+}
+
+public class EndpointConfig
+{
+ [JsonPropertyName("endpointPrivate")] public string? EndpointPrivate { get; set; }
+ [JsonPropertyName("endpointPublic")] public string? EndpointPublic { get; set; }
+
+ public EndpointConfig ReadFromKv(Dictionary<string, object?> kv, string prefix)
+ {
+ foreach (var (key, value) in kv)
+ {
+ if (!key.StartsWith(prefix + "_")) continue;
+ var subKey = key[(prefix + "_").Length..];
+ switch (subKey)
+ {
+ case "ENDPOINT_PRIVATE":
+ EndpointPrivate = value?.ToString();
+ break;
+ case "ENDPOINT_PUBLIC":
+ EndpointPublic = value?.ToString();
+ break;
+ default:
+ Console.WriteLine($"Unrecognized config key: {key}");
+ break;
+ }
+ }
+
+ return this;
+ }
+}
+
+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/Spacebar.ConfigModel/Extensions/JsonExtensions.cs b/extra/admin-api/Spacebar.ConfigModel/Extensions/JsonExtensions.cs
new file mode 100644
index 000000000..20ae73318
--- /dev/null
+++ b/extra/admin-api/Spacebar.ConfigModel/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/Spacebar.ConfigModel/Spacebar.ConfigModel.csproj b/extra/admin-api/Spacebar.ConfigModel/Spacebar.ConfigModel.csproj
new file mode 100644
index 000000000..237d66167
--- /dev/null
+++ b/extra/admin-api/Spacebar.ConfigModel/Spacebar.ConfigModel.csproj
@@ -0,0 +1,9 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>net10.0</TargetFramework>
+ <ImplicitUsings>enable</ImplicitUsings>
+ <Nullable>enable</Nullable>
+ </PropertyGroup>
+
+</Project>
|