using Scalar.AspNetCore; using Microsoft.EntityFrameworkCore; using Microsoft.AspNetCore.Authentication.Cookies; var builder = WebApplication.CreateBuilder(args); // 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(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); var app = builder.Build(); var logger = app.Logger; logger.LogTrace("Setting up user check timer"); var userCheckTimer = new CronTimer("*/1 * * * *", "Europe/Vienna", includingSeconds: false); userCheckTimer.OnOccurence += async (s, ea) => { var signalIntegration = app.Services.GetService(); var memberList = await signalIntegration.GetMemberListAsync(); var dci = DataContext.Instance; var needsSaving = false; foreach (var memberId in memberList) { var foundUser = dci.Users?.Where(u => u.SignalMemberId == memberId).SingleOrDefault(); if (foundUser == null) { var newUserContact = await signalIntegration.GetContactAsync(memberId); logger.LogDebug("New user:"); logger.LogDebug($" Name: {newUserContact.Name}"); logger.LogDebug($" MemberId: {memberId}"); User newUser = new User() { Name = newUserContact.Name, SignalMemberId = memberId, NickName = string.Empty, IsIntroduced = false, LdapUserName = string.Empty, AssociationInProgress = false, WasChosenForSuggestionThisRound = false, }; dci.Users?.Add(newUser); needsSaving = true; } } if (needsSaving) { await dci.SaveChangesAsync(); } await dci.DisposeAsync(); }; logger.LogTrace("Setting up user intro timer"); var userIntroTimer = new CronTimer("*/1 * * * *", "Europe/Vienna", includingSeconds: false); userIntroTimer.OnOccurence += async (s, ea) => { var dci = DataContext.Instance; var introUsers = dci.Users?.Where(u => !u.IsIntroduced); if (introUsers == null) { await dci.DisposeAsync(); return; } bool needsSaving = false; foreach (var user in introUsers) { var signalIntegration = app.Services.GetService(); await signalIntegration.IntroduceUserAsync(user); user.IsIntroduced = true; needsSaving = true; } if (needsSaving) { await dci.SaveChangesAsync(); } await dci.DisposeAsync(); }; logger.LogTrace("Setting up pick of the day timer"); var pickOfTheDayTimer = new CronTimer("0 8 * * *", "Europe/Vienna", includingSeconds: false); pickOfTheDayTimer.OnOccurence += async (s, ea) => { var dci = DataContext.Instance; var lastSong = dci.SongSuggestions?.OrderBy(s => s.Id).LastOrDefault(); if (lastSong != null && lastSong.Date > DateTime.Today.Subtract(TimeSpan.FromDays(AppConfiguration.Instance.DaysBetweenRequests))) { logger.LogWarning("Skipping pick of the day today!"); await dci.DisposeAsync(); return; } if (dci.Users == null || dci.SuggestionHelpers == null || dci.SongSuggestions == null) { logger.LogError("Unable to properly initialize DB context!"); await dci.DisposeAsync(); return; } var potentialUsers = dci.Users.Where(u => !u.WasChosenForSuggestionThisRound); if (!potentialUsers.Any()) { logger.LogTrace("Resetting suggestion count on users before resuming"); await dci.Users.ForEachAsync(u => u.WasChosenForSuggestionThisRound = false); await dci.SaveChangesAsync(); potentialUsers = dci.Users.Where(u => !u.WasChosenForSuggestionThisRound); } logger.LogDebug("Today's pool of pickable users is: " + string.Join(", ", potentialUsers.Select(u => u.Name))); var luckyUser = potentialUsers.ElementAt((new Random()).Next(potentialUsers.Count())); if (luckyUser == null) { logger.LogError("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())); var newSongSuggestion = new SongSuggestion() { User = luckyUser, SuggestionHelper = suggestion, UserHasSubmitted = false, HasUsedSuggestion = false, Date = DateTime.Today.ToUniversalTime() }; if (luckyUser.SignalMemberId is string signalId) { var result = await dci.SongSuggestions.AddAsync(newSongSuggestion); newSongSuggestion = result.Entity; luckyUser.WasChosenForSuggestionThisRound = true; await dci.SaveChangesAsync(); var signalIntegration = app.Services.GetService(); await signalIntegration.SendMessageToGroupAsync($"Today's chosen person to share a song is: **{userName}**"); await signalIntegration.SendMessageToUserAsync($"Congratulations, you have been chosen to share a song today!", signalId); await signalIntegration.SendMessageToUserAsync($"Today's (optional) suggestion helper to help you pick a song is:\n\n**{suggestion.Title}**\n\n*{suggestion.Description}*", signalId); await signalIntegration.SendMessageToUserAsync($"Please navigate to https://sord.disi.dev/SongSubmission/{newSongSuggestion.Id} to submit your choice!", luckyUser.SignalMemberId); } await dci.DisposeAsync(); }; var startUserAssociationProcess = async (User userToAssociate) => { if (userToAssociate.SignalMemberId is string signalId) { var signalIntegration = app.Services.GetService(); await signalIntegration.SendMessageToUserAsync($"Hi, I see you are not associated with any website user yet.", signalId); await signalIntegration.SendMessageToUserAsync($"If you haven't yet, please navigate to https://users.disi.dev to create a new account.", signalId); await signalIntegration.SendMessageToUserAsync($"Once you have done so, go to https://sotd.disi.dev, login, navigate to \"Unclaimed Phone Numbers\" and click on the \"Claim\" button to start the claim process.", signalId); await signalIntegration.SendMessageToUserAsync($"With a future update you will be required to submit songs via your user account - at that point you will be skipped during the selection process if you have not yet claimed your phone number!", signalId); } }; logger.LogTrace("Setting up LdapAssociation timer"); var ldapAssociationTimer = new CronTimer("*/10 * * * *", "Europe/Vienna", includingSeconds: false); ldapAssociationTimer.OnOccurence += async (s, ea) => { var dci = DataContext.Instance; if (dci.Users == null) { logger.LogError("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) { user.AssociationInProgress = true; await startUserAssociationProcess(user); user.IsIntroduced = true; needsSaving = true; } if (needsSaving) { await dci.SaveChangesAsync(); } await dci.DisposeAsync(); }; logger.LogTrace("Setting up MessageSync timer"); var messageSyncTimer = new CronTimer("*/10 * * * *", "Europe/Vienna", includingSeconds: false); messageSyncTimer.OnOccurence += async (s, ea) => { var signalIntegration = app.Services.GetService(); await signalIntegration.GetMessagesAsync(); }; // disabled for now, is still buggy // messageSyncTimer.Start(); // only start interaction timers in production builds // for local/development testing we want those disabled if (!app.Environment.IsDevelopment()) { logger.LogTrace("Starting timer for scheduled processes."); userCheckTimer.Start(); userIntroTimer.Start(); pickOfTheDayTimer.Start(); ldapAssociationTimer.Start(); } else { logger.LogTrace("This is a debug build - scheduled processes are disabled."); } // Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Error"); } else { app.MapOpenApi(); app.MapScalarApiReference(); } app.UseRouting(); app.UseAuthorization(); app.MapStaticAssets(); app.MapRazorPages() .WithStaticAssets(); app.MapControllerRoute( name: "login", pattern: "{controller=Auth}/{action=Login}" ); app.MapControllerRoute( name: "logout", pattern: "{controller=Auth}/{action=Logout}" ); app.MapGet("/debug/routes", (IEnumerable endpointSources) => string.Join("\n", endpointSources.SelectMany(source => source.Endpoints))); app.Run();