fix: some cleanup and fixing runtime bugs, refs NOISSUE
This commit is contained in:
		@@ -9,7 +9,7 @@ RUN apt update && apt install libldap-2.5-0 -y
 | 
				
			|||||||
# Restore as distinct layers
 | 
					# Restore as distinct layers
 | 
				
			||||||
RUN dotnet restore ./song_of_the_day/song_of_the_day.csproj
 | 
					RUN dotnet restore ./song_of_the_day/song_of_the_day.csproj
 | 
				
			||||||
# Build and publish a release
 | 
					# Build and publish a release
 | 
				
			||||||
RUN dotnet publish ./song_of_the_day/song_of_the_day.csproj -o out
 | 
					RUN dotnet publish ./song_of_the_day/song_of_the_day.csproj -o out /p:EnvironmentName=Production
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Build runtime image
 | 
					# Build runtime image
 | 
				
			||||||
FROM mcr.microsoft.com/dotnet/aspnet:9.0
 | 
					FROM mcr.microsoft.com/dotnet/aspnet:9.0
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										2
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								Makefile
									
									
									
									
									
								
							@@ -2,7 +2,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
.PHONY: issetup
 | 
					.PHONY: issetup
 | 
				
			||||||
issetup:
 | 
					issetup:
 | 
				
			||||||
	@[ -f .git/hooks/commit-msg ] || [ $SKIP_MAKE_SETUP_CHECK = "true" ] || (echo "You must run 'make setup' first to initialize the repo!" && exit 1)
 | 
						@[ -f .git/hooks/commit-msg ] || [ ${SKIP_MAKE_SETUP_CHECK} = "true" ] || (echo "You must run 'make setup' first to initialize the repo!" && exit 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.PHONY: setup
 | 
					.PHONY: setup
 | 
				
			||||||
setup:
 | 
					setup:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,3 +2,5 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
# CS8981: The type name only contains lower-cased ascii characters. Such names may become reserved for the language.
 | 
					# 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.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
 | 
					public class LdapAuthenticationService : IAuthenticationService
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    private readonly IConfiguration _configuration;
 | 
					    private readonly IConfiguration _configuration;
 | 
				
			||||||
 | 
					    private LdapIntegration _ldapIntegration;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public LdapAuthenticationService(IConfiguration configuration)
 | 
					    public LdapAuthenticationService(IConfiguration configuration, LdapIntegration ldapIntegration)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        _configuration = configuration;
 | 
					        _configuration = configuration;
 | 
				
			||||||
 | 
					        _ldapIntegration = ldapIntegration;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public bool Authenticate(string username, string password)
 | 
					    public bool Authenticate(string username, string password)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var ldapInstance = LdapIntegration.Instance;
 | 
					        return _ldapIntegration == null ? false : _ldapIntegration.TestLogin(username, password);
 | 
				
			||||||
        return ldapInstance == null ? false : ldapInstance.TestLogin(username, password);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -2,11 +2,13 @@ public class PhoneClaimCodeProviderService
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
    private Dictionary<string, string> _phoneClaimCodes;
 | 
					    private Dictionary<string, string> _phoneClaimCodes;
 | 
				
			||||||
    private Dictionary<string, string> _phoneClaimNumbers;
 | 
					    private Dictionary<string, string> _phoneClaimNumbers;
 | 
				
			||||||
 | 
					    private SignalIntegration _signalIntegration;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public PhoneClaimCodeProviderService()
 | 
					    public PhoneClaimCodeProviderService(SignalIntegration signalIntegration)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        _phoneClaimCodes = new Dictionary<string, string>();
 | 
					        _phoneClaimCodes = new Dictionary<string, string>();
 | 
				
			||||||
        _phoneClaimNumbers = new Dictionary<string, string>();
 | 
					        _phoneClaimNumbers = new Dictionary<string, string>();
 | 
				
			||||||
 | 
					        _signalIntegration = signalIntegration;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private static Random random = new Random();
 | 
					    private static Random random = new Random();
 | 
				
			||||||
@@ -32,7 +34,10 @@ public class PhoneClaimCodeProviderService
 | 
				
			|||||||
            _phoneClaimNumbers[username] = phoneNumber;
 | 
					            _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)
 | 
					    public string ValidateClaimCodeForUser(string code, string username)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,8 +6,6 @@ using System.Linq;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
