Compare commits
No commits in common. "main" and "0.3.2" have entirely different histories.
15
HISTORY.md
15
HISTORY.md
@ -5,22 +5,11 @@ Changelog
|
|||||||
(unreleased)
|
(unreleased)
|
||||||
------------
|
------------
|
||||||
|
|
||||||
Fix
|
|
||||||
~~~
|
|
||||||
- Save DateTime as UTC, refs NOISSUE. [Simon Diesenreiter]
|
|
||||||
|
|
||||||
|
|
||||||
0.3.2 (2025-05-25)
|
|
||||||
------------------
|
|
||||||
|
|
||||||
Fix
|
Fix
|
||||||
~~~
|
~~~
|
||||||
- Exception thrown on LastOrDefault(), refs NOISSUE. [Simon
|
- Exception thrown on LastOrDefault(), refs NOISSUE. [Simon
|
||||||
Diesenreiter]
|
Diesenreiter]
|
||||||
|
|
||||||
Other
|
|
||||||
~~~~~
|
|
||||||
|
|
||||||
|
|
||||||
0.3.1 (2025-05-24)
|
0.3.1 (2025-05-24)
|
||||||
------------------
|
------------------
|
||||||
@ -256,10 +245,6 @@ Other
|
|||||||
0.1.9 (2025-04-15)
|
0.1.9 (2025-04-15)
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
|
||||||
0.1.8 (2025-04-15)
|
|
||||||
------------------
|
|
||||||
|
|
||||||
Fix
|
Fix
|
||||||
~~~
|
~~~
|
||||||
- Additional debug outputs refs NOISSUE. [Simon Diesenreiter]
|
- Additional debug outputs refs NOISSUE. [Simon Diesenreiter]
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
[*.cs]
|
|
||||||
|
|
||||||
# CS8981: The type name only contains lower-cased ascii characters. Such names may become reserved for the language.
|
|
||||||
dotnet_diagnostic.CS8981.severity = none
|
|
@ -10,6 +10,6 @@ public class LdapAuthenticationService : IAuthenticationService
|
|||||||
public bool Authenticate(string username, string password)
|
public bool Authenticate(string username, string password)
|
||||||
{
|
{
|
||||||
var ldapInstance = LdapIntegration.Instance;
|
var ldapInstance = LdapIntegration.Instance;
|
||||||
return ldapInstance == null ? false : ldapInstance.TestLogin(username, password);
|
return ldapInstance.TestLogin(username, password);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -18,7 +18,7 @@ public class PhoneClaimCodeProviderService
|
|||||||
.Select(s => s[random.Next(s.Length)]).ToArray());
|
.Select(s => s[random.Next(s.Length)]).ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
public async void GenerateClaimCodeForUserAndNumber(string username, string phoneNumber)
|
public void GenerateClaimCodeForUserAndNumber(string username, string phoneNumber)
|
||||||
{
|
{
|
||||||
var generatedCode = string.Empty;
|
var generatedCode = string.Empty;
|
||||||
if (IsCodeGeneratedForUser(username))
|
if (IsCodeGeneratedForUser(username))
|
||||||
@ -32,7 +32,7 @@ public class PhoneClaimCodeProviderService
|
|||||||
_phoneClaimNumbers[username] = phoneNumber;
|
_phoneClaimNumbers[username] = phoneNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
await SignalIntegration.Instance.SendMessageToUserAsync("Your phone number validation code is: " + generatedCode, phoneNumber);
|
SignalIntegration.Instance.SendMessageToUserAsync("Your phone number validation code is: " + generatedCode, phoneNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string ValidateClaimCodeForUser(string code, string username)
|
public string ValidateClaimCodeForUser(string code, string username)
|
||||||
|
@ -20,8 +20,6 @@ public class AppConfiguration
|
|||||||
var managersGroupName = Environment.GetEnvironmentVariable("LDAP_ADMINGROUP") ?? "admins";
|
var managersGroupName = Environment.GetEnvironmentVariable("LDAP_ADMINGROUP") ?? "admins";
|
||||||
var userGroupName = Environment.GetEnvironmentVariable("LDAP_USERGROUP") ?? "everybody";
|
var userGroupName = Environment.GetEnvironmentVariable("LDAP_USERGROUP") ?? "everybody";
|
||||||
var bindValue = Environment.GetEnvironmentVariable("LDAP_BIND");
|
var bindValue = Environment.GetEnvironmentVariable("LDAP_BIND");
|
||||||
this.SpotifyClientId = Environment.GetEnvironmentVariable("SPOTIFY_CLIENT_ID") ?? "0c59b625470b4ad1b70743e0254d17fd";
|
|
||||||
this.SpotifyClientSecret = Environment.GetEnvironmentVariable("SPOTIFY_CLIENT_SECRET") ?? "04daaebd42fc47909c5cbd1f5cf23555";
|
|
||||||
this.LDAPConfig = new ConfigurationAD()
|
this.LDAPConfig = new ConfigurationAD()
|
||||||
{
|
{
|
||||||
Username = Environment.GetEnvironmentVariable("LDAP_BIND") ?? "cn=admin,dc=disi,dc=dev",
|
Username = Environment.GetEnvironmentVariable("LDAP_BIND") ?? "cn=admin,dc=disi,dc=dev",
|
||||||
@ -85,16 +83,6 @@ public class AppConfiguration
|
|||||||
get; private set;
|
get; private set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string SpotifyClientId
|
|
||||||
{
|
|
||||||
get; private set;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string SpotifyClientSecret
|
|
||||||
{
|
|
||||||
get; private set;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool UseBotTag
|
public bool UseBotTag
|
||||||
{
|
{
|
||||||
get; private set;
|
get; private set;
|
||||||
|
@ -10,7 +10,7 @@ public class AuthController : Controller
|
|||||||
public async Task<IActionResult> Login(string username, string password)
|
public async Task<IActionResult> Login(string username, string password)
|
||||||
{
|
{
|
||||||
var ldapService = HttpContext.RequestServices.GetService<LdapAuthenticationService>();
|
var ldapService = HttpContext.RequestServices.GetService<LdapAuthenticationService>();
|
||||||
if (ldapService != null && ldapService.Authenticate(username, password))
|
if (ldapService.Authenticate(username, password))
|
||||||
{
|
{
|
||||||
var claims = new[] { new Claim(ClaimTypes.Name, username) };
|
var claims = new[] { new Claim(ClaimTypes.Name, username) };
|
||||||
var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
|
var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
|
||||||
|
@ -6,6 +6,4 @@ public class Song
|
|||||||
public string? Name { get; set; }
|
public string? Name { get; set; }
|
||||||
public string? Artist { get; set; }
|
public string? Artist { get; set; }
|
||||||
public string? Url { get; set; }
|
public string? Url { get; set; }
|
||||||
public SongProvider? Provider { get; set; }
|
|
||||||
public string? SpotifyId { get; set; }
|
|
||||||
}
|
}
|
@ -1,11 +0,0 @@
|
|||||||
public enum SongProvider
|
|
||||||
{
|
|
||||||
Spotify,
|
|
||||||
YouTube,
|
|
||||||
YoutubeMusic,
|
|
||||||
SoundCloud,
|
|
||||||
Bandcamp,
|
|
||||||
PlainHttp,
|
|
||||||
NavidromeSharedLink,
|
|
||||||
Other
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
// This file is used by Code Analysis to maintain SuppressMessage
|
|
||||||
// attributes that are applied to this project.
|
|
||||||
// Project-level suppressions either have no target or are given
|
|
||||||
// a specific target and scoped to a namespace, type, member, etc.
|
|
||||||
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
|
|
||||||
[assembly: SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "<Pending>", Scope = "member", Target = "~P:sotd.Pages.UnclaimedPhoneNumbersModel.userId")]
|
|
||||||
[assembly: SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "<Pending>", Scope = "member", Target = "~P:sotd.Pages.UserModel.userId")]
|
|
||||||
[assembly: SuppressMessage("Compiler", "CS8981:The type name only contains lower-cased ascii characters. Such names may become reserved for the language.", Justification = "<Pending>", Scope = "type", Target = "~T:song_of_the_day.DataMigrations.additionaldataforsongsubmissions")]
|
|
||||||
[assembly: SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "<Pending>", Scope = "type", Target = "~T:song_of_the_day.DataMigrations.additionaldataforsongsubmissions")]
|
|
||||||
[assembly: SuppressMessage("Compiler", "CS8981:The type name only contains lower-cased ascii characters. Such names may become reserved for the language.", Justification = "<Pending>", Scope = "type", Target = "~T:song_of_the_day.DataMigrations.keeptrackofusersoickedforsubmission")]
|
|
||||||
[assembly: SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "<Pending>", Scope = "type", Target = "~T:song_of_the_day.DataMigrations.keeptrackofusersoickedforsubmission")]
|
|
@ -7,13 +7,8 @@ public class SignalIntegration
|
|||||||
{
|
{
|
||||||
public static SignalIntegration? Instance;
|
public static SignalIntegration? Instance;
|
||||||
|
|
||||||
private readonly ILogger logger;
|
|
||||||
|
|
||||||
public SignalIntegration(string uri, int port, string phoneNumber)
|
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 http = new HttpClient()
|
var http = new HttpClient()
|
||||||
{
|
{
|
||||||
BaseAddress = new Uri(uri + ":" + port)
|
BaseAddress = new Uri(uri + ":" + port)
|
||||||
@ -29,19 +24,17 @@ public class SignalIntegration
|
|||||||
|
|
||||||
public async Task ListGroupsAsync()
|
public async Task ListGroupsAsync()
|
||||||
{
|
{
|
||||||
logger.LogDebug("Listing all groups for phone number: {PhoneNumber}", this.phoneNumber);
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ICollection<song_of_the_day.GroupEntry> groupEntries = await apiClient.GroupsAllAsync(this.phoneNumber);
|
ICollection<song_of_the_day.GroupEntry> groupEntries = await apiClient.GroupsAllAsync(this.phoneNumber);
|
||||||
foreach (var group in groupEntries)
|
foreach (var group in groupEntries)
|
||||||
{
|
{
|
||||||
logger.LogDebug($" {group.Name} {group.Id}");
|
Console.WriteLine($"{group.Name} {group.Id}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
logger.LogError("Exception (ListGroupsAsync): " + ex.Message);
|
Console.WriteLine("Exception (ListGroupsAsync): " + ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,7 +52,7 @@ public class SignalIntegration
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
logger.LogError("Exception (SendMessageToGroupAsync): " + ex.Message);
|
Console.WriteLine("Exception (SendMessageToGroupAsync): " + ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,7 +70,7 @@ public class SignalIntegration
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
logger.LogError("Exception (SendMessageToUserAsync): " + ex.Message);
|
Console.WriteLine("Exception (SendMessageToUserAsync): " + ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,7 +97,7 @@ public class SignalIntegration
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
logger.LogError("Exception (GetMemberListAsync): " + ex.Message);
|
Console.WriteLine("Exception (GetMemberListAsync): " + ex.Message);
|
||||||
}
|
}
|
||||||
return new List<string>();
|
return new List<string>();
|
||||||
}
|
}
|
||||||
@ -124,7 +117,7 @@ public class SignalIntegration
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
logger.LogError("Exception (GetContactAsync): " + ex.Message);
|
Console.WriteLine("Exception (GetContactAsync): " + ex.Message);
|
||||||
return new ListContactsResponse();
|
return new ListContactsResponse();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,2 +0,0 @@
|
|||||||
@inherits Microsoft.AspNetCore.Components.Forms.InputText
|
|
||||||
<input @attributes="@AdditionalAttributes" class="@CssClass" @bind="@CurrentValueAsString" @bind:event="oninput" />
|
|
@ -43,12 +43,6 @@
|
|||||||
<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())
|
|
||||||
{
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link text-dark" asp-area="" asp-page="/SubmitSongs">Submit Songs</a>
|
|
||||||
</li>
|
|
||||||
}
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
@model Song
|
|
||||||
<label asp-for="Name">Name:</label>
|
|
||||||
<input asp-for="Name" />
|
|
||||||
<label asp-for="Artist">Artist:</label>
|
|
||||||
<input asp-for="Artist" />
|
|
||||||
<label asp-for="SpotifyId">Spotify ID:</label>
|
|
||||||
<input asp-for="SpotifyId" />
|
|
@ -1,25 +0,0 @@
|
|||||||
@page
|
|
||||||
@model SubmitSongsModel
|
|
||||||
@{
|
|
||||||
ViewData["Title"] = "Submit Songs";
|
|
||||||
}
|
|
||||||
|
|
||||||
<div class="text-left">
|
|
||||||
<form method="post">
|
|
||||||
<label asp-for="SubmitUrl" >Song Url:</label>
|
|
||||||
<input asp-for="SubmitUrl" oninput="Update(this)" />
|
|
||||||
</form>
|
|
||||||
<form method="post">
|
|
||||||
<div id="songdata">
|
|
||||||
<partial name="_SongPartial" model="@Model.SongData" />
|
|
||||||
</div>
|
|
||||||
<input type="submit" title="Submit" value="Submit" disabled="CanSubmit" />
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
function Update(t) {
|
|
||||||
var url = '?handler=Update&&SubmitUrl=' + $(t).val();
|
|
||||||
$('#songdata').load(url)
|
|
||||||
}
|
|
||||||
</script>
|
|
@ -1,71 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Components;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
|
||||||
using Microsoft.VisualBasic;
|
|
||||||
|
|
||||||
namespace sotd.Pages;
|
|
||||||
|
|
||||||
public class SubmitSongsModel : PageModel
|
|
||||||
{
|
|
||||||
private readonly ILogger<UserModel> _logger;
|
|
||||||
|
|
||||||
private SongResolver songResolver;
|
|
||||||
|
|
||||||
private string _submitUrl;
|
|
||||||
|
|
||||||
public SubmitSongsModel(ILogger<UserModel> logger, SongResolver songResolver)
|
|
||||||
{
|
|
||||||
_logger = logger;
|
|
||||||
this.songResolver = songResolver;
|
|
||||||
}
|
|
||||||
|
|
||||||
[BindProperty]
|
|
||||||
public bool IsValidUrl { get; set; } = true;
|
|
||||||
|
|
||||||
[BindProperty]
|
|
||||||
public string SubmitUrl {
|
|
||||||
get {
|
|
||||||
return _submitUrl;
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
_submitUrl = value.ToString();
|
|
||||||
Uri? newValue = default;
|
|
||||||
try {
|
|
||||||
newValue = new Uri(_submitUrl);
|
|
||||||
}
|
|
||||||
catch (UriFormatException)
|
|
||||||
{
|
|
||||||
IsValidUrl = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
IsValidUrl = true;
|
|
||||||
|
|
||||||
if(this.songResolver.CanValidate(newValue))
|
|
||||||
{
|
|
||||||
this.SongData = this.songResolver.ResolveSongAsync(newValue).Result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[BindProperty]
|
|
||||||
public bool CanSubmit { get {
|
|
||||||
return !string.IsNullOrEmpty(SongData?.Artist) && ! string.IsNullOrEmpty(SongData?.Name);
|
|
||||||
} }
|
|
||||||
|
|
||||||
[BindProperty]
|
|
||||||
public Song SongData { get; set; }
|
|
||||||
|
|
||||||
public void OnPost()
|
|
||||||
{
|
|
||||||
// Todo implement save submission
|
|
||||||
var x = SongData.Name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IActionResult OnGetUpdate()
|
|
||||||
{
|
|
||||||
var songUrl = Request.Query["SubmitUrl"];
|
|
||||||
this.SubmitUrl = songUrl.ToString();
|
|
||||||
return Partial("_SongPartial", SongData);;
|
|
||||||
}
|
|
||||||
}
|
|
@ -18,10 +18,7 @@ LdapIntegration.Instance = new LdapIntegration(AppConfiguration.Instance.LDAPCon
|
|||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
using ILoggerFactory factory = LoggerFactory.Create(builder => builder.AddConsole().SetMinimumLevel(LogLevel.Information));
|
Console.WriteLine("Setting up user check timer");
|
||||||
var logger = factory.CreateLogger("SongResolver");
|
|
||||||
|
|
||||||
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) =>
|
||||||
{
|
{
|
||||||
@ -34,9 +31,9 @@ userCheckTimer.OnOccurence += async (s, ea) =>
|
|||||||
if (foundUser == null)
|
if (foundUser == null)
|
||||||
{
|
{
|
||||||
var newUserContact = await SignalIntegration.Instance.GetContactAsync(memberId);
|
var newUserContact = await SignalIntegration.Instance.GetContactAsync(memberId);
|
||||||
logger.LogDebug("New user:");
|
Console.WriteLine("New user:");
|
||||||
logger.LogDebug($" Name: {newUserContact.Name}");
|
Console.WriteLine($" Name: {newUserContact.Name}");
|
||||||
logger.LogDebug($" MemberId: {memberId}");
|
Console.WriteLine($" MemberId: {memberId}");
|
||||||
User newUser = new User()
|
User newUser = new User()
|
||||||
{
|
{
|
||||||
Name = newUserContact.Name,
|
Name = newUserContact.Name,
|
||||||
@ -58,8 +55,9 @@ userCheckTimer.OnOccurence += async (s, ea) =>
|
|||||||
}
|
}
|
||||||
await dci.DisposeAsync();
|
await dci.DisposeAsync();
|
||||||
};
|
};
|
||||||
|
userCheckTimer.Start();
|
||||||
|
|
||||||
logger.LogTrace("Setting up user intro timer");
|
Console.WriteLine("Setting up user intro timer");
|
||||||
var userIntroTimer = new CronTimer("*/1 * * * *", "Europe/Vienna", includingSeconds: false);
|
var userIntroTimer = new CronTimer("*/1 * * * *", "Europe/Vienna", includingSeconds: false);
|
||||||
userIntroTimer.OnOccurence += async (s, ea) =>
|
userIntroTimer.OnOccurence += async (s, ea) =>
|
||||||
{
|
{
|
||||||
@ -85,9 +83,10 @@ userIntroTimer.OnOccurence += async (s, ea) =>
|
|||||||
}
|
}
|
||||||
await dci.DisposeAsync();
|
await dci.DisposeAsync();
|
||||||
};
|
};
|
||||||
|
userIntroTimer.Start();
|
||||||
|
|
||||||
|
|
||||||
logger.LogTrace("Setting up pick of the day timer");
|
Console.WriteLine("Setting up pick of the day timer");
|
||||||
var pickOfTheDayTimer = new CronTimer("0 8 * * *", "Europe/Vienna", includingSeconds: false);
|
var pickOfTheDayTimer = new CronTimer("0 8 * * *", "Europe/Vienna", includingSeconds: false);
|
||||||
pickOfTheDayTimer.OnOccurence += async (s, ea) =>
|
pickOfTheDayTimer.OnOccurence += async (s, ea) =>
|
||||||
{
|
{
|
||||||
@ -95,32 +94,32 @@ pickOfTheDayTimer.OnOccurence += async (s, ea) =>
|
|||||||
|
|
||||||
var lastSong = dci.SongSuggestions?.OrderBy(s => s.Id).LastOrDefault();
|
var lastSong = dci.SongSuggestions?.OrderBy(s => s.Id).LastOrDefault();
|
||||||
|
|
||||||
if (lastSong != null && lastSong.Date > DateTime.Today.Subtract(TimeSpan.FromDays(AppConfiguration.Instance.DaysBetweenRequests)))
|
if (lastSong != null && lastSong.Date >= DateTime.Today.Subtract(TimeSpan.FromDays(AppConfiguration.Instance.DaysBetweenRequests)))
|
||||||
{
|
{
|
||||||
logger.LogWarning("Skipping pick of the day today!");
|
Console.WriteLine("Skipping pick of the day today!");
|
||||||
await dci.DisposeAsync();
|
await dci.DisposeAsync();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dci.Users == null || dci.SuggestionHelpers == null || dci.SongSuggestions == null)
|
if (dci.Users == null || dci.SuggestionHelpers == null || dci.SongSuggestions == null)
|
||||||
{
|
{
|
||||||
logger.LogError("Unable to properly initialize DB context!");
|
Console.WriteLine("Unable to properly initialize DB context!");
|
||||||
await dci.DisposeAsync();
|
await dci.DisposeAsync();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var potentialUsers = dci.Users.Where(u => !u.WasChosenForSuggestionThisRound);
|
var potentialUsers = dci.Users.Where(u => !u.WasChosenForSuggestionThisRound);
|
||||||
if (!potentialUsers.Any())
|
if (!potentialUsers.Any())
|
||||||
{
|
{
|
||||||
logger.LogTrace("Resetting suggestion count on users before resuming");
|
Console.WriteLine("Resetting suggestion count on users before resuming");
|
||||||
await dci.Users.ForEachAsync(u => u.WasChosenForSuggestionThisRound = false);
|
await dci.Users.ForEachAsync(u => u.WasChosenForSuggestionThisRound = false);
|
||||||
await dci.SaveChangesAsync();
|
await dci.SaveChangesAsync();
|
||||||
potentialUsers = dci.Users.Where(u => !u.WasChosenForSuggestionThisRound);
|
potentialUsers = dci.Users.Where(u => !u.WasChosenForSuggestionThisRound);
|
||||||
}
|
}
|
||||||
logger.LogDebug("Today's pool of pickable users is: " + string.Join(", ", potentialUsers.Select(u => u.Name)));
|
Console.WriteLine("Today's pool of pickable users is: " + string.Join(", ", potentialUsers.Select(u => u.Name)));
|
||||||
var luckyUser = potentialUsers.ElementAt((new Random()).Next(potentialUsers.Count()));
|
var luckyUser = potentialUsers.ElementAt((new Random()).Next(potentialUsers.Count()));
|
||||||
if (luckyUser == null)
|
if (luckyUser == null)
|
||||||
{
|
{
|
||||||
logger.LogError("Unable to determine today's lucky user!");
|
Console.WriteLine("Unable to determine today's lucky user!");
|
||||||
await dci.DisposeAsync();
|
await dci.DisposeAsync();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -132,7 +131,7 @@ pickOfTheDayTimer.OnOccurence += async (s, ea) =>
|
|||||||
SuggestionHelper = suggestion,
|
SuggestionHelper = suggestion,
|
||||||
UserHasSubmitted = false,
|
UserHasSubmitted = false,
|
||||||
HasUsedSuggestion = false,
|
HasUsedSuggestion = false,
|
||||||
Date = DateTime.Today.ToUniversalTime()
|
Date = DateTime.Today
|
||||||
};
|
};
|
||||||
if (luckyUser.SignalMemberId is string signalId)
|
if (luckyUser.SignalMemberId is string signalId)
|
||||||
{
|
{
|
||||||
@ -146,6 +145,7 @@ pickOfTheDayTimer.OnOccurence += async (s, ea) =>
|
|||||||
}
|
}
|
||||||
await dci.DisposeAsync();
|
await dci.DisposeAsync();
|
||||||
};
|
};
|
||||||
|
pickOfTheDayTimer.Start();
|
||||||
|
|
||||||
var startUserAssociationProcess = async (User userToAssociate) =>
|
var startUserAssociationProcess = async (User userToAssociate) =>
|
||||||
{
|
{
|
||||||
@ -158,14 +158,14 @@ var startUserAssociationProcess = async (User userToAssociate) =>
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
logger.LogTrace("Setting up LdapAssociation timer");
|
Console.WriteLine("Setting up LdapAssociation timer");
|
||||||
var ldapAssociationTimer = new CronTimer("*/10 * * * *", "Europe/Vienna", includingSeconds: false);
|
var ldapAssociationTimer = new CronTimer("*/10 * * * *", "Europe/Vienna", includingSeconds: false);
|
||||||
ldapAssociationTimer.OnOccurence += async (s, ea) =>
|
ldapAssociationTimer.OnOccurence += async (s, ea) =>
|
||||||
{
|
{
|
||||||
var dci = DataContext.Instance;
|
var dci = DataContext.Instance;
|
||||||
if (dci.Users == null)
|
if (dci.Users == null)
|
||||||
{
|
{
|
||||||
logger.LogError("Unable to properly initialize DB context!");
|
Console.WriteLine("Unable to properly initialize DB context!");
|
||||||
await dci.DisposeAsync();
|
await dci.DisposeAsync();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -186,6 +186,13 @@ ldapAssociationTimer.OnOccurence += async (s, ea) =>
|
|||||||
}
|
}
|
||||||
await dci.DisposeAsync();
|
await dci.DisposeAsync();
|
||||||
};
|
};
|
||||||
|
ldapAssociationTimer.Start();
|
||||||
|
|
||||||
|
var searchResults = LdapIntegration.Instance.SearchInAD(
|
||||||
|
AppConfiguration.Instance.LDAPConfig.LDAPQueryBase,
|
||||||
|
$"(memberOf={AppConfiguration.Instance.LDAPConfig.CrewGroup})",
|
||||||
|
SearchScope.Subtree
|
||||||
|
);
|
||||||
|
|
||||||
// Add services to the container.
|
// Add services to the container.
|
||||||
builder.Services.AddRazorPages();
|
builder.Services.AddRazorPages();
|
||||||
@ -198,20 +205,9 @@ builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationSc
|
|||||||
|
|
||||||
builder.Services.AddSingleton<LdapAuthenticationService>();
|
builder.Services.AddSingleton<LdapAuthenticationService>();
|
||||||
builder.Services.AddSingleton<PhoneClaimCodeProviderService>();
|
builder.Services.AddSingleton<PhoneClaimCodeProviderService>();
|
||||||
builder.Services.AddSingleton<SongResolver>();
|
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
// only start interaction timers in production builds
|
|
||||||
// for local/development testing we want those disabled
|
|
||||||
if (!app.Environment.IsDevelopment())
|
|
||||||
{
|
|
||||||
userCheckTimer.Start();
|
|
||||||
userIntroTimer.Start();
|
|
||||||
pickOfTheDayTimer.Start();
|
|
||||||
ldapAssociationTimer.Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configure the HTTP request pipeline.
|
// Configure the HTTP request pipeline.
|
||||||
if (!app.Environment.IsDevelopment())
|
if (!app.Environment.IsDevelopment())
|
||||||
{
|
{
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
public interface ISongValidator
|
|
||||||
{
|
|
||||||
Task<Song> ValidateAsync(Uri songUri);
|
|
||||||
|
|
||||||
bool CanValidateUri(Uri songUri);
|
|
||||||
|
|
||||||
Task<bool> CanExtractSongMetadataAsync(Uri songUri);
|
|
||||||
}
|
|
@ -1,66 +0,0 @@
|
|||||||
using Microsoft.Extensions.Logging;
|
|
||||||
|
|
||||||
public class SongResolver
|
|
||||||
{
|
|
||||||
private readonly IEnumerable<ISongValidator> _songValidators;
|
|
||||||
|
|
||||||
private readonly ILogger logger;
|
|
||||||
|
|
||||||
public SongResolver()
|
|
||||||
{
|
|
||||||
using ILoggerFactory factory = LoggerFactory.Create(builder => builder.AddConsole().SetMinimumLevel(LogLevel.Information));
|
|
||||||
this.logger = factory.CreateLogger("SongResolver");
|
|
||||||
|
|
||||||
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")))) {
|
|
||||||
if (Activator.CreateInstance(mytype) is ISongValidator validator)
|
|
||||||
{
|
|
||||||
logger.LogDebug("Registering song validator: {ValidatorType}", mytype.Name);
|
|
||||||
this._songValidators = this._songValidators.Append(validator);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<Song> ResolveSongAsync(Uri songUri)
|
|
||||||
{
|
|
||||||
foreach (var validator in _songValidators)
|
|
||||||
{
|
|
||||||
if (validator.CanValidateUri(songUri))
|
|
||||||
{
|
|
||||||
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 {
|
|
||||||
Artist = "Unknown Artist",
|
|
||||||
Name = "Unknown Title",
|
|
||||||
Url = songUri.ToString(),
|
|
||||||
Provider = SongProvider.PlainHttp,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool CanValidate(Uri songUri)
|
|
||||||
{
|
|
||||||
foreach (var validator in _songValidators)
|
|
||||||
{
|
|
||||||
if (validator.CanValidateUri(songUri))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
using System.Text.RegularExpressions;
|
|
||||||
|
|
||||||
public abstract class SongValidatorBase : ISongValidator
|
|
||||||
{
|
|
||||||
public abstract Task<Song> ValidateAsync(Uri songUri);
|
|
||||||
|
|
||||||
public abstract Task<bool> CanExtractSongMetadataAsync(Uri songUri);
|
|
||||||
|
|
||||||
public abstract bool CanValidateUri(Uri songUri);
|
|
||||||
|
|
||||||
protected string LookupSpotifyId(string songName, string songArtist)
|
|
||||||
{
|
|
||||||
// TODO: Implement Spotify ID lookup logic
|
|
||||||
return songName + " by " + songArtist;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,40 +0,0 @@
|
|||||||
using System.Text.RegularExpressions;
|
|
||||||
using AngleSharp;
|
|
||||||
using AngleSharp.Dom;
|
|
||||||
using AngleSharp.Html.Dom;
|
|
||||||
|
|
||||||
public class SpotifyValidator : UriBasedSongValidatorBase
|
|
||||||
{
|
|
||||||
public override string UriValidatorRegex => @"^(https?://)?open.spotify.com/track/([a-zA-Z0-9_-]{22})(\?si=[a-zA-Z0-9_-]+)?$";
|
|
||||||
|
|
||||||
private SpotifyApiClient spotifyApiClient;
|
|
||||||
|
|
||||||
public SpotifyValidator()
|
|
||||||
{
|
|
||||||
spotifyApiClient = new SpotifyApiClient();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task<bool> CanExtractSongMetadataAsync(Uri songUri)
|
|
||||||
{
|
|
||||||
return this.CanValidateUri(songUri);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task<Song> ValidateAsync(Uri songUri)
|
|
||||||
{
|
|
||||||
var regexp = new Regex(UriValidatorRegex, RegexOptions.IgnoreCase);
|
|
||||||
var trackIdMatch = regexp.Match(songUri.ToString()).Groups[2].Value;
|
|
||||||
|
|
||||||
var track = await spotifyApiClient.GetTrackByIdAsync(trackIdMatch);
|
|
||||||
|
|
||||||
var song = new Song
|
|
||||||
{
|
|
||||||
Name = track.Name,
|
|
||||||
Artist = track.Artists.FirstOrDefault()?.Name ?? "Unknown Artist",
|
|
||||||
Url = songUri.ToString(),
|
|
||||||
Provider = SongProvider.Spotify,
|
|
||||||
SpotifyId = trackIdMatch
|
|
||||||
};
|
|
||||||
|
|
||||||
return song;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
using System.Text.RegularExpressions;
|
|
||||||
|
|
||||||
public abstract class UriBasedSongValidatorBase : SongValidatorBase
|
|
||||||
{
|
|
||||||
public abstract string UriValidatorRegex { get; }
|
|
||||||
|
|
||||||
public override bool CanValidateUri(Uri songUri)
|
|
||||||
{
|
|
||||||
var regexp = new Regex(UriValidatorRegex, RegexOptions.IgnoreCase);
|
|
||||||
return regexp.Match(songUri.ToString()).Success;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,43 +0,0 @@
|
|||||||
using AngleSharp;
|
|
||||||
using AngleSharp.Dom;
|
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
return this.CanValidateUri(songUri);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task<Song> ValidateAsync(Uri songUri)
|
|
||||||
{
|
|
||||||
var title = string.Empty;
|
|
||||||
var artist = string.Empty;
|
|
||||||
|
|
||||||
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())))
|
|
||||||
{
|
|
||||||
// document.getElementsByTagName("ytmusic-player-queue-item")[0].getElementsByClassName("song-title")[0].innerHTML
|
|
||||||
title = document.QuerySelector(".ytmusic-player-queue-item")?.QuerySelector(".song-title")?.InnerHtml;
|
|
||||||
// document.getElementsByTagName("ytmusic-player-queue-item")[0].getElementsByClassName("byline")[0].innerHTML
|
|
||||||
artist = document.QuerySelector(".ytmusic-player-queue-item")?.QuerySelector(".byline")?.InnerHtml;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var song = new Song
|
|
||||||
{
|
|
||||||
Name = title,
|
|
||||||
Artist = artist,
|
|
||||||
Url = songUri.ToString(),
|
|
||||||
Provider = SongProvider.YouTube,
|
|
||||||
SpotifyId = this.LookupSpotifyId(title, artist)
|
|
||||||
};
|
|
||||||
|
|
||||||
return song;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,55 +0,0 @@
|
|||||||
using AngleSharp;
|
|
||||||
using AngleSharp.Dom;
|
|
||||||
using AngleSharp.Html.Dom;
|
|
||||||
|
|
||||||
public class YoutubeValidator : UriBasedSongValidatorBase
|
|
||||||
{
|
|
||||||
public override string UriValidatorRegex => @"^(https?://)?(www\.)?(youtube\.com/watch\?v=|youtu\.be/)([a-zA-Z0-9_-]{11})";
|
|
||||||
|
|
||||||
public override async Task<bool> CanExtractSongMetadataAsync(Uri songUri)
|
|
||||||
{
|
|
||||||
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())))
|
|
||||||
{
|
|
||||||
var documentContents = (document.ChildNodes[1] as HtmlElement).InnerHtml;
|
|
||||||
var titleElement = document.QuerySelectorAll(".yt-video-attribute-view-model__title")[0];
|
|
||||||
var artistParentElement = document.QuerySelectorAll(".yt-video-attribute-view-model__secondary-subtitle")[0];
|
|
||||||
|
|
||||||
return titleElement != null && artistParentElement != null && artistParentElement.Children.Length > 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task<Song> ValidateAsync(Uri songUri)
|
|
||||||
{
|
|
||||||
var title = string.Empty;
|
|
||||||
var artist = string.Empty;
|
|
||||||
|
|
||||||
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())))
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var song = new Song
|
|
||||||
{
|
|
||||||
Name = title,
|
|
||||||
Artist = artist,
|
|
||||||
Url = songUri.ToString(),
|
|
||||||
Provider = SongProvider.YouTube,
|
|
||||||
SpotifyId = this.LookupSpotifyId(title, artist)
|
|
||||||
};
|
|
||||||
|
|
||||||
return song;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
using SpotifyAPI.Web;
|
|
||||||
|
|
||||||
public class SpotifyApiClient
|
|
||||||
{
|
|
||||||
private SpotifyClient _spotifyClient;
|
|
||||||
|
|
||||||
public SpotifyApiClient()
|
|
||||||
{
|
|
||||||
var config = SpotifyClientConfig.CreateDefault()
|
|
||||||
.WithAuthenticator(new ClientCredentialsAuthenticator(
|
|
||||||
AppConfiguration.Instance.SpotifyClientId,
|
|
||||||
AppConfiguration.Instance.SpotifyClientSecret));
|
|
||||||
|
|
||||||
_spotifyClient = new SpotifyClient(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<List<FullTrack>> GetTrackCandidatesAsync(string trackName, string artistName)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var searchResponse = await _spotifyClient.Search.Item(new SearchRequest(SearchRequest.Types.Track, $"{trackName} {artistName}")
|
|
||||||
{
|
|
||||||
Limit = 5
|
|
||||||
});
|
|
||||||
return searchResponse.Tracks.Items ?? new List<FullTrack>();
|
|
||||||
}
|
|
||||||
catch (APIException ex)
|
|
||||||
{
|
|
||||||
throw new Exception($"Error fetching tracks by query: \"{trackName} {artistName}\": {ex.Message}", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<FullTrack> GetTrackByIdAsync(string trackId)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return await _spotifyClient.Tracks.Get(trackId);
|
|
||||||
}
|
|
||||||
catch (APIException ex)
|
|
||||||
{
|
|
||||||
throw new Exception($"Error fetching track by ID: {trackId}: {ex.Message}", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1 +1 @@
|
|||||||
0.3.3
|
0.3.2
|
||||||
|
@ -6,18 +6,16 @@
|
|||||||
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
|
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="AngleSharp" Version="1.3.0" />
|
|
||||||
<PackageReference Include="CommandLineParser" Version="2.9.1" />
|
<PackageReference Include="CommandLineParser" Version="2.9.1" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.3" />
|
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.3" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.3">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.3">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
|
||||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
|
||||||
<PackageReference Include="NSwag.ApiDescription.Client" Version="13.0.5" />
|
<PackageReference Include="NSwag.ApiDescription.Client" Version="13.0.5" />
|
||||||
<PackageReference Include="Scalar.AspNetCore" Version="2.1.*" />
|
<PackageReference Include="Scalar.AspNetCore" Version="2.1.*" />
|
||||||
<PackageReference Include="SpotifyAPI.Web" Version="7.2.1" />
|
|
||||||
<PackageReference Include="System.DirectoryServices.Protocols" Version="*" />
|
<PackageReference Include="System.DirectoryServices.Protocols" Version="*" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user