diff --git a/extra/admin-api/Utilities/Spacebar.AdminAPI.TestClient/Pages/Guilds.razor b/extra/admin-api/Utilities/Spacebar.AdminAPI.TestClient/Pages/Guilds.razor
deleted file mode 100644
index 454808b4e..000000000
--- a/extra/admin-api/Utilities/Spacebar.AdminAPI.TestClient/Pages/Guilds.razor
+++ /dev/null
@@ -1,80 +0,0 @@
-@page "/Guilds"
-@using System.Net.Http.Headers
-@using System.Reflection
-@using Spacebar.AdminApi.Models
-@using Spacebar.AdminAPI.TestClient.Services
-@using ArcaneLibs.Blazor.Components
-@using ArcaneLibs.Extensions
-@inject Config Config
-@inject ILocalStorageService LocalStorage
-
-<PageTitle>Guilds</PageTitle>
-
-<details>
- <summary>Displayed columns</summary>
- @foreach (var column in DisplayedColumns) {
- var value = column.Value;
- <span>
- <InputCheckbox @bind-Value:get="@(value)" @bind-Value:set="@(b => {
- DisplayedColumns[column.Key] = b;
- StateHasChanged();
- })"/>
- @column.Key.Name
- </span>
- <br/>
- }
-</details>
-
-<p>Got @GuildList.Count guilds.</p>
-<table class="table table-bordered">
- @{
- var columns = DisplayedColumns.Where(kvp => kvp.Value).Select(kvp => kvp.Key).ToList();
- }
- <thead>
- <tr>
- @foreach (var column in columns) {
- <th>@column.Name</th>
- }
- <th>Actions</th>
- </tr>
- </thead>
- <tbody>
- @foreach (var user in GuildList.Where(x => !x.Unavailable).OrderByDescending(x=>x.MessageCount)) {
- <tr>
- @foreach (var column in columns) {
- <td>@column.GetValue(user)</td>
- }
- <td>
- <LinkButton href="@($"/Users/Delete/{user.Id}")" Color="#ff0000">Delete</LinkButton>
- </td>
- </tr>
- }
- </tbody>
-</table>
-
-@code {
-
- private Dictionary<PropertyInfo, bool> DisplayedColumns { get; set; } = typeof(GuildModel).GetProperties()
- .ToDictionary(p => p, p => p.Name == "Name" || p.Name == "Id" || p.Name == "MessageCount");
-
- private List<GuildModel> GuildList { get; set; } = new();
-
- protected override async Task OnInitializedAsync() {
- var hc = new StreamingHttpClient();
- hc.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Config.AccessToken);
-
- // var request = new HttpRequestMessage(HttpMethod.Get, Config.AdminUrl + "/_spacebar/admin/users/");
-
- var response = hc.GetAsyncEnumerableFromJsonAsync<GuildModel>(Config.AdminUrl + "/_spacebar/admin/guilds/");
- // if (!response.IsSuccessStatusCode) throw new Exception(await response.Content.ReadAsStringAsync());
- // var content = response.Content.ReadFromJsonAsAsyncEnumerable<GuildModel>();
- await foreach (var user in response) {
- // Console.WriteLine(user.ToJson(indent: false, ignoreNull: true));
- GuildList.Add(user!);
- if(GuildList.Count % 1000 == 0)
- StateHasChanged();
- }
- StateHasChanged();
- }
-
-}
\ No newline at end of file
diff --git a/extra/admin-api/Utilities/Spacebar.AdminAPI.TestClient/Pages/Home.razor b/extra/admin-api/Utilities/Spacebar.AdminAPI.TestClient/Pages/Home.razor
deleted file mode 100644
index 812a61521..000000000
--- a/extra/admin-api/Utilities/Spacebar.AdminAPI.TestClient/Pages/Home.razor
+++ /dev/null
@@ -1,107 +0,0 @@
-@page "/"
-@using System.Net.Http.Headers
-@using Spacebar.AdminAPI.TestClient.Services
-@inject Config Config
-@inject ILocalStorageService LocalStorage
-
-<PageTitle>Home</PageTitle>
-
-<span style="@($"color: {(IsApiUrlValid ? "green" : "red")};")">Spacebar API URL: </span>
-<InputText @bind-Value:get="Config.ApiUrl" @bind-Value:set="@(async (v) => {
- Config.ApiUrl = v!;
- await ValidateAndSaveConfig();
- })"/>
-<br/>
-
-<!-- <span style="@($"color: {(IsGatewayUrlValid ? "green" : "red")};")">Spacebar Gateway URL: </span> -->
-<!-- <InputText @bind-Value="GatewayUrl" /> -->
-<!-- <br /> -->
-
-<span style="@($"color: {(IsCdnUrlValid ? "green" : "red")};")">Spacebar CDN URL: </span>
-<InputText @bind-Value:get="Config.CdnUrl" @bind-Value:set="@(async (v) => {
- Config.CdnUrl = v!;
- await ValidateAndSaveConfig();
- })"/>
-<br/>
-
-<span style="@($"color: {(IsAdminApiUrlValid ? "green" : "red")};")">Spacebar Admin API URL: </span>
-<InputText @bind-Value:get="Config.AdminUrl" @bind-Value:set="@(async (v) => {
- Config.AdminUrl = v!;
- await ValidateAndSaveConfig();
- })"/>
-<br/>
-
-<span style="@($"color: {(IsAccessTokenValid ? "green" : "red")};")">Access Token: </span>
-<InputText @bind-Value:get="Config.AccessToken" @bind-Value:set="@(async (v) => {
- Config.AccessToken = v!;
- await ValidateAndSaveConfig();
- })"/>
-<a href="/login">New access token</a>
-<br/>
-
-@code {
-
- private bool IsApiUrlValid { get; set; }
-
- // private bool IsGatewayUrlValid { get; set; }
- private bool IsCdnUrlValid { get; set; }
- private bool IsAdminApiUrlValid { get; set; }
- private bool IsAccessTokenValid { get; set; }
-
- protected override async Task OnInitializedAsync() {
- await ValidateAndSaveConfig();
- }
-
- private async Task ValidateAndSaveConfig() {
- await LocalStorage.SetItemAsync("sb_admin_tc_config", Config);
-
- using var hc = new HttpClient();
- HttpResponseMessage response;
- try {
- response = await hc.GetAsync(Config.ApiUrl + "/api/v9/policies/instance/domains");
- IsApiUrlValid = response.IsSuccessStatusCode;
- }
- catch {
- IsApiUrlValid = false;
- }
-
- StateHasChanged();
-
- // response = await hc.GetAsync(Config.GatewayUrl + "/api/v9/policies/instance");
- // IsGatewayUrlValid = response.IsSuccessStatusCode;
- // StateHasChanged();
-
- try {
- response = await hc.GetAsync(Config.CdnUrl + "/ping");
- IsCdnUrlValid = response.IsSuccessStatusCode;
- }
- catch {
- IsCdnUrlValid = false;
- }
-
- StateHasChanged();
-
- try {
- response = await hc.GetAsync(Config.AdminUrl + "/_spacebar/admin/ping");
- IsAdminApiUrlValid = response.IsSuccessStatusCode;
- }
- catch {
- IsAdminApiUrlValid = false;
- }
-
- StateHasChanged();
-
- try {
- var request = new HttpRequestMessage(HttpMethod.Get, Config.AdminUrl + "/_spacebar/admin/whoami");
- request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", Config.AccessToken);
- response = await hc.SendAsync(request);
- IsAccessTokenValid = response.IsSuccessStatusCode;
- }
- catch {
- IsAccessTokenValid = false;
- }
-
- StateHasChanged();
- }
-
-}
\ No newline at end of file
diff --git a/extra/admin-api/Utilities/Spacebar.AdminAPI.TestClient/Pages/HttpTestClient.razor b/extra/admin-api/Utilities/Spacebar.AdminAPI.TestClient/Pages/HttpTestClient.razor
deleted file mode 100644
index 0be77a999..000000000
--- a/extra/admin-api/Utilities/Spacebar.AdminAPI.TestClient/Pages/HttpTestClient.razor
+++ /dev/null
@@ -1,198 +0,0 @@
-@page "/HttpTestClient"
-@using System.Collections.Immutable
-@using System.Text.Json
-@using ArcaneLibs.Blazor.Components
-@using ArcaneLibs.Extensions
-@using Spacebar.AdminAPI.TestClient.Classes.OpenAPI
-@using Spacebar.AdminAPI.TestClient.Pages.HttpTestClientParts
-@using Spacebar.AdminAPI.TestClient.Services
-@inject Config Config
-<h3>HttpTestClient</h3>
-
-@if (OpenApiSchema is not null) {
- <p>Got OpenAPI schema with @OpenApiSchema.Paths.Count paths.</p>
- <span>Server: </span>
-
- var currentIndex = OpenApiSchema.Servers.IndexOf(Server!);
- <InputSelect TValue="int" Value="@currentIndex" ValueExpression="@(() => currentIndex)" ValueChanged="@SetCurrentServer">
- @for (var index = 0; index < OpenApiSchema.Servers.Count; index++) {
- var server = OpenApiSchema.Servers[index];
- var serverOptionName = $"{server.Description} ({server.Url})";
- <option value="@index">@serverOptionName</option>
- }
- </InputSelect>
- <br/>
- <br/>
- <span>Path: </span>
-
- <InputSelect @bind-Value="_methodKey">
- <option>-- select a method --</option>
- @foreach (var method in OpenApiSchema.Paths.SelectMany(x => x.Value.GetAvailableMethods()).Distinct()) {
- <option value="@method">@method</option>
- }
- </InputSelect>
-
- @if (!string.IsNullOrWhiteSpace(_methodKey)) {
- <InputSelect @bind-Value="_pathKey">
- <option>-- select a path --</option>
- @foreach (var path in OpenApiSchema.Paths.Where(x => x.Value.HasMethod(_methodKey!)).OrderBy(x => x.Key)) {
- <option value="@path.Key">@path.Key</option>
- }
- </InputSelect>
- <br/>
- }
-
- if (Operation != null) {
- if (!string.IsNullOrWhiteSpace(Operation.Description)) {
- <p>@Operation.Description</p>
- }
- }
-
- <details>
- <summary>@AllKnownPathParameters.Count known path parameters</summary>
- @foreach (var (param, value) in AllKnownPathParameters) {
- var _key = param;
- // if (Operation?.Parameters?.Any(x => x.Name == param.Name && x.In == param.In) ?? false)
- // continue;
- <OpenAPIParameterDescription Parameter="@param"/>
- <br/>
- }
- </details>
- <details>
- <summary>@AllKnownQueryParameters.Count known query parameters</summary>
- @foreach (var (param, value) in AllKnownQueryParameters) {
- var _key = param;
- // if (Operation?.Parameters?.Any(x => x.Name == param.Name && x.In == param.In) ?? false)
- // continue;
- <OpenAPIParameterDescription Parameter="@param"/>
- <br/>
- }
- </details>
-
- @if (Operation != null) {
- if (Operation.Parameters?.Any() ?? false) {
- var pathParams = Operation.Parameters.Where(x => x.In == "path").ToList();
- if (pathParams.Any()) {
- <b>Path parameters</b>
- <br/>
- foreach (var key in pathParams) {
- <span>Path parameter </span>
- <OpenAPIParameterDescription Parameter="@key"/>
- <br/>
- }
- }
-
- var queryParams = Operation.Parameters.Except(pathParams).Where(x => x.In == "query").ToList();
- if (queryParams.Any()) {
- <b>Query parameters</b>
- <br/>
- foreach (var key in queryParams) {
- <span>Query parameter </span>
- <OpenAPIParameterDescription Parameter="@key"/>
- <br/>
- }
- }
-
- var otherParams = Operation.Parameters.Except(pathParams).Except(queryParams).ToList();
- if (otherParams.Any()) {
- <b>Other parameters</b>
- <br/>
- foreach (var key in otherParams) {
- <span>Other parameter </span>
- <OpenAPIParameterDescription Parameter="@key"/>
- <br/>
- }
- }
- }
-
- if(Operation.RequestBody is not null) {
- <b>Request body</b>
- <br/>
- <span title="@Operation.RequestBody.ToJson()">@Operation.RequestBody.Content.ApplicationJson?.Schema.ToJson()</span>
- }
- }
-
- <LinkButton OnClickAsync="@Execute">Execute</LinkButton>
- <pre>@ResultContent</pre>
-}
-
-@code {
- private string? _pathKey;
- private string? _methodKey;
-
- private OpenApiSchema? OpenApiSchema { get; set; }
- private OpenApiServer? Server { get; set; }
- private Dictionary<OpenApiPath.OpenApiOperation.OpenApiParameter, string> AllKnownPathParameters { get; set; } = [];
- private Dictionary<OpenApiPath.OpenApiOperation.OpenApiParameter, string> AllKnownQueryParameters { get; set; } = [];
-
- private OpenApiPath? Path => string.IsNullOrWhiteSpace(_pathKey) ? null : OpenApiSchema?.Paths.GetValueOrDefault(_pathKey);
- private OpenApiPath.OpenApiOperation? Operation => Path is null || string.IsNullOrWhiteSpace(_methodKey) ? null : Path.GetOperation(_methodKey);
-
- private string? ResultContent { get; set; }
- private readonly StreamingHttpClient _httpClient = new();
-
- protected override async Task OnInitializedAsync() {
- _httpClient.DefaultRequestHeaders.Authorization = new("Bearer", Config.AccessToken);
-
- OpenApiSchema = await _httpClient.GetFromJsonAsync<OpenApiSchema>($"{Config.ApiUrl}/_spacebar/api/openapi.json");
- OpenApiSchema!.Servers.Insert(0, Server = new() {
- Description = "Current server (config)",
- Url = Config.ApiUrl + "/api/v9"
- });
- SetCurrentServer(0);
-
- AllKnownPathParameters = OpenApiSchema.Paths.Values
- .SelectMany(x => x.GetAvailableMethods().Select(y => x.GetOperation(y)!.Parameters ?? []))
- .SelectMany(x => x)
- .Where(x => x.In == "path")
- .DistinctBy(x => x.ToJson())
- .OrderBy(x => x.Name)
- .ToDictionary(x => x, _ => "");
-
- AllKnownQueryParameters = OpenApiSchema.Paths.Values
- .SelectMany(x => x.GetAvailableMethods().Select(y => x.GetOperation(y)!.Parameters ?? []))
- .SelectMany(x => x)
- .Where(x => x.In == "query")
- .DistinctBy(x => x.ToJson())
- .OrderBy(x => x.Name)
- .ToDictionary(x => x, _ => "");
- }
-
- protected override bool ShouldRender() {
- if (string.IsNullOrWhiteSpace(_methodKey))
- _pathKey = null;
- return base.ShouldRender();
- }
-
- private void SetCurrentServer(int index) {
- Server = OpenApiSchema!.Servers[index];
- _httpClient.BaseAddress = new Uri(Server.Url);
- StateHasChanged();
- }
-
- private async Task Execute() {
- var url = _pathKey!.TrimStart('/');
- if (Operation?.Parameters?.Any(x => x.In == "path") ?? false) {
- foreach (var param in Operation.Parameters.Where(x => x.In == "path")) {
- if (!AllKnownPathParameters.TryGetValue(param, out var value) || string.IsNullOrWhiteSpace(value))
- throw new Exception($"Path parameter {param.Name} not set");
- url = url.Replace($"{{{param.Name}}}", value!);
- }
- }
-
- var request = new HttpRequestMessage(new HttpMethod(_methodKey!), url);
- try {
- var response = await _httpClient.SendAsync(request);
- ResultContent = response.Content.GetType().Name + "\n" + response.Content switch {
- { Headers: { ContentType: { MediaType: "application/json" } } } => (await response.Content.ReadFromJsonAsync<JsonElement>()).ToJson(true),
- _ => await response.Content.ReadAsStringAsync()
- };
- }
- catch (Exception ex) {
- ResultContent = ex.ToString();
- }
-
- StateHasChanged();
- }
-
-}
diff --git a/extra/admin-api/Utilities/Spacebar.AdminAPI.TestClient/Pages/HttpTestClientParts/OpenAPIParameterDescription.razor b/extra/admin-api/Utilities/Spacebar.AdminAPI.TestClient/Pages/HttpTestClientParts/OpenAPIParameterDescription.razor
deleted file mode 100644
index 892224537..000000000
--- a/extra/admin-api/Utilities/Spacebar.AdminAPI.TestClient/Pages/HttpTestClientParts/OpenAPIParameterDescription.razor
+++ /dev/null
@@ -1,21 +0,0 @@
-@using ArcaneLibs.Extensions
-@using Spacebar.AdminAPI.TestClient.Classes.OpenAPI
-<span title="@Parameter.ToJson()">@Summary</span>
-@if (Parameter.Name != Parameter.Description && !string.IsNullOrWhiteSpace(Parameter.Description)) {
- <i> - @Parameter.Description</i>
-}
-
-@code {
-
- private string Summary { get; set; } = "Unbound parameter";
-
- [Parameter]
- public required OpenApiPath.OpenApiOperation.OpenApiParameter Parameter {
- get;
- set {
- field = value;
- Summary = $"{Parameter.Name}{(Parameter.Required ? "*" : "")} ({Parameter.Schema.Type})";
- }
- }
-
-}
\ No newline at end of file
diff --git a/extra/admin-api/Utilities/Spacebar.AdminAPI.TestClient/Pages/Login.razor b/extra/admin-api/Utilities/Spacebar.AdminAPI.TestClient/Pages/Login.razor
deleted file mode 100644
index ca1205ed8..000000000
--- a/extra/admin-api/Utilities/Spacebar.AdminAPI.TestClient/Pages/Login.razor
+++ /dev/null
@@ -1,57 +0,0 @@
-@page "/Login"
-@using System.Text.Json.Nodes
-@using Spacebar.AdminAPI.TestClient.Services
-@inject ILocalStorageService LocalStorage
-@inject Config Config
-@inject NavigationManager Navigation
-<h3>Login</h3>
-
-<span>Email: </span>
-<InputText @bind-Value="Email"/>
-<br/>
-
-<span>Password: </span>
-<InputText type="password" @bind-Value="Password"/>
-<br/>
-
-<button @onclick="DoLogin">Login</button>
-<br/>
-
-<pre style="color: red; font-family: 'JetBrains Mono',monospace">@Error</pre>
-
-
-@code {
- private string Email { get; set; }
- private string Password { get; set; }
- private string Error { get; set; }
-
- private async Task DoLogin() {
- HttpResponseMessage response;
- using var hc = new HttpClient();
-
- try {
- response = await hc.PostAsJsonAsync(Config.ApiUrl + "/api/v9/auth/login", new {
- login = Email,
- password = Password,
- login_source = "Spacebar Admin API Test Client",
- undelete = false
- });
- }
- catch (Exception e) {
- Error = e.ToString();
- return;
- }
-
- if (!response.IsSuccessStatusCode) {
- Error = await response.Content.ReadAsStringAsync();
- return;
- }
-
- var content = await response.Content.ReadFromJsonAsync<JsonObject>();
- var accessToken = content!["token"].ToString();
- Config.AccessToken = accessToken;
- await LocalStorage.SetItemAsync("sb_admin_tc_config", Config);
- Navigation.NavigateTo("/", true, true);
- }
-
-}
\ No newline at end of file
diff --git a/extra/admin-api/Utilities/Spacebar.AdminAPI.TestClient/Pages/Media/Index.razor b/extra/admin-api/Utilities/Spacebar.AdminAPI.TestClient/Pages/Media/Index.razor
deleted file mode 100644
index 76621e74c..000000000
--- a/extra/admin-api/Utilities/Spacebar.AdminAPI.TestClient/Pages/Media/Index.razor
+++ /dev/null
@@ -1,7 +0,0 @@
-@page "/Media"
-<h3>Index of /Media</h3>
-<hr/>
-
-@code {
-
-}
\ No newline at end of file
diff --git a/extra/admin-api/Utilities/Spacebar.AdminAPI.TestClient/Pages/Media/Users.razor b/extra/admin-api/Utilities/Spacebar.AdminAPI.TestClient/Pages/Media/Users.razor
deleted file mode 100644
index 81008a258..000000000
--- a/extra/admin-api/Utilities/Spacebar.AdminAPI.TestClient/Pages/Media/Users.razor
+++ /dev/null
@@ -1,106 +0,0 @@
-@page "/Media/ByUser"
-@using System.Net.Http.Headers
-@using System.Reflection
-@using Spacebar.AdminApi.Models
-@using Spacebar.AdminAPI.TestClient.Services
-@using ArcaneLibs.Blazor.Components
-@inject Config Config
-@inject ILocalStorageService LocalStorage
-
-<PageTitle>Uploaded media by user</PageTitle>
-
-<details>
- <summary>Displayed columns</summary>
- @foreach (var column in DisplayedColumns) {
- var value = column.Value;
- <span>
- <InputCheckbox @bind-Value:get="@(value)" @bind-Value:set="@(b => {
- DisplayedColumns[column.Key] = b;
- StateHasChanged();
- })"/>
- @column.Key.Name
- </span>
- <br/>
- }
-</details>
-
-<InputSelect @bind-Value="@SelectedUserId">
- <option value="">All users</option>
- @if (UserList is { Count: > 0 }) {
- @foreach (var user in UserList.OrderByDescending(u => u.Id).Where(x => !x.Deleted)) {
- <option value="@user.Id">@user.Username</option>
- }
- }
-</InputSelect>
-
-
-<table class="table table-bordered">
- @{
- var columns = DisplayedColumns.Where(kvp => kvp.Value).Select(kvp => kvp.Key).ToList();
- }
- <thead>
- <tr>
- @foreach (var column in columns) {
- <th>@column.Name</th>
- }
- <th>Actions</th>
- </tr>
- </thead>
- <tbody>
- @foreach (var user in UserMedia) {
- <tr>
- @foreach (var column in columns) {
- <td>@column.GetValue(user)</td>
- }
- <td>
- <LinkButton href="@($"/Users/Delete/{user.Id}")" Color="#ff0000">Delete</LinkButton>
- </td>
- </tr>
- }
- </tbody>
-</table>
-
-@code {
-
- private Dictionary<PropertyInfo, bool> DisplayedColumns { get; set; } = typeof(FileMetadataModel).GetProperties()
- .ToDictionary(p => p, p => p.Name == "Username" || p.Name == "Id" || p.Name == "MessageCount");
-
- private List<UserModel> UserList { get; set; } = new();
- private List<FileMetadataModel> UserMedia { get; set; } = new();
-
- [SupplyParameterFromQuery(Name = "UserId")]
- public string? SelectedUserId {
- get;
- set {
- field = value;
- if (string.IsNullOrWhiteSpace(field))
- UserMedia.Clear();
- else _ = GetMediaForUser(value!);
- }
- }
-
- protected override async Task OnInitializedAsync() {
- using var hc = new HttpClient();
- hc.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Config.AccessToken);
- var response = await hc.GetAsync(Config.AdminUrl + "/_spacebar/admin/users/");
- if (!response.IsSuccessStatusCode) throw new Exception(await response.Content.ReadAsStringAsync());
- var content = response.Content.ReadFromJsonAsAsyncEnumerable<UserModel>();
- await foreach (var user in content) {
- UserList.Add(user!);
- StateHasChanged();
- }
- }
-
- private async Task GetMediaForUser(string userId) {
- using var hc = new HttpClient();
- hc.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Config.AccessToken);
- var response = await hc.GetAsync(Config.AdminUrl + $"/_spacebar/admin/media/user/{userId}/attachments");
- if (!response.IsSuccessStatusCode) throw new Exception(await response.Content.ReadAsStringAsync());
- var content = response.Content.ReadFromJsonAsAsyncEnumerable<FileMetadataModel>();
- await foreach (var media in content) {
- UserMedia.Add(media!);
- StateHasChanged();
- }
- }
-
-}
\ No newline at end of file
diff --git a/extra/admin-api/Utilities/Spacebar.AdminAPI.TestClient/Pages/Users.razor b/extra/admin-api/Utilities/Spacebar.AdminAPI.TestClient/Pages/Users.razor
deleted file mode 100644
index c0e678678..000000000
--- a/extra/admin-api/Utilities/Spacebar.AdminAPI.TestClient/Pages/Users.razor
+++ /dev/null
@@ -1,80 +0,0 @@
-@page "/Users"
-@using System.Net.Http.Headers
-@using System.Reflection
-@using Spacebar.AdminApi.Models
-@using Spacebar.AdminAPI.TestClient.Services
-@using ArcaneLibs.Blazor.Components
-@using ArcaneLibs.Extensions
-@inject Config Config
-@inject ILocalStorageService LocalStorage
-
-<PageTitle>Users</PageTitle>
-
-<details>
- <summary>Displayed columns</summary>
- @foreach (var column in DisplayedColumns) {
- var value = column.Value;
- <span>
- <InputCheckbox @bind-Value:get="@(value)" @bind-Value:set="@(b => {
- DisplayedColumns[column.Key] = b;
- StateHasChanged();
- })"/>
- @column.Key.Name
- </span>
- <br/>
- }
-</details>
-
-<p>Got @UserList.Count users.</p>
-<table class="table table-bordered">
- @{
- var columns = DisplayedColumns.Where(kvp => kvp.Value).Select(kvp => kvp.Key).ToList();
- }
- <thead>
- <tr>
- @foreach (var column in columns) {
- <th>@column.Name</th>
- }
- <th>Actions</th>
- </tr>
- </thead>
- <tbody>
- @foreach (var user in UserList.Where(x => !x.Deleted).OrderByDescending(x=>x.MessageCount)) {
- <tr>
- @foreach (var column in columns) {
- <td>@column.GetValue(user)</td>
- }
- <td>
- <LinkButton href="@($"/Users/Delete/{user.Id}")" Color="#ff0000">Delete</LinkButton>
- </td>
- </tr>
- }
- </tbody>
-</table>
-
-@code {
-
- private Dictionary<PropertyInfo, bool> DisplayedColumns { get; set; } = typeof(UserModel).GetProperties()
- .ToDictionary(p => p, p => p.Name == "Username" || p.Name == "Id" || p.Name == "MessageCount");
-
- private List<UserModel> UserList { get; set; } = new();
-
- protected override async Task OnInitializedAsync() {
- var hc = new StreamingHttpClient();
- hc.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Config.AccessToken);
-
- // var request = new HttpRequestMessage(HttpMethod.Get, Config.AdminUrl + "/_spacebar/admin/users/");
-
- var response = hc.GetAsyncEnumerableFromJsonAsync<UserModel>(Config.AdminUrl + "/_spacebar/admin/users/");
- // if (!response.IsSuccessStatusCode) throw new Exception(await response.Content.ReadAsStringAsync());
- // var content = response.Content.ReadFromJsonAsAsyncEnumerable<UserModel>();
- await foreach (var user in response) {
- // Console.WriteLine(user.ToJson(indent: false, ignoreNull: true));
- UserList.Add(user!);
- if(UserList.Count % 1000 == 0)
- StateHasChanged();
- }
- StateHasChanged();
- }
-
-}
\ No newline at end of file
diff --git a/extra/admin-api/Utilities/Spacebar.AdminAPI.TestClient/Pages/UsersDelete.razor b/extra/admin-api/Utilities/Spacebar.AdminAPI.TestClient/Pages/UsersDelete.razor
deleted file mode 100644
index 98a3e0fc7..000000000
--- a/extra/admin-api/Utilities/Spacebar.AdminAPI.TestClient/Pages/UsersDelete.razor
+++ /dev/null
@@ -1,70 +0,0 @@
-@page "/Users/Delete/{Id}"
-@using System.Net.Http.Headers
-@using System.Text.Json
-@using System.Text.Json.Nodes
-@using ArcaneLibs.Extensions
-@using Spacebar.AdminApi.Models
-@using Spacebar.AdminAPI.TestClient.Services
-@inject Config Config
-<h3>UsersDelete - @Id</h3>
-
-Deleted @ChannelDeleteProgress.Sum(x=>x.Value.Deleted) messages so far!
-@foreach (var (channel, progress) in ChannelDeleteProgress.Where(x=>x.Value.Deleted != x.Value.Total).OrderByDescending(x=>x.Value.Progress)) {
- <div>@channel: @progress.Total total, @progress.Deleted deleted</div>
- <progress max="@progress.Total" value="@progress.Deleted"></progress>
-}
-
-@if (Done) {
- <h1>Done!</h1>
-}
-
-@code {
-
- [Parameter]
- public required string Id { get; set; }
-
- private Dictionary<string, DeleteProgress> ChannelDeleteProgress { get; set; } = new();
-
- private bool Done { get; set; }
-
- 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/Users/{Id}/delete?messageDeleteChunkSize=100");
- if (!response.IsSuccessStatusCode) throw new Exception(await response.Content.ReadAsStringAsync());
- var content = response.Content.ReadFromJsonAsAsyncEnumerable<AsyncActionResult>();
- await foreach (var actionResult in content) {
- Console.WriteLine(actionResult.ToJson(indent: false));
- switch (actionResult.MessageType) {
- case "STATS": {
- var data = JsonSerializer.Deserialize<JsonObject>(actionResult.Data.ToJson());
- ChannelDeleteProgress = data!["messages_per_channel"]!
- .Deserialize<Dictionary<string, int>>()!
- .ToDictionary(x=>x.Key, x=>new DeleteProgress { Total = x.Value });
- break;
- }
- case "BULK_DELETED": {
- var data = JsonSerializer.Deserialize<JsonObject>(actionResult.Data.ToJson());
- ChannelDeleteProgress[data!["channel_id"]!.ToString()].Deleted += data!["deleted"]!.GetValue<int>();
- break;
- }
- default: {
- Console.WriteLine($"Unknown message type: {actionResult.MessageType}");
- break;
- }
- }
-
- StateHasChanged();
- await Task.Delay(1);
- }
-
- Done = true;
- StateHasChanged();
- }
-
- private class DeleteProgress {
- public int Total { get; set; }
- public int Deleted { get; set; } = 0;
- public float Progress => (float)Deleted / Total;
- }
-}
\ No newline at end of file
|