diff --git a/testFrontend/.idea/.idea.SafeNSound/.idea/vcs.xml b/testFrontend/.idea/.idea.SafeNSound/.idea/vcs.xml
index 94a25f7..6c0b863 100644
--- a/testFrontend/.idea/.idea.SafeNSound/.idea/vcs.xml
+++ b/testFrontend/.idea/.idea.SafeNSound/.idea/vcs.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
- <mapping directory="$PROJECT_DIR$" vcs="Git" />
+ <mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component>
</project>
\ No newline at end of file
diff --git a/testFrontend/SafeNSound.FakeUser/MonitorService.cs b/testFrontend/SafeNSound.FakeUser/MonitorService.cs
new file mode 100644
index 0000000..57d90a5
--- /dev/null
+++ b/testFrontend/SafeNSound.FakeUser/MonitorService.cs
@@ -0,0 +1,52 @@
+using ArcaneLibs.Extensions;
+
+namespace SafeNSound.FakeUser;
+
+public class MonitorService(ILogger<MonitorService> logger, UserStore userStore): IHostedService {
+ private Task? _getAllAlarmsTask, _assignBudgetTask;
+ private readonly CancellationTokenSource _cts = new();
+
+ public async Task StartAsync(CancellationToken cancellationToken) {
+ _getAllAlarmsTask = GetAllAlarms(_cts.Token);
+ _assignBudgetTask = AssignBudget(_cts.Token);
+ }
+
+ private async Task GetAllAlarms(CancellationToken cancellationToken) {
+ while (!cancellationToken.IsCancellationRequested) {
+ try {
+ var user = userStore.GetRandomMonitor();
+ var alarms = await user.Client!.GetAllAlarms();
+ if(alarms.Count > 0)
+ logger.LogInformation("Monitor {UserId} has outstanding alarms: {Alarm}", user.Auth.Username, alarms.ToJson(indent: false));
+ // else
+ // logger.LogInformation("Monitor {UserId} found no alarms to query", user.Auth.Username);
+ await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
+ } catch (Exception ex) {
+ logger.LogError(ex, "Error querying alarm");
+ }
+ }
+ }
+
+ private async Task AssignBudget(CancellationToken cancellationToken) {
+ while (!cancellationToken.IsCancellationRequested) {
+ try {
+ var user = userStore.GetRandomMonitor();
+ // var alarms = await user.Client!.GetAllAlarms();
+ // if(alarms.Count > 0)
+ // logger.LogInformation("Monitor {UserId} has outstanding alarms: {Alarm}", user.Auth.Username, alarms.ToJson(indent: false));
+ // else
+ // logger.LogInformation("Monitor {UserId} found no alarms to query", user.Auth.Username);
+ await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
+
+ } catch (Exception ex) {
+ logger.LogError(ex, "Error querying alarm");
+ }
+
+ }
+ }
+
+ public async Task StopAsync(CancellationToken cancellationToken) {
+ await _cts.CancelAsync();
+ await _getAllAlarmsTask!;
+ }
+}
\ No newline at end of file
diff --git a/testFrontend/SafeNSound.FakeUser/Program.cs b/testFrontend/SafeNSound.FakeUser/Program.cs
index 3751555..7c3eaff 100644
--- a/testFrontend/SafeNSound.FakeUser/Program.cs
+++ b/testFrontend/SafeNSound.FakeUser/Program.cs
@@ -1,2 +1,24 @@
// See https://aka.ms/new-console-template for more information
+
+using SafeNSound.FakeUser;
+using SafeNSound.Sdk;
+
Console.WriteLine("Hello, World!");
+
+var builder = Host.CreateApplicationBuilder(args);
+// longer shutdown timeout
+builder.Services.Configure<HostOptions>(options => {
+ options.ShutdownTimeout = TimeSpan.FromSeconds(120);
+});
+
+builder.Services.AddSingleton<SafeNSoundConfiguration>();
+builder.Services.AddSingleton<SafeNSoundAuthentication>();
+builder.Services.AddSingleton<UserStore>();
+builder.Services.AddHostedService<UserStore>(sp => sp.GetRequiredService<UserStore>());
+builder.Services.AddHostedService<RandomAlarmService>();
+builder.Services.AddHostedService<MonitorService>();
+
+// WrappedHttpClient.LogRequests = false;
+
+var host = builder.Build();
+host.Run();
\ No newline at end of file
diff --git a/testFrontend/SafeNSound.FakeUser/RandomAlarmService.cs b/testFrontend/SafeNSound.FakeUser/RandomAlarmService.cs
new file mode 100644
index 0000000..7835f89
--- /dev/null
+++ b/testFrontend/SafeNSound.FakeUser/RandomAlarmService.cs
@@ -0,0 +1,35 @@
+namespace SafeNSound.FakeUser;
+
+public class RandomAlarmService(UserStore userStore): IHostedService {
+ private Task? _listenerTask;
+ private readonly CancellationTokenSource _cts = new();
+
+ public async Task StartAsync(CancellationToken cancellationToken) {
+ _listenerTask = Run(_cts.Token);
+ }
+
+ private async Task Run(CancellationToken cancellationToken) {
+ while (!cancellationToken.IsCancellationRequested) {
+ try {
+ var user = userStore.GetRandomUser();
+ if (Random.Shared.Next(100) > 90) {
+ await user.Client!.SetAlarm(new Sdk.AlarmDto {
+ Reason = "fall"
+ });
+ }
+ else {
+ await user.Client!.DeleteAlarm();
+ }
+ }
+ catch (Exception ex) {
+ Console.WriteLine($"Error setting/deleting alarm: {ex.Message}");
+ }
+
+ await Task.Delay(TimeSpan.FromMilliseconds(100), cancellationToken);
+ }
+ }
+
+ public async Task StopAsync(CancellationToken cancellationToken) {
+ await _cts.CancelAsync();
+ }
+}
\ No newline at end of file
diff --git a/testFrontend/SafeNSound.FakeUser/SafeNSound.FakeUser.csproj b/testFrontend/SafeNSound.FakeUser/SafeNSound.FakeUser.csproj
index fd4bd08..830faaa 100644
--- a/testFrontend/SafeNSound.FakeUser/SafeNSound.FakeUser.csproj
+++ b/testFrontend/SafeNSound.FakeUser/SafeNSound.FakeUser.csproj
@@ -1,4 +1,4 @@
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk.Worker">
<PropertyGroup>
<OutputType>Exe</OutputType>
@@ -7,4 +7,12 @@
<Nullable>enable</Nullable>
</PropertyGroup>
+ <ItemGroup>
+ <PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.2"/>
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\SafeNSound.Sdk\SafeNSound.Sdk.csproj" />
+ </ItemGroup>
+
</Project>
diff --git a/testFrontend/SafeNSound.FakeUser/UserStore.cs b/testFrontend/SafeNSound.FakeUser/UserStore.cs
new file mode 100644
index 0000000..9b04efb
--- /dev/null
+++ b/testFrontend/SafeNSound.FakeUser/UserStore.cs
@@ -0,0 +1,97 @@
+using SafeNSound.Sdk;
+
+namespace SafeNSound.FakeUser;
+
+public class UserStore(SafeNSoundAuthentication authService, SafeNSoundConfiguration config) : IHostedService {
+ public List<ClientContainer> Admins { get; } = Enumerable.Range(0, 1).Select(_ => new ClientContainer()).ToList();
+ public List<ClientContainer> Monitors { get; } = Enumerable.Range(0, 5).Select(_ => new ClientContainer()).ToList();
+ public List<ClientContainer> Users { get; } = Enumerable.Range(0, 150000).Select(_ => new ClientContainer()).ToList();
+ public List<ClientContainer> AllUsers => [.. Users, .. Monitors, .. Admins];
+
+ public ClientContainer GetRandomUser() {
+ ClientContainer user;
+ do {
+ user = Users[new Random().Next(Users.Count)];
+ } while (user.Client == null);
+
+ return user;
+ }
+
+ public ClientContainer GetRandomMonitor() {
+ ClientContainer user;
+ do {
+ user = Monitors[new Random().Next(Monitors.Count)];
+ } while (user.Client == null);
+
+ return user;
+ }
+
+ public ClientContainer GetRandomAdmin() {
+ ClientContainer user;
+ do {
+ user = Admins[new Random().Next(Admins.Count)];
+ } while (user.Client == null);
+
+ return user;
+ }
+
+ public ClientContainer GetRandomUserOfAnyType() {
+ ClientContainer user;
+ do {
+ user = AllUsers[new Random().Next(AllUsers.Count)];
+ } while (user.Client == null);
+
+ return user;
+ }
+
+ public async Task StartAsync(CancellationToken cancellationToken) {
+ Admins.ForEach(x => x.Auth.UserType = "admin");
+ Monitors.ForEach(x => x.Auth.UserType = "monitor");
+ var ss = new SemaphoreSlim(256, 256);
+ var tasks = ((ClientContainer[]) [..Users, ..Monitors, ..Admins]).Select(async container => {
+ await ss.WaitAsync();
+ await authService.Register(container.Auth);
+ // container.Client = new SafeNSoundClient(config, (await authService.Login(container.Auth)).AccessToken);
+ // container.WhoAmI = await container.Client.WhoAmI();
+ ss.Release();
+ }).ToList();
+ await Task.WhenAll(tasks);
+
+ var users = Users.ToArray();
+ tasks = Monitors.Select(async container => {
+ var items = Random.Shared.GetItems(users, Users.Count / Monitors.Count).DistinctBy(x=>x.WhoAmI!.UserId);
+ foreach (var user in items) {
+ await container.Client!.AddAssignedUser(user.WhoAmI!.UserId);
+ }
+ }).ToList();
+ await Task.WhenAll(tasks);
+ }
+
+ public async Task StopAsync(CancellationToken cancellationToken) {
+ await Task.WhenAll(Users.Select(Cleanup).ToList());
+ await Task.WhenAll(Monitors.Select(Cleanup).ToList());
+ await Task.WhenAll(Admins.Select(Cleanup).ToList());
+ }
+
+ private async Task Cleanup(ClientContainer container) {
+ if (container.Client == null) return;
+ try {
+ await container.Client.DeleteAccount(container.Auth);
+ }
+ catch {
+ Console.WriteLine("Failed to delete account for user: " + container.Auth.Username);
+ }
+ }
+
+ public class ClientContainer {
+ public RegisterDto Auth { get; set; } = new() {
+ Email = $"{Guid.NewGuid()}@example.com",
+ Username = $"user-{Guid.NewGuid()}",
+ Password = Guid.NewGuid().ToString(),
+ UserType = "user"
+ };
+
+ public SafeNSoundClient? Client { get; set; }
+ public WhoAmI? WhoAmI { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/testFrontend/SafeNSound.FakeUser/appsettings.json b/testFrontend/SafeNSound.FakeUser/appsettings.json
new file mode 100644
index 0000000..f77ef07
--- /dev/null
+++ b/testFrontend/SafeNSound.FakeUser/appsettings.json
@@ -0,0 +1,13 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Trace",
+ "System": "Information",
+ "Microsoft": "Information",
+ "ArcaneLibs.Blazor.Components.AuthorizedImage": "Information"
+ }
+ },
+ "SafeNSound": {
+ "BaseUrl": "http://localhost:3000"
+ }
+}
diff --git a/testFrontend/SafeNSound.Frontend/Pages/Alarm.razor b/testFrontend/SafeNSound.Frontend/Pages/Alarm.razor
index 59b8e4c..1650a75 100644
--- a/testFrontend/SafeNSound.Frontend/Pages/Alarm.razor
+++ b/testFrontend/SafeNSound.Frontend/Pages/Alarm.razor
@@ -5,7 +5,7 @@
<LinkButton OnClick="@RaiseAlarm">Raise</LinkButton>
<LinkButton OnClick="@GetAlarm">Get</LinkButton>
<LinkButton OnClick="@ClearAlarm">Delete</LinkButton>
-<LinkButton OnClick="@ClearAlarm">Get all monitored</LinkButton>
+<LinkButton OnClick="@GetAllAlarms">Get all monitored</LinkButton>
<br/><br/>
@if (Exception != null) {
@@ -59,7 +59,8 @@
}
private async Task GetAllAlarms() {
- Result = await App.Client.GetAllAlarms();
+ Result = await App.Client!.GetAllAlarms();
+ StateHasChanged();
}
}
\ No newline at end of file
diff --git a/testFrontend/SafeNSound.Frontend/Pages/Auth.razor b/testFrontend/SafeNSound.Frontend/Pages/Auth.razor
index 7a9f5d1..5540f02 100644
--- a/testFrontend/SafeNSound.Frontend/Pages/Auth.razor
+++ b/testFrontend/SafeNSound.Frontend/Pages/Auth.razor
@@ -1,7 +1,7 @@
@page "/Auth"
<h1>Auth</h1>
-
+<u>User:</u><br/>
<span>Username (L?, R): </span>
<FancyTextBox @bind-Value="@Username"/><br/>
<span>Email (L? R): </span>
@@ -15,8 +15,16 @@
<LinkButton OnClick="@Login">Login</LinkButton>
<LinkButton OnClick="@WhoAmI">Who Am I</LinkButton>
<LinkButton OnClick="@Delete">Delete</LinkButton>
+<LinkButton OnClick="@MakeFullAdmin">Register superadmin</LinkButton>
<br/><br/>
+<u>Monitor:</u><br/>
+<span>User ID: </span>
+<FancyTextBox @bind-Value="@TargetUserId"/><br/>
+<LinkButton OnClick="@GetAssignedUsers">Get</LinkButton>
+<LinkButton OnClick="@AddAssignedUser">Add</LinkButton>
+<LinkButton OnClick="@RemoveAssignedUser">Remove</LinkButton>
+
@if (Exception != null) {
<div class="alert alert-danger">
<strong>Error:</strong><br/>
@@ -36,10 +44,12 @@
}
@code {
- private string Username { get; set; } = String.Empty;
- private string Email { get; set; } = String.Empty;
- private string Password { get; set; } = String.Empty;
- private string UserType { get; set; } = "";
+ private string Username { get; set; } = string.Empty;
+ private string Email { get; set; } = string.Empty;
+ private string Password { get; set; } = string.Empty;
+ private string UserType { get; set; } = string.Empty;
+
+ private string TargetUserId { get; set; } = string.Empty;
private Exception? Exception { get; set; }
private object? Result { get; set; }
@@ -74,7 +84,7 @@
Result = null;
Exception = null;
try {
- SafeNSoundAuthResult result;
+ AuthResult result;
Result = result = await Authentication.Login(new() {
Username = Username,
Password = Password,
@@ -118,4 +128,64 @@
StateHasChanged();
}
+ private async Task GetAssignedUsers() {
+ Result = null;
+ Exception = null;
+ try {
+ Result = await App.Client!.GetAssignedUsers();
+ }
+ catch (Exception ex) {
+ Exception = ex;
+ }
+ StateHasChanged();
+ }
+
+ private async Task AddAssignedUser() {
+ Result = null;
+ Exception = null;
+ try {
+ await App.Client!.AddAssignedUser(TargetUserId);
+ await GetAssignedUsers();
+ }
+ catch (Exception ex) {
+ Exception = ex;
+ }
+ StateHasChanged();
+ }
+
+ private async Task RemoveAssignedUser() {
+ Result = null;
+ Exception = null;
+ try {
+ await App.Client!.RemoveAssignedUser(TargetUserId);
+ await GetAssignedUsers();
+ }
+ catch (Exception ex) {
+ Exception = ex;
+ }
+ StateHasChanged();
+ }
+
+ private async Task MakeFullAdmin() {
+ Result = null;
+ Exception = null;
+ try {
+ AuthResult result;
+ RegisterDto auth = new() {
+ Username = Guid.NewGuid().ToString(),
+ Password = Guid.NewGuid().ToString(),
+ Email = Guid.NewGuid() + "@example.com",
+ UserType = "admin"
+ };
+ await Authentication.Register(auth);
+ Result = result = await Authentication.Login(auth);
+ App.Client = new SafeNSoundClient(Config, result.AccessToken);
+ await App.Client.MonitorAllUsers();
+ }
+ catch (Exception ex) {
+ Exception = ex;
+ }
+ StateHasChanged();
+ }
+
}
\ No newline at end of file
diff --git a/testFrontend/SafeNSound.Frontend/Program.cs b/testFrontend/SafeNSound.Frontend/Program.cs
index 2642a75..25537ba 100644
--- a/testFrontend/SafeNSound.Frontend/Program.cs
+++ b/testFrontend/SafeNSound.Frontend/Program.cs
@@ -45,4 +45,6 @@ builder.Services.AddBlazoredLocalStorage(config => {
config.JsonSerializerOptions.WriteIndented = false;
});
+WrappedHttpClient.LogRequests = false;
+
await builder.Build().RunAsync();
\ No newline at end of file
diff --git a/testFrontend/SafeNSound.Sdk/SafeNSoundAuthentication.cs b/testFrontend/SafeNSound.Sdk/SafeNSoundAuthentication.cs
index 333db6d..d0ed245 100644
--- a/testFrontend/SafeNSound.Sdk/SafeNSoundAuthentication.cs
+++ b/testFrontend/SafeNSound.Sdk/SafeNSoundAuthentication.cs
@@ -13,13 +13,13 @@ public class SafeNSoundAuthentication(SafeNSoundConfiguration config) {
res.EnsureSuccessStatusCode();
}
- public async Task<SafeNSoundAuthResult> Login(AuthDto authDto) {
+ public async Task<AuthResult> Login(AuthDto authDto) {
var hc = new WrappedHttpClient() {
BaseAddress = new Uri(config.BaseUri)
};
var res = await hc.PostAsJsonAsync("/auth/login", authDto);
- return (await res.Content.ReadFromJsonAsync<SafeNSoundAuthResult>())!;
+ return (await res.Content.ReadFromJsonAsync<AuthResult>())!;
}
public async Task Delete(AuthDto authDto) {
@@ -32,16 +32,7 @@ public class SafeNSoundAuthentication(SafeNSoundConfiguration config) {
}
}
-public class RegisterDto {
- [JsonPropertyName("username")]
- public string Username { get; set; } = string.Empty;
-
- [JsonPropertyName("password")]
- public string Password { get; set; } = string.Empty;
-
- [JsonPropertyName("email")]
- public string Email { get; set; } = string.Empty;
-
+public class RegisterDto : AuthDto {
[JsonPropertyName("type")]
public string UserType { get; set; } = string.Empty;
}
@@ -68,7 +59,7 @@ public class WhoAmI {
public required string UserType { get; set; }
}
-public class SafeNSoundAuthResult : WhoAmI {
+public class AuthResult : WhoAmI {
[JsonPropertyName("accessToken")]
public required string AccessToken { get; set; }
}
\ No newline at end of file
diff --git a/testFrontend/SafeNSound.Sdk/SafeNSoundClient.cs b/testFrontend/SafeNSound.Sdk/SafeNSoundClient.cs
index c6f16f2..8291178 100644
--- a/testFrontend/SafeNSound.Sdk/SafeNSoundClient.cs
+++ b/testFrontend/SafeNSound.Sdk/SafeNSoundClient.cs
@@ -1,4 +1,5 @@
using System.Net.Http.Json;
+using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
namespace SafeNSound.Sdk;
@@ -54,10 +55,47 @@ public class SafeNSoundClient(SafeNSoundConfiguration config, string accessToken
res.EnsureSuccessStatusCode();
return (await res.Content.ReadFromJsonAsync<Dictionary<string, AlarmDto>>())!;
}
+
+ public async Task DeleteAccount(AuthDto auth) {
+ var res = await HttpClient.DeleteAsJsonAsync("/auth/delete", auth);
+ res.EnsureSuccessStatusCode();
+ }
+
+ public async Task<List<string>> GetAssignedUsers() {
+ var res = await HttpClient.GetAsync("/monitor/assignedUsers");
+ res.EnsureSuccessStatusCode();
+ return (await res.Content.ReadFromJsonAsync<List<string>>())!;
+ }
+
+ public async Task AddAssignedUser(string targetUserId) {
+ var res = await HttpClient.PatchAsJsonAsync("/monitor/assignedUsers", new { userId = targetUserId });
+ res.EnsureSuccessStatusCode();
+ }
+
+ public async Task RemoveAssignedUser(string targetUserId) {
+ var res = await HttpClient.DeleteAsJsonAsync($"/monitor/assignedUsers", new { userId = targetUserId });
+ res.EnsureSuccessStatusCode();
+ }
+
+ public async IAsyncEnumerable<string> GetAllUserIdsEnumerable() {
+ var res = await HttpClient.GetAsync($"/admin/allUserIds");
+ res.EnsureSuccessStatusCode();
+ await foreach (var item in res.Content.ReadFromJsonAsAsyncEnumerable<string>()) {
+ yield return item!;
+ }
+ }
+
+ public async Task MonitorAllUsers() {
+ var res = await HttpClient.PostAsync("/admin/monitorAllUsers", null);
+ res.EnsureSuccessStatusCode();
+ }
}
public class AlarmDto {
[JsonPropertyName("reason")]
public required string Reason { get; set; }
+
+ [JsonPropertyName("createdAt")]
+ public DateTime CreatedAt { get; set; }
}
\ No newline at end of file
diff --git a/testFrontend/SafeNSound.Sdk/WrappedHttpClient.cs b/testFrontend/SafeNSound.Sdk/WrappedHttpClient.cs
index e4b4500..aa785cd 100644
--- a/testFrontend/SafeNSound.Sdk/WrappedHttpClient.cs
+++ b/testFrontend/SafeNSound.Sdk/WrappedHttpClient.cs
@@ -157,12 +157,15 @@ public class WrappedHttpClient
"Access-Control-Allow-Methods",
"Access-Control-Allow-Headers",
"Access-Control-Expose-Headers",
+ "Access-Control-Allow-Credentials",
"Cache-Control",
"Cross-Origin-Resource-Policy",
"X-Content-Security-Policy",
"Referrer-Policy",
"X-Robots-Tag",
- "Content-Security-Policy"
+ "Content-Security-Policy",
+ "Keep-Alive",
+ "ETag"
]));
return responseMessage;
@@ -338,4 +341,12 @@ public class WrappedHttpClient
};
return await SendAsync(request);
}
+
+ public async Task<HttpResponseMessage> PatchAsJsonAsync<T>(string url, T payload) {
+ var request = new HttpRequestMessage(HttpMethod.Patch, url)
+ {
+ Content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json")
+ };
+ return await SendAsync(request);
+ }
}
\ No newline at end of file
diff --git a/testFrontend/SafeNSound.sln b/testFrontend/SafeNSound.sln
index c5bee09..95edddc 100644
--- a/testFrontend/SafeNSound.sln
+++ b/testFrontend/SafeNSound.sln
@@ -22,6 +22,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.Timings", "Arcan
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.UsageTest", "ArcaneLibs\ArcaneLibs.UsageTest\ArcaneLibs.UsageTest.csproj", "{24FC30D3-E68C-471D-99DA-63C469C3262C}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SafeNSound.FakeUser", "SafeNSound.FakeUser\SafeNSound.FakeUser.csproj", "{AB7CDBEC-4D64-4FCF-B4F0-5FF3F86084B8}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -152,6 +154,18 @@ Global
{24FC30D3-E68C-471D-99DA-63C469C3262C}.Release|x64.Build.0 = Release|Any CPU
{24FC30D3-E68C-471D-99DA-63C469C3262C}.Release|x86.ActiveCfg = Release|Any CPU
{24FC30D3-E68C-471D-99DA-63C469C3262C}.Release|x86.Build.0 = Release|Any CPU
+ {AB7CDBEC-4D64-4FCF-B4F0-5FF3F86084B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {AB7CDBEC-4D64-4FCF-B4F0-5FF3F86084B8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {AB7CDBEC-4D64-4FCF-B4F0-5FF3F86084B8}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {AB7CDBEC-4D64-4FCF-B4F0-5FF3F86084B8}.Debug|x64.Build.0 = Debug|Any CPU
+ {AB7CDBEC-4D64-4FCF-B4F0-5FF3F86084B8}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {AB7CDBEC-4D64-4FCF-B4F0-5FF3F86084B8}.Debug|x86.Build.0 = Debug|Any CPU
+ {AB7CDBEC-4D64-4FCF-B4F0-5FF3F86084B8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {AB7CDBEC-4D64-4FCF-B4F0-5FF3F86084B8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {AB7CDBEC-4D64-4FCF-B4F0-5FF3F86084B8}.Release|x64.ActiveCfg = Release|Any CPU
+ {AB7CDBEC-4D64-4FCF-B4F0-5FF3F86084B8}.Release|x64.Build.0 = Release|Any CPU
+ {AB7CDBEC-4D64-4FCF-B4F0-5FF3F86084B8}.Release|x86.ActiveCfg = Release|Any CPU
+ {AB7CDBEC-4D64-4FCF-B4F0-5FF3F86084B8}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/testFrontend/SafeNSound.sln.DotSettings.user b/testFrontend/SafeNSound.sln.DotSettings.user
new file mode 100644
index 0000000..ae9fb74
--- /dev/null
+++ b/testFrontend/SafeNSound.sln.DotSettings.user
@@ -0,0 +1,11 @@
+<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
+ <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ATaskAwaiter_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fba81a6af46624b56ac6210bdbcc99af2d19e00_003F4b_003Fb9d0e80e_003FTaskAwaiter_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
+ <s:Boolean x:Key="/Default/Monitoring/Counters/=System_002ERuntime/@EntryIndexedValue">True</s:Boolean>
+ <s:Boolean x:Key="/Default/Monitoring/Counters/=Microsoft_002EAspNetCore_002EHttp_002EConnections/@EntryIndexedValue">True</s:Boolean>
+ <s:Boolean x:Key="/Default/Monitoring/Counters/=Microsoft_002EAspNetCore_002EHosting/@EntryIndexedValue">True</s:Boolean>
+ <s:Boolean x:Key="/Default/Monitoring/Counters/=Microsoft_002DAspNetCore_002DServer_002DKestrel/@EntryIndexedValue">True</s:Boolean>
+ <s:Boolean x:Key="/Default/Monitoring/Counters/=System_002ENet_002EHttp/@EntryIndexedValue">True</s:Boolean>
+ <s:Boolean x:Key="/Default/Monitoring/Counters/=System_002ENet_002ENameResolution/@EntryIndexedValue">True</s:Boolean>
+ <s:Boolean x:Key="/Default/Monitoring/Counters/=System_002ENet_002ESecurity/@EntryIndexedValue">True</s:Boolean>
+ <s:Boolean x:Key="/Default/Monitoring/Counters/=Microsoft_002EEntityFrameworkCore/@EntryIndexedValue">True</s:Boolean>
+ <s:Boolean x:Key="/Default/Monitoring/Counters/=System_002ENet_002ESockets/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
\ No newline at end of file
|