diff --git a/song_of_the_day/Config/AppConfiguration.cs b/song_of_the_day/Config/AppConfiguration.cs
index 707166c..d6b2828 100644
--- a/song_of_the_day/Config/AppConfiguration.cs
+++ b/song_of_the_day/Config/AppConfiguration.cs
@@ -16,14 +16,15 @@ public class AppConfiguration
this.SignalGroupId = Environment.GetEnvironmentVariable("SIGNAL_GROUP_ID") ?? "group.Wmk1UTVQTnh0Sjd6a0xiOGhnTnMzZlNkc2p2Q3c0SXJiQkU2eDlNU0hyTT0=";
this.WebUIBaseURL = Environment.GetEnvironmentVariable("WEB_BASE_URL") ?? "https://sotd.disi.dev/";
this.UseBotTag = bool.Parse(Environment.GetEnvironmentVariable("USE_BOT_TAG") ?? "true");
- this.AverageDaysBetweenRequests = int.Parse(Environment.GetEnvironmentVariable("AVERAGE_DAYS_BETWEEN_REQUESTS") ?? "2");
+ this.DaysBetweenRequests = int.Parse(Environment.GetEnvironmentVariable("DAYS_BETWEEN_REQUESTS") ?? "2");
var managersGroupName = Environment.GetEnvironmentVariable("LDAP_ADMINGROUP") ?? "admins";
var userGroupName = Environment.GetEnvironmentVariable("LDAP_USERGROUP") ?? "everybody";
+ var bindValue = Environment.GetEnvironmentVariable("LDAP_BIND");
this.LDAPConfig = new ConfigurationAD()
{
Username = Environment.GetEnvironmentVariable("LDAP_BIND") ?? "cn=admin,dc=disi,dc=dev",
Password = Environment.GetEnvironmentVariable("LDAP_PASS") ?? "adminPass2022!",
- Port = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("LDAP_BIND")) ? int.Parse(Environment.GetEnvironmentVariable("LDAP_BIND")) : 389,
+ Port = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("LDAP_BIND")) ? int.Parse(bindValue ?? "389") : 389,
LDAPserver = Environment.GetEnvironmentVariable("LDAP_URL") ?? "192.168.1.108",
LDAPQueryBase = Environment.GetEnvironmentVariable("LDAP_BASE") ?? "dc=disi,dc=dev",
LDAPUserQueryBase = Environment.GetEnvironmentVariable("LDAP_BASE") ?? "ou=people,dc=disi,dc=dev",
@@ -87,7 +88,7 @@ public class AppConfiguration
get; private set;
}
- public int AverageDaysBetweenRequests
+ public int DaysBetweenRequests
{
get; private set;
}
diff --git a/song_of_the_day/Data/Migrations/20250524160218_additional data for song submissions.Designer.cs b/song_of_the_day/Data/Migrations/20250524160218_additional data for song submissions.Designer.cs
new file mode 100644
index 0000000..82fe4b7
--- /dev/null
+++ b/song_of_the_day/Data/Migrations/20250524160218_additional data for song submissions.Designer.cs
@@ -0,0 +1,145 @@
+//
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace song_of_the_day.DataMigrations
+{
+ [DbContext(typeof(DataContext))]
+ [Migration("20250524160218_additional data for song submissions")]
+ partial class additionaldataforsongsubmissions
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "9.0.3")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("Song", b =>
+ {
+ b.Property("SongId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SongId"));
+
+ b.Property("Artist")
+ .HasColumnType("text");
+
+ b.Property("Name")
+ .HasColumnType("text");
+
+ b.Property("Url")
+ .HasColumnType("text");
+
+ b.HasKey("SongId");
+
+ b.ToTable("Songs");
+ });
+
+ modelBuilder.Entity("SongSuggestion", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("Date")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("SongId")
+ .HasColumnType("integer");
+
+ b.Property("Submitted")
+ .HasColumnType("boolean");
+
+ b.Property("UserId")
+ .HasColumnType("integer");
+
+ b.HasKey("Id");
+
+ b.HasIndex("SongId");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("SongSuggestions");
+ });
+
+ modelBuilder.Entity("SuggestionHelper", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("Description")
+ .HasColumnType("text");
+
+ b.Property("Title")
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.ToTable("SuggestionHelpers");
+ });
+
+ modelBuilder.Entity("User", b =>
+ {
+ b.Property("UserId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("UserId"));
+
+ b.Property("AssociationInProgress")
+ .HasColumnType("boolean");
+
+ b.Property("IsIntroduced")
+ .HasColumnType("boolean");
+
+ b.Property("LdapUserName")
+ .HasColumnType("text");
+
+ b.Property("Name")
+ .HasColumnType("text");
+
+ b.Property("NickName")
+ .HasColumnType("text");
+
+ b.Property("SignalMemberId")
+ .HasColumnType("text");
+
+ b.HasKey("UserId");
+
+ b.ToTable("Users");
+ });
+
+ modelBuilder.Entity("SongSuggestion", b =>
+ {
+ b.HasOne("Song", "Song")
+ .WithMany()
+ .HasForeignKey("SongId");
+
+ b.HasOne("User", "User")
+ .WithMany()
+ .HasForeignKey("UserId");
+
+ b.Navigation("Song");
+
+ b.Navigation("User");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/song_of_the_day/Data/Migrations/20250524160218_additional data for song submissions.cs b/song_of_the_day/Data/Migrations/20250524160218_additional data for song submissions.cs
new file mode 100644
index 0000000..2561ef3
--- /dev/null
+++ b/song_of_the_day/Data/Migrations/20250524160218_additional data for song submissions.cs
@@ -0,0 +1,29 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace song_of_the_day.DataMigrations
+{
+ ///
+ public partial class additionaldataforsongsubmissions : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.AddColumn(
+ name: "Submitted",
+ table: "SongSuggestions",
+ type: "boolean",
+ nullable: false,
+ defaultValue: false);
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropColumn(
+ name: "Submitted",
+ table: "SongSuggestions");
+ }
+ }
+}
diff --git a/song_of_the_day/Data/Migrations/DataContextModelSnapshot.cs b/song_of_the_day/Data/Migrations/DataContextModelSnapshot.cs
index 680993c..59cf79b 100644
--- a/song_of_the_day/Data/Migrations/DataContextModelSnapshot.cs
+++ b/song_of_the_day/Data/Migrations/DataContextModelSnapshot.cs
@@ -57,6 +57,9 @@ namespace song_of_the_day.DataMigrations
b.Property("SongId")
.HasColumnType("integer");
+ b.Property("Submitted")
+ .HasColumnType("boolean");
+
b.Property("UserId")
.HasColumnType("integer");
diff --git a/song_of_the_day/Data/SongSuggestion.cs b/song_of_the_day/Data/SongSuggestion.cs
index 68fb4d3..a618afc 100644
--- a/song_of_the_day/Data/SongSuggestion.cs
+++ b/song_of_the_day/Data/SongSuggestion.cs
@@ -1,3 +1,4 @@
+using System.ComponentModel.DataAnnotations;
using Microsoft.EntityFrameworkCore;
public class SongSuggestion
@@ -7,4 +8,7 @@ public class SongSuggestion
public User? User { get; set; }
public Song? Song { get; set; }
public DateTime Date { get; set; }
+ public bool UserHasSubmitted { get; set; }
+ public required SuggestionHelper SuggestionHelper { get; set; }
+ public bool HasUsedSuggestion { get; set; }
}
\ No newline at end of file
diff --git a/song_of_the_day/Program.cs b/song_of_the_day/Program.cs
index ef1f08a..a2df2ff 100644
--- a/song_of_the_day/Program.cs
+++ b/song_of_the_day/Program.cs
@@ -31,7 +31,7 @@ userCheckTimer.OnOccurence += async (s, ea) =>
var needsSaving = false;
foreach (var memberId in memberList)
{
- var foundUser = dci.Users.Where(u => u.SignalMemberId == memberId).SingleOrDefault();
+ var foundUser = dci.Users?.Where(u => u.SignalMemberId == memberId).SingleOrDefault();
if (foundUser == null)
{
var newUserContact = await SignalIntegration.Instance.GetContactAsync(memberId);
@@ -47,7 +47,7 @@ userCheckTimer.OnOccurence += async (s, ea) =>
LdapUserName = string.Empty,
AssociationInProgress = false,
};
- dci.Users.Add(newUser);
+ dci.Users?.Add(newUser);
needsSaving = true;
}
}
@@ -65,7 +65,13 @@ var userIntroTimer = new CronTimer("*/1 * * * *", "Europe/Vienna", includingSeco
userIntroTimer.OnOccurence += async (s, ea) =>
{
var dci = DataContext.Instance;
- var introUsers = dci.Users.Where(u => !u.IsIntroduced);
+ var introUsers = dci.Users?.Where(u => !u.IsIntroduced);
+ if (introUsers == null)
+ {
+ await dci.DisposeAsync();
+ return;
+ }
+
bool needsSaving = false;
foreach (var user in introUsers)
{
@@ -87,34 +93,63 @@ Console.WriteLine("Setting up pick of the day timer");
var pickOfTheDayTimer = new CronTimer("0 8 * * *", "Europe/Vienna", includingSeconds: false);
pickOfTheDayTimer.OnOccurence += async (s, ea) =>
{
- var rand = new Random();
- var num = rand.NextInt64();
- var mod = num % AppConfiguration.Instance.AverageDaysBetweenRequests;
+ var dci = DataContext.Instance;
- if (mod > 0)
+ var lastSong = dci.SongSuggestions?.LastOrDefault();
+
+ if (lastSong != null && lastSong.Date >= DateTime.Today.Subtract(TimeSpan.FromDays(AppConfiguration.Instance.DaysBetweenRequests)))
{
Console.WriteLine("Skipping pick of the day today!");
+ await dci.DisposeAsync();
return;
}
- var dci = DataContext.Instance;
+ if (dci.Users == null || dci.SuggestionHelpers == null || dci.SongSuggestions == null)
+ {
+ Console.WriteLine("Unable to properly initialize DB context!");
+ await dci.DisposeAsync();
+ return;
+ }
var luckyUser = await dci.Users.ElementAtAsync((new Random()).Next(await dci.Users.CountAsync()));
+ if (luckyUser == null)
+ {
+ Console.WriteLine("Unable to determine today's lucky user!");
+ await dci.DisposeAsync();
+ return;
+ }
var userName = string.IsNullOrEmpty(luckyUser.NickName) ? luckyUser.Name : luckyUser.NickName;
var suggestion = await dci.SuggestionHelpers.ElementAtAsync((new Random()).Next(await dci.SuggestionHelpers.CountAsync()));
- await SignalIntegration.Instance.SendMessageToGroupAsync($"Today's chosen person to share a song is: **{userName}**");
- await SignalIntegration.Instance.SendMessageToGroupAsync($"Today's (optional) suggestion helper to help you pick a song is:\n\n**{suggestion.Title}**\n\n*{suggestion.Description}*");
- await SignalIntegration.Instance.SendMessageToUserAsync($"Congratulations, you have been chosen to share a song today!", luckyUser.SignalMemberId);
- await SignalIntegration.Instance.SendMessageToUserAsync($"Today's (optional) suggestion helper to help you pick a song is:\n\n**{suggestion.Title}**\n\n*{suggestion.Description}*", luckyUser.SignalMemberId);
- await SignalIntegration.Instance.SendMessageToUserAsync($"For now please just share your suggestion with the group - in the future I might ask you to share directly with me or via the website to help me keep track of past suggestions!", luckyUser.SignalMemberId);
+ var newSongSuggestion = new SongSuggestion()
+ {
+ User = luckyUser,
+ SuggestionHelper = suggestion,
+ UserHasSubmitted = false,
+ HasUsedSuggestion = false,
+ Date = DateTime.Today
+ };
+ if (luckyUser.SignalMemberId is string signalId)
+ {
+ await dci.SongSuggestions.AddAsync(newSongSuggestion);
+ await dci.SaveChangesAsync();
+ await SignalIntegration.Instance.SendMessageToGroupAsync($"Today's chosen person to share a song is: **{userName}**");
+ await SignalIntegration.Instance.SendMessageToGroupAsync($"Today's (optional) suggestion helper to help you pick a song is:\n\n**{suggestion.Title}**\n\n*{suggestion.Description}*");
+ await SignalIntegration.Instance.SendMessageToUserAsync($"Congratulations, you have been chosen to share a song today!", signalId);
+ await SignalIntegration.Instance.SendMessageToUserAsync($"Today's (optional) suggestion helper to help you pick a song is:\n\n**{suggestion.Title}**\n\n*{suggestion.Description}*", signalId);
+ await SignalIntegration.Instance.SendMessageToUserAsync($"For now please just share your suggestion with the group - in the future I might ask you to share directly with me or via the website to help me keep track of past suggestions!", luckyUser.SignalMemberId);
+ }
+ await dci.DisposeAsync();
};
pickOfTheDayTimer.Start();
var startUserAssociationProcess = async (User userToAssociate) =>
{
- await SignalIntegration.Instance.SendMessageToUserAsync($"Hi, I see you are not associated with any website user yet.", userToAssociate.SignalMemberId);
- await SignalIntegration.Instance.SendMessageToUserAsync($"If you haven't yet, please navigate to https://users.disi.dev to create a new account.", userToAssociate.SignalMemberId);
- 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.", userToAssociate.SignalMemberId);
- 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!", userToAssociate.SignalMemberId);
+ if (userToAssociate.SignalMemberId is string signalId)
+ {
+ await SignalIntegration.Instance.SendMessageToUserAsync($"Hi, I see you are not associated with any website user yet.", signalId);
+ await SignalIntegration.Instance.SendMessageToUserAsync($"If you haven't yet, please navigate to https://users.disi.dev to create a new account.", signalId);
+ await SignalIntegration.Instance.SendMessageToUserAsync($"Once you have done so, go to https://sotd.disi.dev, login, navigate to \"Unclaimed Phone Numbers\" and click on the \"Claim\" button to start the claim process.", signalId);
+ await SignalIntegration.Instance.SendMessageToUserAsync($"With a future update you will be required to submit songs via your user account - at that point you will be skipped during the selection process if you have not yet claimed your phone number!", signalId);
+ }
};
Console.WriteLine("Setting up LdapAssociation timer");
@@ -122,6 +157,12 @@ var ldapAssociationTimer = new CronTimer("*/10 * * * *", "Europe/Vienna", includ
ldapAssociationTimer.OnOccurence += async (s, ea) =>
{
var dci = DataContext.Instance;
+ if (dci.Users == null)
+ {
+ Console.WriteLine("Unable to properly initialize DB context!");
+ await dci.DisposeAsync();
+ return;
+ }
var nonAssociatedUsers = dci.Users.Where(u => string.IsNullOrEmpty(u.LdapUserName) && !u.AssociationInProgress);
var needsSaving = false;
foreach (var user in nonAssociatedUsers)