public class LdapIntegration
 | 
					public class LdapIntegration
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    public static LdapIntegration? Instance;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private readonly string[] attributesToQuery = new string[]
 | 
					    private readonly string[] attributesToQuery = new string[]
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            "uid",
 | 
					            "uid",
 | 
				
			||||||
@@ -16,12 +14,13 @@ public class LdapIntegration
 | 
				
			|||||||
            "mail"
 | 
					            "mail"
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public LdapIntegration(string uri, int port, string adminBind, string adminPass)
 | 
					    public LdapIntegration(ILogger<LdapIntegration> logger)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        this.Uri = uri;
 | 
					        this.Uri = AppConfiguration.Instance.LDAPConfig.LDAPserver;
 | 
				
			||||||
        this.Port = port;
 | 
					        this.Port = AppConfiguration.Instance.LDAPConfig.Port;
 | 
				
			||||||
        this.AdminBind = adminBind;
 | 
					        this.AdminBind = AppConfiguration.Instance.LDAPConfig.Username;
 | 
				
			||||||
        this.AdminPass = adminPass;
 | 
					        this.AdminPass = AppConfiguration.Instance.LDAPConfig.Password;
 | 
				
			||||||
 | 
					        this.logger = logger;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private string Uri { get; set; }
 | 
					    private string Uri { get; set; }
 | 
				
			||||||
