fix: some cleanup and fixing runtime bugs, refs NOISSUE
This commit is contained in:
		@@ -2,3 +2,5 @@
 | 
			
		||||
 | 
			
		||||
# CS8981: The type name only contains lower-cased ascii characters. Such names may become reserved for the language.
 | 
			
		||||
dotnet_diagnostic.CS8981.severity = none
 | 
			
		||||
dotnet_diagnostic.CS8602.severity = none
 | 
			
		||||
dotnet_diagnostic.CS8604.severity = none
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										46
									
								
								song_of_the_day/.vscode/launch.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								song_of_the_day/.vscode/launch.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,46 @@
 | 
			
		||||
{
 | 
			
		||||
    // Use IntelliSense to learn about possible attributes.
 | 
			
		||||
    // Hover to view descriptions of existing attributes.
 | 
			
		||||
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
 | 
			
		||||
    "version": "0.2.0",
 | 
			
		||||
    "configurations": [
 | 
			
		||||
        {
 | 
			
		||||
            "name": "C#: SongOfTheDay Debug",
 | 
			
		||||
            "type": "coreclr",
 | 
			
		||||
            "request": "launch",
 | 
			
		||||
            "preLaunchTask": "publish Debug",
 | 
			
		||||
            "program": "${workspaceFolder}/bin/Debug/net9.0/publish/song_of_the_day.dll",
 | 
			
		||||
            "cwd": "${workspaceFolder}/bin/Debug/net9.0/publish/",
 | 
			
		||||
            "args": [
 | 
			
		||||
                "/p:EnvironmentName=Development"
 | 
			
		||||
            ],
 | 
			
		||||
            "serverReadyAction": {
 | 
			
		||||
                "action": "openExternally",
 | 
			
		||||
                "pattern": "\\bNow listening on:\\s+(https?://\\S+)"
 | 
			
		||||
            },
 | 
			
		||||
            "env": {
 | 
			
		||||
                "ASPNETCORE_ENVIRONMENT": "Development",
 | 
			
		||||
                "Logging__LogLevel__Microsoft": "Information"
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "C#: SongOfTheDay Production",
 | 
			
		||||
            "type": "coreclr",
 | 
			
		||||
            "request": "launch",
 | 
			
		||||
            "preLaunchTask": "publish Release",
 | 
			
		||||
            "program": "${workspaceFolder}/bin/Release/net9.0/publish/song_of_the_day.dll",
 | 
			
		||||
            "cwd": "${workspaceFolder}/bin/Release/net9.0/publish/",
 | 
			
		||||
            "args": [
 | 
			
		||||
                "/p:EnvironmentName=Production"
 | 
			
		||||
            ],
 | 
			
		||||
            "serverReadyAction": {
 | 
			
		||||
                "action": "openExternally",
 | 
			
		||||
                "pattern": "\\bNow listening on:\\s+(https?://\\S+)"
 | 
			
		||||
            },
 | 
			
		||||
            "env": {
 | 
			
		||||
                "ASPNETCORE_ENVIRONMENT": "Production",
 | 
			
		||||
                "Logging__LogLevel__Microsoft": "Information"
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    ]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										47
									
								
								song_of_the_day/.vscode/tasks.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								song_of_the_day/.vscode/tasks.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,47 @@
 | 
			
		||||
{
 | 
			
		||||
    // See https://go.microsoft.com/fwlink/?LinkId=733558
 | 
			
		||||
    // for the documentation about the tasks.json format
 | 
			
		||||
    "version": "2.0.0",
 | 
			
		||||
    "tasks": [
 | 
			
		||||
        {
 | 
			
		||||
            "label": "publish Debug",
 | 
			
		||||
            "command": "dotnet",
 | 
			
		||||
            "type": "shell",
 | 
			
		||||
            "args": [
 | 
			
		||||
                "publish",
 | 
			
		||||
                // Ask dotnet build to generate full paths for file names.
 | 
			
		||||
                "/property:GenerateFullPaths=true",
 | 
			
		||||
                // Do not generate summary otherwise it leads to duplicate errors in Problems panel
 | 
			
		||||
                "/consoleloggerparameters:NoSummary",
 | 
			
		||||
                "/p:EnvironmentName=Development",
 | 
			
		||||
                "-c",
 | 
			
		||||
                "Debug"
 | 
			
		||||
            ],
 | 
			
		||||
            "group": "build",
 | 
			
		||||
            "presentation": {
 | 
			
		||||
                "reveal": "silent"
 | 
			
		||||
            },
 | 
			
		||||
            "problemMatcher": "$msCompile"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "label": "publish Release",
 | 
			
		||||
            "command": "dotnet",
 | 
			
		||||
            "type": "shell",
 | 
			
		||||
            "args": [
 | 
			
		||||
                "publish",
 | 
			
		||||
                // Ask dotnet build to generate full paths for file names.
 | 
			
		||||
                "/property:GenerateFullPaths=true",
 | 
			
		||||
                // Do not generate summary otherwise it leads to duplicate errors in Problems panel
 | 
			
		||||
                "/consoleloggerparameters:NoSummary",
 | 
			
		||||
                "/p:EnvironmentName=Production",
 | 
			
		||||
                "-c",
 | 
			
		||||
                "Release"
 | 
			
		||||
            ],
 | 
			
		||||
            "group": "build",
 | 
			
		||||
            "presentation": {
 | 
			
		||||
                "reveal": "silent"
 | 
			
		||||
            },
 | 
			
		||||
            "problemMatcher": "$msCompile"
 | 
			
		||||
        }
 | 
			
		||||
    ]
 | 
			
		||||
}
 | 
			
		||||
@@ -1,15 +1,16 @@
 | 
			
		||||
public class LdapAuthenticationService : IAuthenticationService
 | 
			
		||||
{
 | 
			
		||||
    private readonly IConfiguration _configuration;
 | 
			
		||||
    private LdapIntegration _ldapIntegration;
 | 
			
		||||
 | 
			
		||||
    public LdapAuthenticationService(IConfiguration configuration)
 | 
			
		||||
    public LdapAuthenticationService(IConfiguration configuration, LdapIntegration ldapIntegration)
 | 
			
		||||
    {
 | 
			
		||||
        _configuration = configuration;
 | 
			
		||||
        _ldapIntegration = ldapIntegration;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public bool Authenticate(string username, string password)
 | 
			
		||||
    {
 | 
			
		||||
        var ldapInstance = LdapIntegration.Instance;
 | 
			
		||||
        return ldapInstance == null ? false : ldapInstance.TestLogin(username, password);
 | 
			
		||||
        return _ldapIntegration == null ? false : _ldapIntegration.TestLogin(username, password);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -2,11 +2,13 @@ public class PhoneClaimCodeProviderService
 | 
			
		||||
{
 | 
			
		||||
    private Dictionary<string, string> _phoneClaimCodes;
 | 
			
		||||
    private Dictionary<string, string> _phoneClaimNumbers;
 | 
			
		||||
    private SignalIntegration _signalIntegration;
 | 
			
		||||
 | 
			
		||||
    public PhoneClaimCodeProviderService()
 | 
			
		||||
    public PhoneClaimCodeProviderService(SignalIntegration signalIntegration)
 | 
			
		||||
    {
 | 
			
		||||
        _phoneClaimCodes = new Dictionary<string, string>();
 | 
			
		||||
        _phoneClaimNumbers = new Dictionary<string, string>();
 | 
			
		||||
        _signalIntegration = signalIntegration;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static Random random = new Random();
 | 
			
		||||
@@ -32,7 +34,10 @@ public class PhoneClaimCodeProviderService
 | 
			
		||||
            _phoneClaimNumbers[username] = phoneNumber;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await SignalIntegration.Instance.SendMessageToUserAsync("Your phone number validation code is: " + generatedCode, phoneNumber);
 | 
			
		||||
        if (_signalIntegration != null)
 | 
			
		||||
        {
 | 
			
		||||
            await _signalIntegration.SendMessageToUserAsync("Your phone number validation code is: " + generatedCode, phoneNumber);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public string ValidateClaimCodeForUser(string code, string username)
 | 
			
		||||
 
 | 
			
		||||
@@ -6,8 +6,6 @@ using System.Linq;
 | 
			
		||||
 | 
			
		||||
public class LdapIntegration
 | 
			
		||||
{
 | 
			
		||||
    public static LdapIntegration? Instance;
 | 
			
		||||
 | 
			
		||||
    private readonly string[] attributesToQuery = new string[]
 | 
			
		||||
        {
 | 
			
		||||
            "uid",
 | 
			
		||||
@@ -16,12 +14,13 @@ public class LdapIntegration
 | 
			
		||||
            "mail"
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
    public LdapIntegration(string uri, int port, string adminBind, string adminPass)
 | 
			
		||||
    public LdapIntegration(ILogger<LdapIntegration> logger)
 | 
			
		||||
    {
 | 
			
		||||
        this.Uri = uri;
 | 
			
		||||
        this.Port = port;
 | 
			
		||||
        this.AdminBind = adminBind;
 | 
			
		||||
        this.AdminPass = adminPass;
 | 
			
		||||
        this.Uri = AppConfiguration.Instance.LDAPConfig.LDAPserver;
 | 
			
		||||
        this.Port = AppConfiguration.Instance.LDAPConfig.Port;
 | 
			
		||||
        this.AdminBind = AppConfiguration.Instance.LDAPConfig.Username;
 | 
			
		||||
        this.AdminPass = AppConfiguration.Instance.LDAPConfig.Password;
 | 
			
		||||
        this.logger = logger;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private string Uri { get; set; }
 | 
			
		||||
@@ -32,6 +31,8 @@ public class LdapIntegration
 | 
			
		||||
 | 
			
		||||
    private string AdminPass { get; set; }
 | 
			
		||||
 | 
			
		||||
    private ILogger<LdapIntegration> logger { get; set; }
 | 
			
		||||
 | 
			
		||||
    public bool TestLogin(string username, string password)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
 
 | 
			
		||||
@@ -1,29 +1,28 @@
 | 
			
		||||
 | 
			
		||||
using System.Collections;
 | 
			
		||||
using System.ComponentModel;
 | 
			
		||||
using song_of_the_day;
 | 
			
		||||
 | 
			
		||||
public class SignalIntegration
 | 
			
		||||
{
 | 
			
		||||
    public static SignalIntegration? Instance;
 | 
			
		||||
    private readonly ILogger<SignalIntegration> logger;
 | 
			
		||||
 | 
			
		||||
    private readonly ILogger logger;
 | 
			
		||||
 | 
			
		||||
    public SignalIntegration(string uri, int port, string phoneNumber)
 | 
			
		||||
    public SignalIntegration(ILogger<SignalIntegration> logger)
 | 
			
		||||
    {
 | 
			
		||||
        using ILoggerFactory factory = LoggerFactory.Create(builder => builder.AddConsole().SetMinimumLevel(LogLevel.Information));
 | 
			
		||||
        this.logger = factory.CreateLogger("SignalIntegration");
 | 
			
		||||
 | 
			
		||||
        var uri = AppConfiguration.Instance.SignalAPIEndpointUri;
 | 
			
		||||
        var port = int.Parse(AppConfiguration.Instance.SignalAPIEndpointPort);
 | 
			
		||||
        var phoneNumber = AppConfiguration.Instance.HostPhoneNumber;
 | 
			
		||||
        this.logger = logger;
 | 
			
		||||
 | 
			
		||||
        var http = new HttpClient()
 | 
			
		||||
        {
 | 
			
		||||
            BaseAddress = new Uri(uri + ":" + port)
 | 
			
		||||
        };
 | 
			
		||||
        apiClient = new song_of_the_day.swaggerClient(http);
 | 
			
		||||
        apiClient = new swaggerClient(http);
 | 
			
		||||
        apiClient.BaseUrl = "";
 | 
			
		||||
        this.phoneNumber = phoneNumber;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private song_of_the_day.swaggerClient apiClient;
 | 
			
		||||
    private swaggerClient apiClient;
 | 
			
		||||
 | 
			
		||||
    private string phoneNumber;
 | 
			
		||||
 | 
			
		||||
@@ -83,6 +82,11 @@ public class SignalIntegration
 | 
			
		||||
 | 
			
		||||
    public async Task IntroduceUserAsync(User user)
 | 
			
		||||
    {
 | 
			
		||||
        if (user == null || user.SignalMemberId == null)
 | 
			
		||||
        {
 | 
			
		||||
            logger.LogWarning("Attempt to introduce unknown user was aborted.");
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        await this.SendMessageToUserAsync("Hi, my name is Proggy and I am your friendly neighborhood *Song of the Day* bot!", user.SignalMemberId);
 | 
			
		||||
        await this.SendMessageToUserAsync("You are receiving this message because you have been invited to a *Song of the Day* community group.", user.SignalMemberId);
 | 
			
		||||
        await this.SendMessageToUserAsync("In that community group I will pick a person at random each day at 8 AM and encourage them to share a song with the rest of the community.", user.SignalMemberId);
 | 
			
		||||
 
 | 
			
		||||
@@ -3,8 +3,8 @@
 | 
			
		||||
    {
 | 
			
		||||
        using (var dci = DataContext.Instance)
 | 
			
		||||
        {
 | 
			
		||||
            var user = dci.Users.Where(u => u.LdapUserName == User.Identity.Name);
 | 
			
		||||
            return user.Any();
 | 
			
		||||
            var user = dci.Users?.Where(u => u.LdapUserName == User.Identity.Name);
 | 
			
		||||
            return user == null ? false : user.Any();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -18,7 +18,7 @@
 | 
			
		||||
    <script type="importmap"></script>
 | 
			
		||||
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
 | 
			
		||||
    <link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
 | 
			
		||||
    <link rel="stylesheet" href="~/sotd.styles.css" asp-append-version="true" />
 | 
			
		||||
    <link rel="stylesheet" href="~/song_of_the_day.styles.css" asp-append-version="true" />
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
    <header>
 | 
			
		||||
@@ -37,13 +37,15 @@
 | 
			
		||||
                        <li class="nav-item">
 | 
			
		||||
                            <a class="nav-link text-dark" asp-area="" asp-page="/SuggestionHelpers">Suggestion Helpers</a>
 | 
			
		||||
                        </li>
 | 
			
		||||
                        @if (this.User.Identity.IsAuthenticated && !DoesUserHaveClaimedPhoneNumber())
 | 
			
		||||
                        @if (this.User != null && this.User.Identity != null &&
 | 
			
		||||
                            this.User.Identity.IsAuthenticated && !DoesUserHaveClaimedPhoneNumber())
 | 
			
		||||
                        {
 | 
			
		||||
                            <li class="nav-item">
 | 
			
		||||
                                <a class="nav-link text-dark" asp-area="" asp-page="/UnclaimedPhoneNumbers">Unclaimed Phone Numbers</a>
 | 
			
		||||
                            </li>
 | 
			
		||||
                        }
 | 
			
		||||
                        @if (this.User.Identity.IsAuthenticated && DoesUserHaveClaimedPhoneNumber())
 | 
			
		||||
                        @if (this.User != null && this.User.Identity != null &&
 | 
			
		||||
                            this.User.Identity.IsAuthenticated && DoesUserHaveClaimedPhoneNumber())
 | 
			
		||||
                        {
 | 
			
		||||
                            <li class="nav-item">
 | 
			
		||||
                                <a class="nav-link text-dark" asp-area="" asp-page="/SubmitSongs">Submit Songs</a>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
@using Microsoft.AspNetCore.Authentication
 | 
			
		||||
 | 
			
		||||
<div class="loginform">
 | 
			
		||||
    @if (!this.User.Identity.IsAuthenticated)
 | 
			
		||||
    @if (this.User == null || this.User.Identity == null || !this.User.Identity.IsAuthenticated)
 | 
			
		||||
    {
 | 
			
		||||
        <form method="post" action="Auth/Login">
 | 
			
		||||
            <div>
 | 
			
		||||
 
 | 
			
		||||
@@ -17,20 +17,26 @@ public class SubmitSongsModel : PageModel
 | 
			
		||||
    {
 | 
			
		||||
        _logger = logger;
 | 
			
		||||
        this.songResolver = songResolver;
 | 
			
		||||
        _submitUrl = string.Empty;
 | 
			
		||||
        SongData = new Song();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [BindProperty]
 | 
			
		||||
    public bool IsValidUrl { get; set; } = true;
 | 
			
		||||
 | 
			
		||||
    [BindProperty]
 | 
			
		||||
    public string SubmitUrl { 
 | 
			
		||||
        get {
 | 
			
		||||
    public string SubmitUrl
 | 
			
		||||
    {
 | 
			
		||||
        get
 | 
			
		||||
        {
 | 
			
		||||
            return _submitUrl;
 | 
			
		||||
        }
 | 
			
		||||
        set {
 | 
			
		||||
        set
 | 
			
		||||
        {
 | 
			
		||||
            _submitUrl = value.ToString();
 | 
			
		||||
            Uri? newValue = default;
 | 
			
		||||
            try {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                newValue = new Uri(_submitUrl);
 | 
			
		||||
            }
 | 
			
		||||
            catch (UriFormatException)
 | 
			
		||||
@@ -41,7 +47,7 @@ public class SubmitSongsModel : PageModel
 | 
			
		||||
 | 
			
		||||
            IsValidUrl = true;
 | 
			
		||||
 | 
			
		||||
            if(this.songResolver.CanValidate(newValue))
 | 
			
		||||
            if (this.songResolver.CanValidate(newValue))
 | 
			
		||||
            {
 | 
			
		||||
                this.SongData = this.songResolver.ResolveSongAsync(newValue).Result;
 | 
			
		||||
            }
 | 
			
		||||
@@ -49,9 +55,13 @@ public class SubmitSongsModel : PageModel
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [BindProperty]
 | 
			
		||||
    public bool CanSubmit { get {
 | 
			
		||||
        return !string.IsNullOrEmpty(SongData?.Artist) && ! string.IsNullOrEmpty(SongData?.Name);
 | 
			
		||||
    } }
 | 
			
		||||
    public bool CanSubmit
 | 
			
		||||
    {
 | 
			
		||||
        get
 | 
			
		||||
        {
 | 
			
		||||
            return !string.IsNullOrEmpty(SongData?.Artist) && !string.IsNullOrEmpty(SongData?.Name);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [BindProperty]
 | 
			
		||||
    public Song SongData { get; set; }
 | 
			
		||||
@@ -66,6 +76,6 @@ public class SubmitSongsModel : PageModel
 | 
			
		||||
    {
 | 
			
		||||
        var songUrl = Request.Query["SubmitUrl"];
 | 
			
		||||
        this.SubmitUrl = songUrl.ToString();
 | 
			
		||||
        return Partial("_SongPartial", SongData);;
 | 
			
		||||
        return Partial("_SongPartial", SongData); ;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -29,7 +29,7 @@ public class SuggestionHelpersModel : PageModel
 | 
			
		||||
    {
 | 
			
		||||
        using (var dci = DataContext.Instance)
 | 
			
		||||
        {
 | 
			
		||||
            this.SuggestionHelpers = dci.SuggestionHelpers.ToList();
 | 
			
		||||
            this.SuggestionHelpers = dci.SuggestionHelpers == null ? new List<SuggestionHelper>() : dci.SuggestionHelpers.ToList();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -42,9 +42,9 @@ public class SuggestionHelpersModel : PageModel
 | 
			
		||||
                Title = this.NewSuggestionTitle,
 | 
			
		||||
                Description = this.NewSuggestionDescription
 | 
			
		||||
            };
 | 
			
		||||
            dci.SuggestionHelpers.Add(newHelper);
 | 
			
		||||
            dci.SuggestionHelpers?.Add(newHelper);
 | 
			
		||||
            dci.SaveChanges();
 | 
			
		||||
            this.SuggestionHelpers = dci.SuggestionHelpers.ToList();
 | 
			
		||||
            this.SuggestionHelpers = dci.SuggestionHelpers == null ? new List<SuggestionHelper>() : dci.SuggestionHelpers.ToList();
 | 
			
		||||
        }
 | 
			
		||||
        this.NewSuggestionDescription = "";
 | 
			
		||||
        this.NewSuggestionTitle = "";
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
using Microsoft.AspNetCore.Mvc;
 | 
			
		||||
using Microsoft.AspNetCore.Mvc.RazorPages;
 | 
			
		||||
using Microsoft.VisualBasic;
 | 
			
		||||
using SpotifyAPI.Web;
 | 
			
		||||
 | 
			
		||||
namespace sotd.Pages;
 | 
			
		||||
 | 
			
		||||
@@ -11,6 +12,7 @@ public class UnclaimedPhoneNumbersModel : PageModel
 | 
			
		||||
    public UnclaimedPhoneNumbersModel(ILogger<UserModel> logger)
 | 
			
		||||
    {
 | 
			
		||||
        _logger = logger;
 | 
			
		||||
        UnclaimedUsers = new List<User>();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public int userId { get; set; }
 | 
			
		||||
@@ -22,7 +24,7 @@ public class UnclaimedPhoneNumbersModel : PageModel
 | 
			
		||||
    {
 | 
			
		||||
        using (var dci = DataContext.Instance)
 | 
			
		||||
        {
 | 
			
		||||
            this.UnclaimedUsers = dci.Users.Where(u => string.IsNullOrEmpty(u.LdapUserName)).ToList();
 | 
			
		||||
            this.UnclaimedUsers = dci.Users == null ? new List<User>(): dci.Users.Where(u => string.IsNullOrEmpty(u.LdapUserName)).ToList();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -25,9 +25,9 @@ public class UserModel : PageModel
 | 
			
		||||
    {
 | 
			
		||||
        using (var dci = DataContext.Instance)
 | 
			
		||||
        {
 | 
			
		||||
            var user = dci.Users.Find(userIndex);
 | 
			
		||||
            this.UserName = user.Name;
 | 
			
		||||
            this.UserNickName = user.NickName;
 | 
			
		||||
            var user = dci.Users?.Find(userIndex);
 | 
			
		||||
            this.UserName = user == null ? string.Empty : (user.Name ?? string.Empty);
 | 
			
		||||
            this.UserNickName = user == null ? string.Empty : (user.NickName ?? string.Empty);
 | 
			
		||||
            this.userId = userIndex;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -36,10 +36,13 @@ public class UserModel : PageModel
 | 
			
		||||
    {
 | 
			
		||||
        using (var dci = DataContext.Instance)
 | 
			
		||||
        {
 | 
			
		||||
            var user = dci.Users.Find(userIndex);
 | 
			
		||||
            user.NickName = this.UserNickName;
 | 
			
		||||
            dci.SaveChanges();
 | 
			
		||||
            this.UserName = user.Name;
 | 
			
		||||
            var user = dci.Users?.Find(userIndex);
 | 
			
		||||
            if (user != null)
 | 
			
		||||
            {
 | 
			
		||||
                user.NickName = this.UserNickName;
 | 
			
		||||
                dci.SaveChanges();
 | 
			
		||||
                this.UserName = user.Name ?? string.Empty;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,31 +1,37 @@
 | 
			
		||||
 | 
			
		||||
using Scalar.AspNetCore;
 | 
			
		||||
using Microsoft.AspNetCore.OpenApi;
 | 
			
		||||
using Microsoft.EntityFrameworkCore;
 | 
			
		||||
using Microsoft.AspNetCore.Authentication;
 | 
			
		||||
using Microsoft.AspNetCore.Authentication.Cookies;
 | 
			
		||||
using System.DirectoryServices.Protocols;
 | 
			
		||||
using System.Runtime.CompilerServices;
 | 
			
		||||
 | 
			
		||||
SignalIntegration.Instance = new SignalIntegration(AppConfiguration.Instance.SignalAPIEndpointUri,
 | 
			
		||||
                                                    int.Parse(AppConfiguration.Instance.SignalAPIEndpointPort),
 | 
			
		||||
                                                    AppConfiguration.Instance.HostPhoneNumber);
 | 
			
		||||
 | 
			
		||||
LdapIntegration.Instance = new LdapIntegration(AppConfiguration.Instance.LDAPConfig.LDAPserver,
 | 
			
		||||
                                                    AppConfiguration.Instance.LDAPConfig.Port,
 | 
			
		||||
                                                    AppConfiguration.Instance.LDAPConfig.Username,
 | 
			
		||||
                                                    AppConfiguration.Instance.LDAPConfig.Password);
 | 
			
		||||
 | 
			
		||||
var builder = WebApplication.CreateBuilder(args);
 | 
			
		||||
 | 
			
		||||
using ILoggerFactory factory = LoggerFactory.Create(builder => builder.AddConsole().SetMinimumLevel(LogLevel.Information));
 | 
			
		||||
var logger = factory.CreateLogger("SongResolver");
 | 
			
		||||
 | 
			
		||||
// Add services to the container.
 | 
			
		||||
builder.Services.AddRazorPages();
 | 
			
		||||
builder.Services.AddOpenApi();
 | 
			
		||||
builder.Services.AddLogging((ILoggingBuilder b) => b.ClearProviders().AddConsole());
 | 
			
		||||
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
 | 
			
		||||
    .AddCookie(options =>
 | 
			
		||||
    {
 | 
			
		||||
        options.LoginPath = "/Auth/Login";
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
builder.Services.AddSingleton<LdapAuthenticationService>();
 | 
			
		||||
builder.Services.AddSingleton<PhoneClaimCodeProviderService>();
 | 
			
		||||
builder.Services.AddSingleton<SignalIntegration>();
 | 
			
		||||
builder.Services.AddSingleton<LdapIntegration>();
 | 
			
		||||
builder.Services.AddSingleton<SongResolver>();
 | 
			
		||||
 | 
			
		||||
var app = builder.Build();
 | 
			
		||||
 | 
			
		||||
var logger = app.Logger;
 | 
			
		||||
 | 
			
		||||
logger.LogTrace("Setting up user check timer");
 | 
			
		||||
var userCheckTimer = new CronTimer("*/1 * * * *", "Europe/Vienna", includingSeconds: false);
 | 
			
		||||
userCheckTimer.OnOccurence += async (s, ea) =>
 | 
			
		||||
{
 | 
			
		||||
    var memberList = await SignalIntegration.Instance.GetMemberListAsync();
 | 
			
		||||
    var signalIntegration = app.Services.GetService<SignalIntegration>();
 | 
			
		||||
    var memberList = await signalIntegration.GetMemberListAsync();
 | 
			
		||||
    var dci = DataContext.Instance;
 | 
			
		||||
    var needsSaving = false;
 | 
			
		||||
    foreach (var memberId in memberList)
 | 
			
		||||
@@ -33,7 +39,7 @@ userCheckTimer.OnOccurence += async (s, ea) =>
 | 
			
		||||
        var foundUser = dci.Users?.Where(u => u.SignalMemberId == memberId).SingleOrDefault();
 | 
			
		||||
        if (foundUser == null)
 | 
			
		||||
        {
 | 
			
		||||
            var newUserContact = await SignalIntegration.Instance.GetContactAsync(memberId);
 | 
			
		||||
            var newUserContact = await signalIntegration.GetContactAsync(memberId);
 | 
			
		||||
            logger.LogDebug("New user:");
 | 
			
		||||
            logger.LogDebug($"   Name: {newUserContact.Name}");
 | 
			
		||||
            logger.LogDebug($"   MemberId: {memberId}");
 | 
			
		||||
@@ -74,7 +80,8 @@ userIntroTimer.OnOccurence += async (s, ea) =>
 | 
			
		||||
    bool needsSaving = false;
 | 
			
		||||
    foreach (var user in introUsers)
 | 
			
		||||
    {
 | 
			
		||||
        await SignalIntegration.Instance.IntroduceUserAsync(user);
 | 
			
		||||
        var signalIntegration = app.Services.GetService<SignalIntegration>();
 | 
			
		||||
        await signalIntegration.IntroduceUserAsync(user);
 | 
			
		||||
        user.IsIntroduced = true;
 | 
			
		||||
        needsSaving = true;
 | 
			
		||||
    }
 | 
			
		||||
@@ -137,12 +144,14 @@ pickOfTheDayTimer.OnOccurence += async (s, ea) =>
 | 
			
		||||
    if (luckyUser.SignalMemberId is string signalId)
 | 
			
		||||
    {
 | 
			
		||||
        await dci.SongSuggestions.AddAsync(newSongSuggestion);
 | 
			
		||||
        luckyUser.WasChosenForSuggestionThisRound = true;
 | 
			
		||||
        await dci.SaveChangesAsync();
 | 
			
		||||
        await SignalIntegration.Instance.SendMessageToGroupAsync($"Today's chosen person to share a song is: **{userName}**");
 | 
			
		||||
        await SignalIntegration.Instance.SendMessageToGroupAsync($"Today's (optional) suggestion helper to help you pick a song is:\n\n**{suggestion.Title}**\n\n*{suggestion.Description}*");
 | 
			
		||||
        await SignalIntegration.Instance.SendMessageToUserAsync($"Congratulations, you have been chosen to share a song today!", signalId);
 | 
			
		||||
        await SignalIntegration.Instance.SendMessageToUserAsync($"Today's (optional) suggestion helper to help you pick a song is:\n\n**{suggestion.Title}**\n\n*{suggestion.Description}*", signalId);
 | 
			
		||||
        await SignalIntegration.Instance.SendMessageToUserAsync($"For now please just share your suggestion with the group - in the future I might ask you to share directly with me or via the website to help me keep track of past suggestions!", luckyUser.SignalMemberId);
 | 
			
		||||
        var signalIntegration = app.Services.GetService<SignalIntegration>();
 | 
			
		||||
        await signalIntegration.SendMessageToGroupAsync($"Today's chosen person to share a song is: **{userName}**");
 | 
			
		||||
        await signalIntegration.SendMessageToGroupAsync($"Today's (optional) suggestion helper to help you pick a song is:\n\n**{suggestion.Title}**\n\n*{suggestion.Description}*");
 | 
			
		||||
        await signalIntegration.SendMessageToUserAsync($"Congratulations, you have been chosen to share a song today!", signalId);
 | 
			
		||||
        await signalIntegration.SendMessageToUserAsync($"Today's (optional) suggestion helper to help you pick a song is:\n\n**{suggestion.Title}**\n\n*{suggestion.Description}*", signalId);
 | 
			
		||||
        await signalIntegration.SendMessageToUserAsync($"For now please just share your suggestion with the group - in the future I might ask you to share directly with me or via the website to help me keep track of past suggestions!", luckyUser.SignalMemberId);
 | 
			
		||||
    }
 | 
			
		||||
    await dci.DisposeAsync();
 | 
			
		||||
};
 | 
			
		||||
@@ -151,10 +160,11 @@ var startUserAssociationProcess = async (User userToAssociate) =>
 | 
			
		||||
{
 | 
			
		||||
    if (userToAssociate.SignalMemberId is string signalId)
 | 
			
		||||
    {
 | 
			
		||||
        await SignalIntegration.Instance.SendMessageToUserAsync($"Hi, I see you are not associated with any website user yet.", signalId);
 | 
			
		||||
        await SignalIntegration.Instance.SendMessageToUserAsync($"If you haven't yet, please navigate to https://users.disi.dev to create a new account.", signalId);
 | 
			
		||||
        await SignalIntegration.Instance.SendMessageToUserAsync($"Once you have done so, go to https://sotd.disi.dev, login, navigate to \"Unclaimed Phone Numbers\" and click on the \"Claim\" button to start the claim process.", signalId);
 | 
			
		||||
        await SignalIntegration.Instance.SendMessageToUserAsync($"With a future update you will be required to submit songs via your user account - at that point you will be skipped during the selection process if you have not yet claimed your phone number!", signalId);
 | 
			
		||||
        var signalIntegration = app.Services.GetService<SignalIntegration>();
 | 
			
		||||
        await signalIntegration.SendMessageToUserAsync($"Hi, I see you are not associated with any website user yet.", signalId);
 | 
			
		||||
        await signalIntegration.SendMessageToUserAsync($"If you haven't yet, please navigate to https://users.disi.dev to create a new account.", signalId);
 | 
			
		||||
        await signalIntegration.SendMessageToUserAsync($"Once you have done so, go to https://sotd.disi.dev, login, navigate to \"Unclaimed Phone Numbers\" and click on the \"Claim\" button to start the claim process.", signalId);
 | 
			
		||||
        await signalIntegration.SendMessageToUserAsync($"With a future update you will be required to submit songs via your user account - at that point you will be skipped during the selection process if you have not yet claimed your phone number!", signalId);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -187,30 +197,20 @@ ldapAssociationTimer.OnOccurence += async (s, ea) =>
 | 
			
		||||
    await dci.DisposeAsync();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Add services to the container.
 | 
			
		||||
builder.Services.AddRazorPages();
 | 
			
		||||
builder.Services.AddOpenApi();
 | 
			
		||||
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
 | 
			
		||||
    .AddCookie(options =>
 | 
			
		||||
    {
 | 
			
		||||
        options.LoginPath = "/Auth/Login";
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
builder.Services.AddSingleton<LdapAuthenticationService>();
 | 
			
		||||
builder.Services.AddSingleton<PhoneClaimCodeProviderService>();
 | 
			
		||||
builder.Services.AddSingleton<SongResolver>();
 | 
			
		||||
 | 
			
		||||
var app = builder.Build();
 | 
			
		||||
 | 
			
		||||
// only start interaction timers in production builds
 | 
			
		||||
// for local/development testing we want those disabled
 | 
			
		||||
if (!app.Environment.IsDevelopment())
 | 
			
		||||
{
 | 
			
		||||
    logger.LogTrace("Starting timer for scheduled processes.");
 | 
			
		||||
    userCheckTimer.Start();
 | 
			
		||||
    userIntroTimer.Start();
 | 
			
		||||
    pickOfTheDayTimer.Start();
 | 
			
		||||
    ldapAssociationTimer.Start();
 | 
			
		||||
}
 | 
			
		||||
else
 | 
			
		||||
{
 | 
			
		||||
    logger.LogTrace("This is a debug build - scheduled processes are disabled.");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Configure the HTTP request pipeline.
 | 
			
		||||
if (!app.Environment.IsDevelopment())
 | 
			
		||||
 
 | 
			
		||||
@@ -4,17 +4,17 @@ public class SongResolver
 | 
			
		||||
{
 | 
			
		||||
    private readonly IEnumerable<ISongValidator> _songValidators;
 | 
			
		||||
 | 
			
		||||
    private readonly ILogger logger;
 | 
			
		||||
    private readonly ILogger<SongResolver> logger;
 | 
			
		||||
 | 
			
		||||
    public SongResolver()
 | 
			
		||||
    public SongResolver(ILogger<SongResolver> logger)
 | 
			
		||||
    {
 | 
			
		||||
        using ILoggerFactory factory = LoggerFactory.Create(builder => builder.AddConsole().SetMinimumLevel(LogLevel.Information));
 | 
			
		||||
        this.logger = factory.CreateLogger("SongResolver");
 | 
			
		||||
        
 | 
			
		||||
        this.logger = logger;
 | 
			
		||||
 | 
			
		||||
        this._songValidators = new List<ISongValidator>();
 | 
			
		||||
 | 
			
		||||
        foreach (Type mytype in System.Reflection.Assembly.GetExecutingAssembly().GetTypes()
 | 
			
		||||
                 .Where(mytype => mytype.GetInterfaces().Contains(typeof(ISongValidator)) && !(mytype.Name.EndsWith("Base")))) {
 | 
			
		||||
                 .Where(mytype => mytype.GetInterfaces().Contains(typeof(ISongValidator)) && !(mytype.Name.EndsWith("Base"))))
 | 
			
		||||
        {
 | 
			
		||||
            if (Activator.CreateInstance(mytype) is ISongValidator validator)
 | 
			
		||||
            {
 | 
			
		||||
                logger.LogDebug("Registering song validator: {ValidatorType}", mytype.Name);
 | 
			
		||||
@@ -32,23 +32,25 @@ public class SongResolver
 | 
			
		||||
                if (!await validator.CanExtractSongMetadataAsync(songUri))
 | 
			
		||||
                {
 | 
			
		||||
                    this.logger.LogWarning("Cannot extract metadata for song URI: {SongUri}", songUri);
 | 
			
		||||
                    return new Song {
 | 
			
		||||
                                    Artist = "Unknown Artist",
 | 
			
		||||
                                    Name = "Unknown Title",
 | 
			
		||||
                                    Url = songUri.ToString(),
 | 
			
		||||
                                    Provider = SongProvider.PlainHttp,
 | 
			
		||||
                                };
 | 
			
		||||
                }
 | 
			
		||||
                return await validator.ValidateAsync(songUri);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return new Song {
 | 
			
		||||
                    return new Song
 | 
			
		||||
                    {
 | 
			
		||||
                        Artist = "Unknown Artist",
 | 
			
		||||
                        Name = "Unknown Title",
 | 
			
		||||
                        Url = songUri.ToString(),
 | 
			
		||||
                        Provider = SongProvider.PlainHttp,
 | 
			
		||||
                    };
 | 
			
		||||
                }
 | 
			
		||||
                return await validator.ValidateAsync(songUri);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return new Song
 | 
			
		||||
        {
 | 
			
		||||
            Artist = "Unknown Artist",
 | 
			
		||||
            Name = "Unknown Title",
 | 
			
		||||
            Url = songUri.ToString(),
 | 
			
		||||
            Provider = SongProvider.PlainHttp,
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public bool CanValidate(Uri songUri)
 | 
			
		||||
 
 | 
			
		||||
@@ -14,9 +14,9 @@ public class SpotifyValidator : UriBasedSongValidatorBase
 | 
			
		||||
        spotifyApiClient = new SpotifyApiClient();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public override async Task<bool> CanExtractSongMetadataAsync(Uri songUri)
 | 
			
		||||
    public override Task<bool> CanExtractSongMetadataAsync(Uri songUri)
 | 
			
		||||
    {
 | 
			
		||||
        return this.CanValidateUri(songUri);
 | 
			
		||||
        return Task.FromResult(this.CanValidateUri(songUri));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public override async Task<Song> ValidateAsync(Uri songUri)
 | 
			
		||||
@@ -25,7 +25,7 @@ public class SpotifyValidator : UriBasedSongValidatorBase
 | 
			
		||||
        var trackIdMatch = regexp.Match(songUri.ToString()).Groups[2].Value;
 | 
			
		||||
 | 
			
		||||
        var track = await spotifyApiClient.GetTrackByIdAsync(trackIdMatch);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        var song = new Song
 | 
			
		||||
        {
 | 
			
		||||
            Name = track.Name,
 | 
			
		||||
 
 | 
			
		||||
@@ -5,9 +5,9 @@ public class YoutubeMusicValidator : UriBasedSongValidatorBase
 | 
			
		||||
{
 | 
			
		||||
    public override string UriValidatorRegex => @"^(https?://)?(music\.youtube\.com/watch\?v=|youtu\.be/)([a-zA-Z0-9_-]{11})";
 | 
			
		||||
 | 
			
		||||
    public override async Task<bool> CanExtractSongMetadataAsync(Uri songUri)
 | 
			
		||||
    public override Task<bool> CanExtractSongMetadataAsync(Uri songUri)
 | 
			
		||||
    {
 | 
			
		||||
        return this.CanValidateUri(songUri);
 | 
			
		||||
        return Task.FromResult(this.CanValidateUri(songUri));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public override async Task<Song> ValidateAsync(Uri songUri)
 | 
			
		||||
@@ -15,12 +15,12 @@ public class YoutubeMusicValidator : UriBasedSongValidatorBase
 | 
			
		||||
        var title = string.Empty;
 | 
			
		||||
        var artist = string.Empty;
 | 
			
		||||
 | 
			
		||||
        using(HttpClient httpClient = new HttpClient())
 | 
			
		||||
        using (HttpClient httpClient = new HttpClient())
 | 
			
		||||
        {
 | 
			
		||||
            var response = await httpClient.GetAsync(songUri);
 | 
			
		||||
            var config = Configuration.Default.WithDefaultLoader();
 | 
			
		||||
            var context = BrowsingContext.New(config);
 | 
			
		||||
            using(var document = await context.OpenAsync(async req => req.Content(await response.Content.ReadAsStringAsync())))
 | 
			
		||||
            using (var document = await context.OpenAsync(async req => req.Content(await response.Content.ReadAsStringAsync())))
 | 
			
		||||
            {
 | 
			
		||||
                // document.getElementsByTagName("ytmusic-player-queue-item")[0].getElementsByClassName("song-title")[0].innerHTML
 | 
			
		||||
                title = document.QuerySelector(".ytmusic-player-queue-item")?.QuerySelector(".song-title")?.InnerHtml;
 | 
			
		||||
@@ -28,7 +28,9 @@ public class YoutubeMusicValidator : UriBasedSongValidatorBase
 | 
			
		||||
                artist = document.QuerySelector(".ytmusic-player-queue-item")?.QuerySelector(".byline")?.InnerHtml;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
#pragma warning disable CS8604 // Possible null reference argument.
 | 
			
		||||
#pragma warning disable CS8604 // Possible null reference argument.
 | 
			
		||||
        var song = new Song
 | 
			
		||||
        {
 | 
			
		||||
            Name = title,
 | 
			
		||||
@@ -37,6 +39,8 @@ public class YoutubeMusicValidator : UriBasedSongValidatorBase
 | 
			
		||||
            Provider = SongProvider.YouTube,
 | 
			
		||||
            SpotifyId = this.LookupSpotifyId(title, artist)
 | 
			
		||||
        };
 | 
			
		||||
#pragma warning restore CS8604 // Possible null reference argument.
 | 
			
		||||
#pragma warning restore CS8604 // Possible null reference argument.
 | 
			
		||||
 | 
			
		||||
        return song;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -8,14 +8,16 @@ public class YoutubeValidator : UriBasedSongValidatorBase
 | 
			
		||||
 | 
			
		||||
    public override async Task<bool> CanExtractSongMetadataAsync(Uri songUri)
 | 
			
		||||
    {
 | 
			
		||||
        using(HttpClient httpClient = new HttpClient())
 | 
			
		||||
        using (HttpClient httpClient = new HttpClient())
 | 
			
		||||
        {
 | 
			
		||||
            var response = await httpClient.GetAsync(songUri);
 | 
			
		||||
            var config = Configuration.Default.WithDefaultLoader();
 | 
			
		||||
            var context = BrowsingContext.New(config);
 | 
			
		||||
            using(var document = await context.OpenAsync(async req => req.Content(await response.Content.ReadAsStringAsync())))
 | 
			
		||||
            using (var document = await context.OpenAsync(async req => req.Content(await response.Content.ReadAsStringAsync())))
 | 
			
		||||
            {
 | 
			
		||||
#pragma warning disable CS8602 // Dereference of a possibly null reference.
 | 
			
		||||
                var documentContents = (document.ChildNodes[1] as HtmlElement).InnerHtml;
 | 
			
		||||
#pragma warning restore CS8602 // Dereference of a possibly null reference.
 | 
			
		||||
                var titleElement = document.QuerySelectorAll(".yt-video-attribute-view-model__title")[0];
 | 
			
		||||
                var artistParentElement = document.QuerySelectorAll(".yt-video-attribute-view-model__secondary-subtitle")[0];
 | 
			
		||||
 | 
			
		||||
@@ -29,18 +31,20 @@ public class YoutubeValidator : UriBasedSongValidatorBase
 | 
			
		||||
        var title = string.Empty;
 | 
			
		||||
        var artist = string.Empty;
 | 
			
		||||
 | 
			
		||||
        using(HttpClient httpClient = new HttpClient())
 | 
			
		||||
        using (HttpClient httpClient = new HttpClient())
 | 
			
		||||
        {
 | 
			
		||||
            var response = await httpClient.GetAsync(songUri);
 | 
			
		||||
            var config = Configuration.Default.WithDefaultLoader();
 | 
			
		||||
            var context = BrowsingContext.New(config);
 | 
			
		||||
            using(var document = await context.OpenAsync(async req => req.Content(await response.Content.ReadAsStringAsync())))
 | 
			
		||||
            using (var document = await context.OpenAsync(async req => req.Content(await response.Content.ReadAsStringAsync())))
 | 
			
		||||
            {
 | 
			
		||||
                title = document.QuerySelectorAll(".yt-video-attribute-view-model__title")[0]?.InnerHtml;
 | 
			
		||||
                artist = document.QuerySelectorAll(".yt-video-attribute-view-model__secondary-subtitle")[0]?.Children[0]?.InnerHtml;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
#pragma warning disable CS8604 // Possible null reference argument.
 | 
			
		||||
#pragma warning disable CS8604 // Possible null reference argument.
 | 
			
		||||
        var song = new Song
 | 
			
		||||
        {
 | 
			
		||||
            Name = title,
 | 
			
		||||
@@ -49,6 +53,8 @@ public class YoutubeValidator : UriBasedSongValidatorBase
 | 
			
		||||
            Provider = SongProvider.YouTube,
 | 
			
		||||
            SpotifyId = this.LookupSpotifyId(title, artist)
 | 
			
		||||
        };
 | 
			
		||||
#pragma warning restore CS8604 // Possible null reference argument.
 | 
			
		||||
#pragma warning restore CS8604 // Possible null reference argument.
 | 
			
		||||
 | 
			
		||||
        return song;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@ public class SpotifyApiClient
 | 
			
		||||
    {
 | 
			
		||||
        var config = SpotifyClientConfig.CreateDefault()
 | 
			
		||||
            .WithAuthenticator(new ClientCredentialsAuthenticator(
 | 
			
		||||
                AppConfiguration.Instance.SpotifyClientId, 
 | 
			
		||||
                AppConfiguration.Instance.SpotifyClientId,
 | 
			
		||||
                AppConfiguration.Instance.SpotifyClientSecret));
 | 
			
		||||
 | 
			
		||||
        _spotifyClient = new SpotifyClient(config);
 | 
			
		||||
 
 | 
			
		||||
@@ -2,8 +2,11 @@
 | 
			
		||||
  "DetailedErrors": true,
 | 
			
		||||
  "Logging": {
 | 
			
		||||
    "LogLevel": {
 | 
			
		||||
      "Default": "Information",
 | 
			
		||||
      "Microsoft.AspNetCore": "Warning"
 | 
			
		||||
      "Default": "Trace",
 | 
			
		||||
      "Microsoft.AspNetCore": "Warning",
 | 
			
		||||
      "SongResolver": "Trace",
 | 
			
		||||
      "SignalIntegration": "Trace",
 | 
			
		||||
      "SongOfTheDay": "Trace"
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,11 @@
 | 
			
		||||
{
 | 
			
		||||
  "Logging": {
 | 
			
		||||
    "LogLevel": {
 | 
			
		||||
      "Default": "Information",
 | 
			
		||||
      "Microsoft.AspNetCore": "Warning"
 | 
			
		||||
      "Default": "Trace",
 | 
			
		||||
      "Microsoft.AspNetCore": "Warning",
 | 
			
		||||
      "SongResolver": "Information",
 | 
			
		||||
      "SignalIntegration": "Information",
 | 
			
		||||
      "SongOfTheDay": "Trace"
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  "AllowedHosts": "*"
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user