diff --git a/extra/admin-api/Spacebar.AdminApi/Controllers/DiscoveryController.cs b/extra/admin-api/Spacebar.AdminApi/Controllers/DiscoveryController.cs
index 3f38b3a8..b914abef 100644
--- a/extra/admin-api/Spacebar.AdminApi/Controllers/DiscoveryController.cs
+++ b/extra/admin-api/Spacebar.AdminApi/Controllers/DiscoveryController.cs
@@ -10,9 +10,9 @@ using Spacebar.Models.Db.Models;
namespace Spacebar.AdminApi.Controllers;
[ApiController]
-[Route("/discovery")]
-public class DiscoveryController(
- ILogger<DiscoveryController> logger,
+[Route("/discovery/guilds")]
+public class GuildDiscoveryController(
+ ILogger<GuildDiscoveryController> logger,
SpacebarDbContext db,
IServiceProvider sp,
SpacebarAspNetAuthenticationService auth,
diff --git a/extra/admin-api/Spacebar.AdminApi/Program.cs b/extra/admin-api/Spacebar.AdminApi/Program.cs
index 072fba4b..3b3a9ef1 100644
--- a/extra/admin-api/Spacebar.AdminApi/Program.cs
+++ b/extra/admin-api/Spacebar.AdminApi/Program.cs
@@ -60,7 +60,7 @@ builder.Services.AddRequestTimeouts(x => {
var app = builder.Build();
app.Use((context, next) => {
context.Response.Headers["Access-Control-Allow-Origin"] = "*";
- context.Response.Headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE, OPTIONS";
+ context.Response.Headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE, OPTIONS, PATCH";
context.Response.Headers["Access-Control-Allow-Headers"] = "*, Authorization";
if (context.Request.Method == "OPTIONS") {
context.Response.StatusCode = 200;
diff --git a/extra/admin-api/Utilities/Spacebar.AdminApi.TestClient/Pages/GuildDiscovery.razor b/extra/admin-api/Utilities/Spacebar.AdminApi.TestClient/Pages/GuildDiscovery.razor
new file mode 100644
index 00000000..7e388fbc
--- /dev/null
+++ b/extra/admin-api/Utilities/Spacebar.AdminApi.TestClient/Pages/GuildDiscovery.razor
@@ -0,0 +1,84 @@
+@page "/GuildDiscovery"
+@using System.Net.Http.Headers
+@using System.Text.Json.Nodes
+@using ArcaneLibs
+@using ArcaneLibs.Blazor.Components
+@using Spacebar.AdminApi.TestClient.Services
+@using Spacebar.Models.AdminApi
+@inject Config Config
+<h3>GuildDiscovery</h3>
+<LinkButton OnClickAsync="@SaveChangesAsync">Save</LinkButton>
+
+<div class="guild-list">
+ @foreach (var guild in _guilds) {
+ if (guild == null) continue;
+
+ <div class="@("guild-card " + (guild.DiscoveryExcluded ? "excluded" : ""))">
+ <img class="guild-icon" src="@(guild.Icon is null ? $"{Config.CdnUrl}/embed/avatars/0.png" : $"{Config.CdnUrl}/icons/{guild.Id}/{guild.Icon}")" alt="@guild.Name"/>
+ @if (guild.Banner is not null) {
+ <img class="guild-banner" src="@($"{Config.CdnUrl}/banners/{guild.Id}/{guild.Banner}")"
+ alt="@guild.Name"/>
+ }
+ <h4>@guild.Name</h4>
+ <p>@guild.Description</p>
+
+ <div class="guild-details">
+ <div class="guild-stats">
+ <span>@guild.PresenceCount Online</span>
+ <span>@guild.MemberCount Members</span>
+ </div>
+ <label>Exclude</label>
+ <InputCheckbox @bind-Value="@guild.DiscoveryExcluded"/>
+ <label>Weight</label>
+ <InputNumber @bind-Value="@guild.DiscoveryWeight" style="width: 100px;"/>
+ </div>
+ </div>
+ }
+</div>
+
+@code {
+
+ private List<DiscoverableGuildModel?> _guilds = [];
+
+ protected override async Task OnInitializedAsync() {
+ var hc = new StreamingHttpClient();
+ hc.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Config.AccessToken);
+ var response = await hc.GetAsync(Config.AdminUrl + $"/_spacebar/admin/discovery/guilds?includeExcluded=true");
+ if (!response.IsSuccessStatusCode) throw new Exception(await response.Content.ReadAsStringAsync());
+ var content = response.Content.ReadFromJsonAsAsyncEnumerable<DiscoverableGuildModel>();
+ await foreach (var guild in content) {
+ _guilds.Add(guild);
+ // var d = Task.Delay(25);
+ await StateHasChangedAsync();
+ // await d;
+ }
+ }
+
+ private async Task StateHasChangedAsync() {
+ StateHasChanged();
+ await Task.Delay(1);
+ }
+
+ protected async Task SaveChangesAsync() {
+ var hc = new StreamingHttpClient();
+ hc.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Config.AccessToken);
+ var response = await hc.GetAsync(Config.AdminUrl + $"/_spacebar/admin/discovery/guilds?includeExcluded=true");
+ if (!response.IsSuccessStatusCode) throw new Exception(await response.Content.ReadAsStringAsync());
+ var content = response.Content.ReadFromJsonAsAsyncEnumerable<DiscoverableGuildModel>();
+ await foreach (var guild in content) {
+ var matchingGuild = _guilds.FirstOrDefault(g => g!.Id == guild!.Id);
+ if (matchingGuild == null) continue;
+ JsonObject update = new();
+
+ if (matchingGuild.DiscoveryExcluded != guild.DiscoveryExcluded)
+ update["discovery_excluded"] = matchingGuild.DiscoveryExcluded;
+
+ if (matchingGuild.DiscoveryWeight != guild.DiscoveryWeight)
+ update["discovery_weight"] = matchingGuild.DiscoveryWeight;
+
+ if (update.Count == 0) continue;
+ await hc.PatchAsJsonAsync(Config.AdminUrl + $"/_spacebar/admin/discovery/guilds/{guild.Id}", update);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/extra/admin-api/Utilities/Spacebar.AdminApi.TestClient/Pages/GuildDiscovery.razor.css b/extra/admin-api/Utilities/Spacebar.AdminApi.TestClient/Pages/GuildDiscovery.razor.css
new file mode 100644
index 00000000..b1873d10
--- /dev/null
+++ b/extra/admin-api/Utilities/Spacebar.AdminApi.TestClient/Pages/GuildDiscovery.razor.css
@@ -0,0 +1,59 @@
+@keyframes fadeIn {
+ from { opacity: 0; transform: translateY(20px); }
+ to { transform: translateY(0); }
+}
+
+.guild-list {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
+ gap: 3rem 1rem;
+ padding: 2rem 1rem 1rem;
+}
+
+.guild-card {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 1rem;
+ padding-top: 40px;
+ border: 1px solid #ccc;
+ border-radius: 8px;
+ width: 100%;
+ height: 360px;
+ background-color: var(--bs-card-bg);
+ margin-top: 32px;
+ animation: fadeIn 0.5s ease-out forwards;
+}
+
+.guild-icon {
+ position: absolute;
+ top: -32px;
+ width: 64px;
+ height: 64px;
+ border-radius: 50%;
+ margin-bottom: 0;
+ border: 4px solid var(--bs-body-bg);
+ z-index: 10;
+}
+
+.guild-banner {
+ width: 100%;
+ height: 150px;
+ object-fit: cover;
+ margin-bottom: 0.5rem;
+ position: absolute;
+ top: 0;
+ left: 0;
+ z-index: -1;
+ mask-image: linear-gradient(to bottom, black 0%, transparent 100%);
+}
+
+.guild-details {
+ margin-top: auto;
+ text-align: center;
+}
+
+.guild-card.excluded {
+ opacity: 0.2;
+}
\ No newline at end of file
|