@@ -32,6 +31,8 @@ public class LdapIntegration
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    private string AdminPass { get; set; }
 | 
					    private string AdminPass { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private ILogger<LdapIntegration> logger { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public bool TestLogin(string username, string password)
 | 
					    public bool TestLogin(string username, string password)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        try
 | 
					        try
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,29 +1,28 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
using System.Collections;
 | 
					 | 
				
			||||||
using System.ComponentModel;
 | 
					 | 
				
			||||||
using song_of_the_day;
 | 
					using song_of_the_day;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public class SignalIntegration
 | 
					public class SignalIntegration
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    public static SignalIntegration? Instance;
 | 
					    private readonly ILogger<SignalIntegration> logger;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private readonly ILogger logger;
 | 
					    public SignalIntegration(ILogger<SignalIntegration> logger)
 | 
				
			||||||
 | 
					 | 
				
			||||||
    public SignalIntegration(string uri, int port, string phoneNumber)
 | 
					 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        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()
 | 
					        var http = new HttpClient()
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            BaseAddress = new Uri(uri + ":" + port)
 | 
					            BaseAddress = new Uri(uri + ":" + port)
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
        apiClient = new song_of_the_day.swaggerClient(http);
 | 
					        apiClient = new swaggerClient(http);
 | 
				
			||||||
        apiClient.BaseUrl = "";
 | 
					        apiClient.BaseUrl = "";
 | 
				
			||||||
        this.phoneNumber = phoneNumber;
 | 
					        this.phoneNumber = phoneNumber;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private song_of_the_day.swaggerClient apiClient;
 | 
					    private swaggerClient apiClient;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private string phoneNumber;
 | 
					    private string phoneNumber;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -83,6 +82,11 @@ public class SignalIntegration
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    public async Task IntroduceUserAsync(User user)
 | 
					    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("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("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);
 | 
					        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)
 | 
					        using (var dci = DataContext.Instance)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var user = dci.Users.Where(u => u.LdapUserName == User.Identity.Name);
 | 
					            var user = dci.Users?.Where(u => u.LdapUserName == User.Identity.Name);
 | 
				
			||||||
            return user.Any();
 | 
					            return user == null ? false : user.Any();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -18,7 +18,7 @@
 | 
				
			|||||||
    <script type="importmap"></script>
 | 
					    <script type="importmap"></script>
 | 
				
			||||||
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
 | 
					    <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="~/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>
 | 
					</head>
 | 
				
			||||||
<body>
 | 
					<body>
 | 
				
			||||||
    <header>
 | 
					    <header>
 | 
				
			||||||
@@ -37,13 +37,15 @@
 | 
				
			|||||||
                        <li class="nav-item">
 | 
					                        <li class="nav-item">
 | 
				
			||||||
                            <a class="nav-link text-dark" asp-area="" asp-page="/SuggestionHelpers">Suggestion Helpers</a>
 | 
					                            <a class="nav-link text-dark" asp-area="" asp-page="/SuggestionHelpers">Suggestion Helpers</a>
 | 
				
			||||||
                        </li>
 | 
					                        </li>
 | 
				
			||||||
                        @if (this.User.Identity.IsAuthenticated && !DoesUserHaveClaimedPhoneNumber())
 | 
					                        @if (this.User != null && this.User.Identity != null &&
 | 
				
			||||||
 | 
					                            this.User.Identity.IsAuthenticated && !DoesUserHaveClaimedPhoneNumber())
 | 
				
			||||||
                        {
 | 
					                        {
 | 
				
			||||||
                            <li class="nav-item">
 | 
					                            <li class="nav-item">
 | 
				
			||||||
                                <a class="nav-link text-dark" asp-area="" asp-page="/UnclaimedPhoneNumbers">Unclaimed Phone Numbers</a>
 | 
					                                <a class="nav-link text-dark" asp-area="" asp-page="/UnclaimedPhoneNumbers">Unclaimed Phone Numbers</a>
 | 
				
			||||||
                            </li>
 | 
					                            </li>
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                        @if (this.User.Identity.IsAuthenticated && DoesUserHaveClaimedPhoneNumber())
 | 
					                        @if (this.User != null && this.User.Identity != null &&
 | 
				
			||||||
 | 
					                            this.User.Identity.IsAuthenticated && DoesUserHaveClaimedPhoneNumber())
 | 
				
			||||||
                        {
 | 
					                        {
 | 
				
			||||||
                            <li class="nav-item">
 | 
					                            <li class="nav-item">
 | 
				
			||||||
                                <a class="nav-link text-dark" asp-area="" asp-page="/SubmitSongs">Submit Songs</a>
 | 
					                                <a class="nav-link text-dark" asp-area="" asp-page="/SubmitSongs">Submit Songs</a>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
@using Microsoft.AspNetCore.Authentication
 | 
					@using Microsoft.AspNetCore.Authentication
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<div class="loginform">
 | 
					<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">
 | 
					        <form method="post" action="Auth/Login">
 | 
				
			||||||
            <div>
 | 
					            <div>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,20 +17,26 @@ public class SubmitSongsModel : PageModel
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
        _logger = logger;
 | 
					        _logger = logger;
 | 
				
			||||||
        this.songResolver = songResolver;
 | 
					        this.songResolver = songResolver;
 | 
				
			||||||
 | 
					        _submitUrl = string.Empty;
 | 
				
			||||||
 | 
					        SongData = new Song();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [BindProperty]
 | 
					    [BindProperty]
 | 
				
			||||||
    public bool IsValidUrl { get; set; } = true;
 | 
					    public bool IsValidUrl { get; set; } = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [BindProperty]
 | 
					    [BindProperty]
 | 
				
			||||||
    public string SubmitUrl { 
 | 
					    public string SubmitUrl
 | 
				
			||||||
        get {
 | 
					    {
 | 
				
			||||||
 | 
					        get
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
            return _submitUrl;
 | 
					            return _submitUrl;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        set {
 | 
					        set
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
            _submitUrl = value.ToString();
 | 
					            _submitUrl = value.ToString();
 | 
				
			||||||
            Uri? newValue = default;
 | 
					            Uri? newValue = default;
 | 
				
			||||||
            try {
 | 
					            try
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
                newValue = new Uri(_submitUrl);
 | 
					                newValue = new Uri(_submitUrl);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            catch (UriFormatException)
 | 
					            catch (UriFormatException)
 | 
				
			||||||
@@ -41,7 +47,7 @@ public class SubmitSongsModel : PageModel
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            IsValidUrl = true;
 | 
					            IsValidUrl = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if(this.songResolver.CanValidate(newValue))
 | 
					            if (this.songResolver.CanValidate(newValue))
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                this.SongData = this.songResolver.ResolveSongAsync(newValue).Result;
 | 
					                this.SongData = this.songResolver.ResolveSongAsync(newValue).Result;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@@ -49,9 +55,13 @@ public class SubmitSongsModel : PageModel
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [BindProperty]
 | 
					    [BindProperty]
 | 
				
			||||||
    public bool CanSubmit { get {
 | 
					    public bool CanSubmit
 | 
				
			||||||
        return !string.IsNullOrEmpty(SongData?.Artist) && ! string.IsNullOrEmpty(SongData?.Name);
 | 
					    {
 | 
				
			||||||
    } }
 | 
					        get
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return !string.IsNullOrEmpty(SongData?.Artist) && !string.IsNullOrEmpty(SongData?.Name);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [BindProperty]
 | 
					    [BindProperty]
 | 
				
			||||||
    public Song SongData { get; set; }
 | 
					    public Song SongData { get; set; }
 | 
				
			||||||
@@ -66,6 +76,6 @@ public class SubmitSongsModel : PageModel
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
        var songUrl = Request.Query["SubmitUrl"];
 | 
					        var songUrl = Request.Query["SubmitUrl"];
 | 
				
			||||||
        this.SubmitUrl = songUrl.ToString();
 | 
					        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)
 | 
					        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,
 | 
					                Title = this.NewSuggestionTitle,
 | 
				
			||||||
                Description = this.NewSuggestionDescription
 | 
					                Description = this.NewSuggestionDescription
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
            dci.SuggestionHelpers.Add(newHelper);
 | 
					            dci.SuggestionHelpers?.Add(newHelper);
 | 
				
			||||||
            dci.SaveChanges();
 | 
					            dci.SaveChanges();
 | 
				
			||||||
            this.SuggestionHelpers = dci.SuggestionHelpers.ToList();
 | 
					            this.SuggestionHelpers = dci.SuggestionHelpers == null ? new List<SuggestionHelper>() : dci.SuggestionHelpers.ToList();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        this.NewSuggestionDescription = "";
 | 
					        this.NewSuggestionDescription = "";
 | 
				
			||||||
        this.NewSuggestionTitle = "";
 | 
					        this.NewSuggestionTitle = "";
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
using Microsoft.AspNetCore.Mvc;
 | 
					using Microsoft.AspNetCore.Mvc;
 | 
				
			||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
 | 
					using Microsoft.AspNetCore.Mvc.RazorPages;
 | 
				
			||||||
using Microsoft.VisualBasic;
 | 
					using Microsoft.VisualBasic;
 | 
				
			||||||
 | 
					using SpotifyAPI.Web;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace sotd.Pages;
 | 
					namespace sotd.Pages;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -11,6 +12,7 @@ public class UnclaimedPhoneNumbersModel : PageModel
 | 
				
			|||||||
    public UnclaimedPhoneNumbersModel(ILogger<UserModel> logger)
 | 
					    public UnclaimedPhoneNumbersModel(ILogger<UserModel> logger)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        _logger = logger;
 | 
					        _logger = logger;
 | 
				
			||||||
 | 
					        UnclaimedUsers = new List<User>();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public int userId { get; set; }
 | 
					    public int userId { get; set; }
 | 
				
			||||||
@@ -22,7 +24,7 @@ public class UnclaimedPhoneNumbersModel : PageModel
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
        using (var dci = DataContext.Instance)
 | 
					        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)
 | 
					        using (var dci = DataContext.Instance)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var user = dci.Users.Find(userIndex);
 | 
					            var user = dci.Users?.Find(userIndex);
 | 
				
			||||||
            this.UserName = user.Name;
 | 
					            this.UserName = user == null ? string.Empty : (user.Name ?? string.Empty);
 | 
				
			||||||
            this.UserNickName = user.NickName;
 | 
					            this.UserNickName = user == null ? string.Empty : (user.NickName ?? string.Empty);
 | 
				
			||||||
            this.userId = userIndex;
 | 
					            this.userId = userIndex;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -36,10 +36,13 @@ public class UserModel : PageModel
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
        using (var dci = DataContext.Instance)
 | 
					        using (var dci = DataContext.Instance)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var user = dci.Users.Find(userIndex);
 | 
					            var user = dci.Users?.Find(userIndex);
 | 
				
			||||||
 | 
					            if (user != null)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
                user.NickName = this.UserNickName;
 | 
					                user.NickName = this.UserNickName;
 | 
				
			||||||
                dci.SaveChanges();
 | 
					                dci.SaveChanges();
 | 
				
			||||||
            this.UserName = user.Name;
 | 
					                this.UserName = user.Name ?? string.Empty;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,31 +1,37 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
using Scalar.AspNetCore;
 | 
					using Scalar.AspNetCore;
 | 
				
			||||||
using Microsoft.AspNetCore.OpenApi;
 | 
					 | 
				
			||||||
using Microsoft.EntityFrameworkCore;
 | 
					using Microsoft.EntityFrameworkCore;
 | 
				
			||||||
using Microsoft.AspNetCore.Authentication;
 | 
					 | 
				
			||||||
using Microsoft.AspNetCore.Authentication.Cookies;
 | 
					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);
 | 
					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");
 | 
					logger.LogTrace("Setting up user check timer");
 | 
				
			||||||
var userCheckTimer = new CronTimer("*/1 * * * *", "Europe/Vienna", includingSeconds: false);
 | 
					var userCheckTimer = new CronTimer("*/1 * * * *", "Europe/Vienna", includingSeconds: false);
 | 
				
			||||||
userCheckTimer.OnOccurence += async (s, ea) =>
 | 
					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 dci = DataContext.Instance;
 | 
				
			||||||
    var needsSaving = false;
 | 
					    var needsSaving = false;
 | 
				
			||||||
    foreach (var memberId in memberList)
 | 
					    foreach (var memberId in memberList)
 | 
				
			||||||
@@ -33,7 +39,7 @@ userCheckTimer.OnOccurence += async (s, ea) =>
 | 
				
			|||||||
        var foundUser = dci.Users?.Where(u => u.SignalMemberId == memberId).SingleOrDefault();
 | 
					        var foundUser = dci.Users?.Where(u => u.SignalMemberId == memberId).SingleOrDefault();
 | 
				
			||||||
        if (foundUser == null)
 | 
					        if (foundUser == null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var newUserContact = await SignalIntegration.Instance.GetContactAsync(memberId);
 | 
					            var newUserContact = await signalIntegration.GetContactAsync(memberId);
 | 
				
			||||||
            logger.LogDebug("New user:");
 | 
					            logger.LogDebug("New user:");
 | 
				
			||||||
            logger.LogDebug($"   Name: {newUserContact.Name}");
 | 
					            logger.LogDebug($"   Name: {newUserContact.Name}");
 | 
				
			||||||
            logger.LogDebug($"   MemberId: {memberId}");
 | 
					            logger.LogDebug($"   MemberId: {memberId}");
 | 
				
			||||||
@@ -74,7 +80,8 @@ userIntroTimer.OnOccurence += async (s, ea) =>
 | 
				
			|||||||
    bool needsSaving = false;
 | 
					    bool needsSaving = false;
 | 
				
			||||||
    foreach (var user in introUsers)
 | 
					    foreach (var user in introUsers)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        await SignalIntegration.Instance.IntroduceUserAsync(user);
 | 
					        var signalIntegration = app.Services.GetService<SignalIntegration>();
 | 
				
			||||||
 | 
					        await signalIntegration.IntroduceUserAsync(user);
 | 
				
			||||||
        user.IsIntroduced = true;
 | 
					        user.IsIntroduced = true;
 | 
				
			||||||
        needsSaving = true;
 | 
					        needsSaving = true;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -137,12 +144,14 @@ pickOfTheDayTimer.OnOccurence += async (s, ea) =>
 | 
				
			|||||||
    if (luckyUser.SignalMemberId is string signalId)
 | 
					    if (luckyUser.SignalMemberId is string signalId)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        await dci.SongSuggestions.AddAsync(newSongSuggestion);
 | 
					        await dci.SongSuggestions.AddAsync(newSongSuggestion);
 | 
				
			||||||
 | 
					        luckyUser.WasChosenForSuggestionThisRound = true;
 | 
				
			||||||
        await dci.SaveChangesAsync();
 | 
					        await dci.SaveChangesAsync();
 | 
				
			||||||
        await SignalIntegration.Instance.SendMessageToGroupAsync($"Today's chosen person to share a song is: **{userName}**");
 | 
					        var signalIntegration = app.Services.GetService<SignalIntegration>();
 | 
				
			||||||
        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.SendMessageToGroupAsync($"Today's chosen person to share a song is: **{userName}**");
 | 
				
			||||||
        await SignalIntegration.Instance.SendMessageToUserAsync($"Congratulations, you have been chosen to share a song today!", signalId);
 | 
					        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.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.SendMessageToUserAsync($"Congratulations, you have been chosen to share a song today!", 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);
 | 
					        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();
 | 
					    await dci.DisposeAsync();
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
@@ -151,10 +160,11 @@ var startUserAssociationProcess = async (User userToAssociate) =>
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
    if (userToAssociate.SignalMemberId is string signalId)
 | 
					    if (userToAssociate.SignalMemberId is string signalId)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        await SignalIntegration.Instance.SendMessageToUserAsync($"Hi, I see you are not associated with any website user yet.", signalId);
 | 
					        var signalIntegration = app.Services.GetService<SignalIntegration>();
 | 
				
			||||||
        await SignalIntegration.Instance.SendMessageToUserAsync($"If you haven't yet, please navigate to https://users.disi.dev to create a new account.", signalId);
 | 
					        await signalIntegration.SendMessageToUserAsync($"Hi, I see you are not associated with any website user yet.", 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.SendMessageToUserAsync($"If you haven't yet, please navigate to https://users.disi.dev to create a new account.", 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);
 | 
					        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();
 | 
					    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
 | 
					// only start interaction timers in production builds
 | 
				
			||||||
// for local/development testing we want those disabled
 | 
					// for local/development testing we want those disabled
 | 
				
			||||||
if (!app.Environment.IsDevelopment())
 | 
					if (!app.Environment.IsDevelopment())
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 | 
					    logger.LogTrace("Starting timer for scheduled processes.");
 | 
				
			||||||
    userCheckTimer.Start();
 | 
					    userCheckTimer.Start();
 | 
				
			||||||
    userIntroTimer.Start();
 | 
					    userIntroTimer.Start();
 | 
				
			||||||
    pickOfTheDayTimer.Start();
 | 
					    pickOfTheDayTimer.Start();
 | 
				
			||||||
    ldapAssociationTimer.Start();
 | 
					    ldapAssociationTimer.Start();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					else
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    logger.LogTrace("This is a debug build - scheduled processes are disabled.");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Configure the HTTP request pipeline.
 | 
					// Configure the HTTP request pipeline.
 | 
				
			||||||
if (!app.Environment.IsDevelopment())
 | 
					if (!app.Environment.IsDevelopment())
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,17 +4,17 @@ public class SongResolver
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
    private readonly IEnumerable<ISongValidator> _songValidators;
 | 
					    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 = logger;
 | 
				
			||||||
        this.logger = factory.CreateLogger("SongResolver");
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this._songValidators = new List<ISongValidator>();
 | 
					        this._songValidators = new List<ISongValidator>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        foreach (Type mytype in System.Reflection.Assembly.GetExecutingAssembly().GetTypes()
 | 
					        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)
 | 
					            if (Activator.CreateInstance(mytype) is ISongValidator validator)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                logger.LogDebug("Registering song validator: {ValidatorType}", mytype.Name);
 | 
					                logger.LogDebug("Registering song validator: {ValidatorType}", mytype.Name);
 | 
				
			||||||
@@ -32,7 +32,8 @@ public class SongResolver
 | 
				
			|||||||
                if (!await validator.CanExtractSongMetadataAsync(songUri))
 | 
					                if (!await validator.CanExtractSongMetadataAsync(songUri))
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    this.logger.LogWarning("Cannot extract metadata for song URI: {SongUri}", songUri);
 | 
					                    this.logger.LogWarning("Cannot extract metadata for song URI: {SongUri}", songUri);
 | 
				
			||||||
                    return new Song {
 | 
					                    return new Song
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
                        Artist = "Unknown Artist",
 | 
					                        Artist = "Unknown Artist",
 | 
				
			||||||
                        Name = "Unknown Title",
 | 
					                        Name = "Unknown Title",
 | 
				
			||||||
                        Url = songUri.ToString(),
 | 
					                        Url = songUri.ToString(),
 | 
				
			||||||
@@ -43,7 +44,8 @@ public class SongResolver
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return new Song {
 | 
					        return new Song
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
            Artist = "Unknown Artist",
 | 
					            Artist = "Unknown Artist",
 | 
				
			||||||
            Name = "Unknown Title",
 | 
					            Name = "Unknown Title",
 | 
				
			||||||
            Url = songUri.ToString(),
 | 
					            Url = songUri.ToString(),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,9 +14,9 @@ public class SpotifyValidator : UriBasedSongValidatorBase
 | 
				
			|||||||
        spotifyApiClient = new SpotifyApiClient();
 | 
					        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)
 | 
					    public override async Task<Song> ValidateAsync(Uri songUri)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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 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)
 | 
					    public override async Task<Song> ValidateAsync(Uri songUri)
 | 
				
			||||||
@@ -15,12 +15,12 @@ public class YoutubeMusicValidator : UriBasedSongValidatorBase
 | 
				
			|||||||
        var title = string.Empty;
 | 
					        var title = string.Empty;
 | 
				
			||||||
        var artist = string.Empty;
 | 
					        var artist = string.Empty;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        using(HttpClient httpClient = new HttpClient())
 | 
					        using (HttpClient httpClient = new HttpClient())
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var response = await httpClient.GetAsync(songUri);
 | 
					            var response = await httpClient.GetAsync(songUri);
 | 
				
			||||||
            var config = Configuration.Default.WithDefaultLoader();
 | 
					            var config = Configuration.Default.WithDefaultLoader();
 | 
				
			||||||
            var context = BrowsingContext.New(config);
 | 
					            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
 | 
					                // document.getElementsByTagName("ytmusic-player-queue-item")[0].getElementsByClassName("song-title")[0].innerHTML
 | 
				
			||||||
                title = document.QuerySelector(".ytmusic-player-queue-item")?.QuerySelector(".song-title")?.InnerHtml;
 | 
					                title = document.QuerySelector(".ytmusic-player-queue-item")?.QuerySelector(".song-title")?.InnerHtml;
 | 
				
			||||||
@@ -29,6 +29,8 @@ public class YoutubeMusicValidator : UriBasedSongValidatorBase
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#pragma warning disable CS8604 // Possible null reference argument.
 | 
				
			||||||
 | 
					#pragma warning disable CS8604 // Possible null reference argument.
 | 
				
			||||||
        var song = new Song
 | 
					        var song = new Song
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            Name = title,
 | 
					            Name = title,
 | 
				
			||||||
@@ -37,6 +39,8 @@ public class YoutubeMusicValidator : UriBasedSongValidatorBase
 | 
				
			|||||||
            Provider = SongProvider.YouTube,
 | 
					            Provider = SongProvider.YouTube,
 | 
				
			||||||
            SpotifyId = this.LookupSpotifyId(title, artist)
 | 
					            SpotifyId = this.LookupSpotifyId(title, artist)
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					#pragma warning restore CS8604 // Possible null reference argument.
 | 
				
			||||||
 | 
					#pragma warning restore CS8604 // Possible null reference argument.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return song;
 | 
					        return song;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,14 +8,16 @@ public class YoutubeValidator : UriBasedSongValidatorBase
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    public override async Task<bool> CanExtractSongMetadataAsync(Uri songUri)
 | 
					    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 response = await httpClient.GetAsync(songUri);
 | 
				
			||||||
            var config = Configuration.Default.WithDefaultLoader();
 | 
					            var config = Configuration.Default.WithDefaultLoader();
 | 
				
			||||||
            var context = BrowsingContext.New(config);
 | 
					            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;
 | 
					                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 titleElement = document.QuerySelectorAll(".yt-video-attribute-view-model__title")[0];
 | 
				
			||||||
                var artistParentElement = document.QuerySelectorAll(".yt-video-attribute-view-model__secondary-subtitle")[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 title = string.Empty;
 | 
				
			||||||
        var artist = string.Empty;
 | 
					        var artist = string.Empty;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        using(HttpClient httpClient = new HttpClient())
 | 
					        using (HttpClient httpClient = new HttpClient())
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var response = await httpClient.GetAsync(songUri);
 | 
					            var response = await httpClient.GetAsync(songUri);
 | 
				
			||||||
            var config = Configuration.Default.WithDefaultLoader();
 | 
					            var config = Configuration.Default.WithDefaultLoader();
 | 
				
			||||||
            var context = BrowsingContext.New(config);
 | 
					            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;
 | 
					                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;
 | 
					                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
 | 
					        var song = new Song
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            Name = title,
 | 
					            Name = title,
 | 
				
			||||||
@@ -49,6 +53,8 @@ public class YoutubeValidator : UriBasedSongValidatorBase
 | 
				
			|||||||
            Provider = SongProvider.YouTube,
 | 
					            Provider = SongProvider.YouTube,
 | 
				
			||||||
            SpotifyId = this.LookupSpotifyId(title, artist)
 | 
					            SpotifyId = this.LookupSpotifyId(title, artist)
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					#pragma warning restore CS8604 // Possible null reference argument.
 | 
				
			||||||
 | 
					#pragma warning restore CS8604 // Possible null reference argument.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return song;
 | 
					        return song;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,8 +2,11 @@
 | 
				
			|||||||
  "DetailedErrors": true,
 | 
					  "DetailedErrors": true,
 | 
				
			||||||
  "Logging": {
 | 
					  "Logging": {
 | 
				
			||||||
    "LogLevel": {
 | 
					    "LogLevel": {
 | 
				
			||||||
      "Default": "Information",
 | 
					      "Default": "Trace",
 | 
				
			||||||
      "Microsoft.AspNetCore": "Warning"
 | 
					      "Microsoft.AspNetCore": "Warning",
 | 
				
			||||||
 | 
					      "SongResolver": "Trace",
 | 
				
			||||||
 | 
					      "SignalIntegration": "Trace",
 | 
				
			||||||
 | 
					      "SongOfTheDay": "Trace"
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,11 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "Logging": {
 | 
					  "Logging": {
 | 
				
			||||||
    "LogLevel": {
 | 
					    "LogLevel": {
 | 
				
			||||||
      "Default": "Information",
 | 
					      "Default": "Trace",
 | 
				
			||||||
      "Microsoft.AspNetCore": "Warning"
 | 
					      "Microsoft.AspNetCore": "Warning",
 | 
				
			||||||
 | 
					      "SongResolver": "Information",
 | 
				
			||||||
 | 
					      "SignalIntegration": "Information",
 | 
				
			||||||
 | 
					      "SongOfTheDay": "Trace"
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "AllowedHosts": "*"
 | 
					  "AllowedHosts": "*"
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user