diff --git a/docker-compose.yml b/docker-compose.yml
index ae79381..a03f5ff 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -32,6 +32,21 @@ services:
networks:
- app-network
+ background-service:
+ build:
+ context: ./fullstack2026BE
+ dockerfile: BackgroundService/Dockerfile
+ environment:
+ ConnectionStrings__DefaultConnection: "Server=sqlserver,1433;Database=${DB_NAME};User Id=sa;Password=${DB_PASSWORD};TrustServerCertificate=True"
+ ASPNETCORE_ENVIRONMENT: Development
+ ports:
+ - "5002:8080" # Hangfire dashboard at http://localhost:5002/hangfire
+ depends_on:
+ sqlserver:
+ condition: service_healthy
+ networks:
+ - app-network
+
nginx:
build:
context: .
diff --git a/fullstack2026BE/BLL/BLL.csproj.lscache b/fullstack2026BE/BLL/BLL.csproj.lscache
index b0051ff..9d99154 100644
--- a/fullstack2026BE/BLL/BLL.csproj.lscache
+++ b/fullstack2026BE/BLL/BLL.csproj.lscache
@@ -53,6 +53,7 @@ TemporaryDependencyNodeTargetIdentifier=net9.0
/warnaserror+:NU1605,SYSLIB0011
[sourceFiles]
+Jobs/INotificationJob.cs
obj/Debug/net9.0/
.NETCoreApp,Version=v9.0.AssemblyAttributes.cs
BLL.AssemblyInfo.cs
diff --git a/fullstack2026BE/BLL/Jobs/INotificationJob.cs b/fullstack2026BE/BLL/Jobs/INotificationJob.cs
new file mode 100644
index 0000000..284e57c
--- /dev/null
+++ b/fullstack2026BE/BLL/Jobs/INotificationJob.cs
@@ -0,0 +1,6 @@
+namespace BLL.Jobs;
+
+public interface INotificationJob
+{
+ Task SendUserDigestAsync(string userId);
+}
\ No newline at end of file
diff --git a/fullstack2026BE/BackgroundService/BackgroundService.csproj b/fullstack2026BE/BackgroundService/BackgroundService.csproj
new file mode 100644
index 0000000..f85f1a5
--- /dev/null
+++ b/fullstack2026BE/BackgroundService/BackgroundService.csproj
@@ -0,0 +1,22 @@
+
+
+
+ net9.0
+ enable
+ enable
+ dotnet-BackgroundService-284fb7f5-8953-4e33-9155-9a3ced1bb482
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/fullstack2026BE/BackgroundService/BackgroundService.csproj.lscache b/fullstack2026BE/BackgroundService/BackgroundService.csproj.lscache
new file mode 100644
index 0000000..a45865f
--- /dev/null
+++ b/fullstack2026BE/BackgroundService/BackgroundService.csproj.lscache
@@ -0,0 +1,444 @@
+version=1
+
+# This file caches language service data to improve the performance of C# Dev Kit.
+# It is not intended for manual editing. It can safely be deleted and will be
+# regenerated automatically. For more information, see https://aka.ms/lscache
+#
+# To control where cache files are stored, use the following VS Code setting:
+# "dotnet.projectsystem.cacheInProjectFolder": true
+
+[project]
+language=C#
+primary
+lastDtbSucceeded
+
+[properties]
+AssemblyName=BackgroundService
+CommandLineArgsForDesignTimeEvaluation=-langversion:13.0 -define:TRACE
+CompilerGeneratedFilesOutputPath=
+MaxSupportedLangVersion=13.0
+ProjectAssetsFile=obj/project.assets.json
+RootNamespace=BackgroundService
+RunAnalyzers=
+RunAnalyzersDuringLiveAnalysis=
+SolutionPath=*Undefined*
+TargetFrameworkIdentifier=.NETCoreApp
+TargetPath=bin/Debug/net9.0/BackgroundService.dll
+TargetRefPath=obj/Debug/net9.0/ref/BackgroundService.dll
+TemporaryDependencyNodeTargetIdentifier=net9.0
+
+[commandLineArguments]
+/noconfig
+/unsafe-
+/checked-
+/nowarn:1701,1702,1701,1702
+/fullpaths
+/nostdlib+
+/errorreport:prompt
+/warn:9
+/define:TRACE;DEBUG;NET;NET9_0;NETCOREAPP;NET5_0_OR_GREATER;NET6_0_OR_GREATER;NET7_0_OR_GREATER;NET8_0_OR_GREATER;NET9_0_OR_GREATER;NETCOREAPP1_0_OR_GREATER;NETCOREAPP1_1_OR_GREATER;NETCOREAPP2_0_OR_GREATER;NETCOREAPP2_1_OR_GREATER;NETCOREAPP2_2_OR_GREATER;NETCOREAPP3_0_OR_GREATER;NETCOREAPP3_1_OR_GREATER
+/highentropyva+
+/nullable:enable
+/debug+
+/debug:portable
+/filealign:512
+/optimize-
+/out:obj/Debug/net9.0/BackgroundService.dll
+/refout:obj/Debug/net9.0/refint/BackgroundService.dll
+/target:exe
+/warnaserror-
+/utf8output
+/deterministic+
+/langversion:13.0
+/warnaserror+:NU1605,SYSLIB0011
+
+[sourceFiles]
+Config/
+ CronJobConfig.cs
+ CronJobDispatcher.cs
+ CronJobRegistrar.cs
+ ICronJob.cs
+HangfireBuilder.cs
+Jobs/
+ DeleteLogsTill60DaysAgoJob.cs
+ DeletesExpiredRefreshTokensJob.cs
+obj/Debug/net9.0/
+ .NETCoreApp,Version=v9.0.AssemblyAttributes.cs
+ BackgroundService.AssemblyInfo.cs
+ BackgroundService.GlobalUsings.g.cs
+Program.cs
+
+[metadataReferences]
+../
+ Data.Access.Contracts/obj/Debug/net9.0/ref/Data.Access.Contracts.dll
+ Data.Access.Dapper/obj/Debug/net9.0/ref/Data.Access.Dapper.dll
+ Data.Model/obj/Debug/net9.0/ref/Data.Model.dll
+ Infrastructure/obj/Debug/net9.0/ref/Infrastructure.dll
+/packs/Microsoft.AspNetCore.App.Ref/9.0.0/ref/net9.0/
+ Microsoft.AspNetCore.Antiforgery.dll
+ Microsoft.AspNetCore.Authentication.Abstractions.dll
+ Microsoft.AspNetCore.Authentication.BearerToken.dll
+ Microsoft.AspNetCore.Authentication.Cookies.dll
+ Microsoft.AspNetCore.Authentication.Core.dll
+ Microsoft.AspNetCore.Authentication.dll
+ Microsoft.AspNetCore.Authentication.OAuth.dll
+ Microsoft.AspNetCore.Authorization.dll
+ Microsoft.AspNetCore.Authorization.Policy.dll
+ Microsoft.AspNetCore.Components.Authorization.dll
+ Microsoft.AspNetCore.Components.dll
+ Microsoft.AspNetCore.Components.Endpoints.dll
+ Microsoft.AspNetCore.Components.Forms.dll
+ Microsoft.AspNetCore.Components.Server.dll
+ Microsoft.AspNetCore.Components.Web.dll
+ Microsoft.AspNetCore.Connections.Abstractions.dll
+ Microsoft.AspNetCore.CookiePolicy.dll
+ Microsoft.AspNetCore.Cors.dll
+ Microsoft.AspNetCore.Cryptography.Internal.dll
+ Microsoft.AspNetCore.Cryptography.KeyDerivation.dll
+ Microsoft.AspNetCore.DataProtection.Abstractions.dll
+ Microsoft.AspNetCore.DataProtection.dll
+ Microsoft.AspNetCore.DataProtection.Extensions.dll
+ Microsoft.AspNetCore.Diagnostics.Abstractions.dll
+ Microsoft.AspNetCore.Diagnostics.dll
+ Microsoft.AspNetCore.Diagnostics.HealthChecks.dll
+ Microsoft.AspNetCore.dll
+ Microsoft.AspNetCore.HostFiltering.dll
+ Microsoft.AspNetCore.Hosting.Abstractions.dll
+ Microsoft.AspNetCore.Hosting.dll
+ Microsoft.AspNetCore.Hosting.Server.Abstractions.dll
+ Microsoft.AspNetCore.Html.Abstractions.dll
+ Microsoft.AspNetCore.Http.Abstractions.dll
+ Microsoft.AspNetCore.Http.Connections.Common.dll
+ Microsoft.AspNetCore.Http.Connections.dll
+ Microsoft.AspNetCore.Http.dll
+ Microsoft.AspNetCore.Http.Extensions.dll
+ Microsoft.AspNetCore.Http.Features.dll
+ Microsoft.AspNetCore.Http.Results.dll
+ Microsoft.AspNetCore.HttpLogging.dll
+ Microsoft.AspNetCore.HttpOverrides.dll
+ Microsoft.AspNetCore.HttpsPolicy.dll
+ Microsoft.AspNetCore.Identity.dll
+ Microsoft.AspNetCore.Localization.dll
+ Microsoft.AspNetCore.Localization.Routing.dll
+ Microsoft.AspNetCore.Metadata.dll
+ Microsoft.AspNetCore.Mvc.Abstractions.dll
+ Microsoft.AspNetCore.Mvc.ApiExplorer.dll
+ Microsoft.AspNetCore.Mvc.Core.dll
+ Microsoft.AspNetCore.Mvc.Cors.dll
+ Microsoft.AspNetCore.Mvc.DataAnnotations.dll
+ Microsoft.AspNetCore.Mvc.dll
+ Microsoft.AspNetCore.Mvc.Formatters.Json.dll
+ Microsoft.AspNetCore.Mvc.Formatters.Xml.dll
+ Microsoft.AspNetCore.Mvc.Localization.dll
+ Microsoft.AspNetCore.Mvc.Razor.dll
+ Microsoft.AspNetCore.Mvc.RazorPages.dll
+ Microsoft.AspNetCore.Mvc.TagHelpers.dll
+ Microsoft.AspNetCore.Mvc.ViewFeatures.dll
+ Microsoft.AspNetCore.OutputCaching.dll
+ Microsoft.AspNetCore.RateLimiting.dll
+ Microsoft.AspNetCore.Razor.dll
+ Microsoft.AspNetCore.Razor.Runtime.dll
+ Microsoft.AspNetCore.RequestDecompression.dll
+ Microsoft.AspNetCore.ResponseCaching.Abstractions.dll
+ Microsoft.AspNetCore.ResponseCaching.dll
+ Microsoft.AspNetCore.ResponseCompression.dll
+ Microsoft.AspNetCore.Rewrite.dll
+ Microsoft.AspNetCore.Routing.Abstractions.dll
+ Microsoft.AspNetCore.Routing.dll
+ Microsoft.AspNetCore.Server.HttpSys.dll
+ Microsoft.AspNetCore.Server.IIS.dll
+ Microsoft.AspNetCore.Server.IISIntegration.dll
+ Microsoft.AspNetCore.Server.Kestrel.Core.dll
+ Microsoft.AspNetCore.Server.Kestrel.dll
+ Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.dll
+ Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.dll
+ Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.dll
+ Microsoft.AspNetCore.Session.dll
+ Microsoft.AspNetCore.SignalR.Common.dll
+ Microsoft.AspNetCore.SignalR.Core.dll
+ Microsoft.AspNetCore.SignalR.dll
+ Microsoft.AspNetCore.SignalR.Protocols.Json.dll
+ Microsoft.AspNetCore.StaticAssets.dll
+ Microsoft.AspNetCore.StaticFiles.dll
+ Microsoft.AspNetCore.WebSockets.dll
+ Microsoft.AspNetCore.WebUtilities.dll
+ Microsoft.Extensions.Caching.Abstractions.dll
+ Microsoft.Extensions.Caching.Memory.dll
+ Microsoft.Extensions.Configuration.Abstractions.dll
+ Microsoft.Extensions.Configuration.Binder.dll
+ Microsoft.Extensions.Configuration.CommandLine.dll
+ Microsoft.Extensions.Configuration.dll
+ Microsoft.Extensions.Configuration.EnvironmentVariables.dll
+ Microsoft.Extensions.Configuration.FileExtensions.dll
+ Microsoft.Extensions.Configuration.Ini.dll
+ Microsoft.Extensions.Configuration.Json.dll
+ Microsoft.Extensions.Configuration.KeyPerFile.dll
+ Microsoft.Extensions.Configuration.UserSecrets.dll
+ Microsoft.Extensions.Configuration.Xml.dll
+ Microsoft.Extensions.DependencyInjection.dll
+ Microsoft.Extensions.Diagnostics.Abstractions.dll
+ Microsoft.Extensions.Diagnostics.dll
+ Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.dll
+ Microsoft.Extensions.Diagnostics.HealthChecks.dll
+ Microsoft.Extensions.Features.dll
+ Microsoft.Extensions.FileProviders.Abstractions.dll
+ Microsoft.Extensions.FileProviders.Composite.dll
+ Microsoft.Extensions.FileProviders.Embedded.dll
+ Microsoft.Extensions.FileProviders.Physical.dll
+ Microsoft.Extensions.FileSystemGlobbing.dll
+ Microsoft.Extensions.Hosting.Abstractions.dll
+ Microsoft.Extensions.Hosting.dll
+ Microsoft.Extensions.Http.dll
+ Microsoft.Extensions.Identity.Core.dll
+ Microsoft.Extensions.Identity.Stores.dll
+ Microsoft.Extensions.Localization.Abstractions.dll
+ Microsoft.Extensions.Localization.dll
+ Microsoft.Extensions.Logging.Configuration.dll
+ Microsoft.Extensions.Logging.Console.dll
+ Microsoft.Extensions.Logging.Debug.dll
+ Microsoft.Extensions.Logging.dll
+ Microsoft.Extensions.Logging.EventLog.dll
+ Microsoft.Extensions.Logging.EventSource.dll
+ Microsoft.Extensions.Logging.TraceSource.dll
+ Microsoft.Extensions.ObjectPool.dll
+ Microsoft.Extensions.Options.ConfigurationExtensions.dll
+ Microsoft.Extensions.Options.DataAnnotations.dll
+ Microsoft.Extensions.WebEncoders.dll
+ Microsoft.JSInterop.dll
+ Microsoft.Net.Http.Headers.dll
+ System.Security.Cryptography.Xml.dll
+ System.Threading.RateLimiting.dll
+/packs/Microsoft.NETCore.App.Ref/9.0.0/ref/net9.0/
+ Microsoft.CSharp.dll
+ Microsoft.VisualBasic.Core.dll
+ Microsoft.VisualBasic.dll
+ Microsoft.Win32.Primitives.dll
+ Microsoft.Win32.Registry.dll
+ mscorlib.dll
+ netstandard.dll
+ System.AppContext.dll
+ System.Buffers.dll
+ System.Collections.Concurrent.dll
+ System.Collections.dll
+ System.Collections.Immutable.dll
+ System.Collections.NonGeneric.dll
+ System.Collections.Specialized.dll
+ System.ComponentModel.Annotations.dll
+ System.ComponentModel.DataAnnotations.dll
+ System.ComponentModel.dll
+ System.ComponentModel.EventBasedAsync.dll
+ System.ComponentModel.Primitives.dll
+ System.ComponentModel.TypeConverter.dll
+ System.Configuration.dll
+ System.Console.dll
+ System.Core.dll
+ System.Data.Common.dll
+ System.Data.DataSetExtensions.dll
+ System.Data.dll
+ System.Diagnostics.Contracts.dll
+ System.Diagnostics.Debug.dll
+ System.Diagnostics.DiagnosticSource.dll
+ System.Diagnostics.FileVersionInfo.dll
+ System.Diagnostics.Process.dll
+ System.Diagnostics.StackTrace.dll
+ System.Diagnostics.TextWriterTraceListener.dll
+ System.Diagnostics.Tools.dll
+ System.Diagnostics.TraceSource.dll
+ System.Diagnostics.Tracing.dll
+ System.dll
+ System.Drawing.dll
+ System.Drawing.Primitives.dll
+ System.Dynamic.Runtime.dll
+ System.Formats.Asn1.dll
+ System.Formats.Tar.dll
+ System.Globalization.Calendars.dll
+ System.Globalization.dll
+ System.Globalization.Extensions.dll
+ System.IO.Compression.Brotli.dll
+ System.IO.Compression.dll
+ System.IO.Compression.FileSystem.dll
+ System.IO.Compression.ZipFile.dll
+ System.IO.dll
+ System.IO.FileSystem.AccessControl.dll
+ System.IO.FileSystem.dll
+ System.IO.FileSystem.DriveInfo.dll
+ System.IO.FileSystem.Primitives.dll
+ System.IO.FileSystem.Watcher.dll
+ System.IO.IsolatedStorage.dll
+ System.IO.MemoryMappedFiles.dll
+ System.IO.Pipelines.dll
+ System.IO.Pipes.AccessControl.dll
+ System.IO.Pipes.dll
+ System.IO.UnmanagedMemoryStream.dll
+ System.Linq.dll
+ System.Linq.Expressions.dll
+ System.Linq.Parallel.dll
+ System.Linq.Queryable.dll
+ System.Memory.dll
+ System.Net.dll
+ System.Net.Http.dll
+ System.Net.Http.Json.dll
+ System.Net.HttpListener.dll
+ System.Net.Mail.dll
+ System.Net.NameResolution.dll
+ System.Net.NetworkInformation.dll
+ System.Net.Ping.dll
+ System.Net.Primitives.dll
+ System.Net.Quic.dll
+ System.Net.Requests.dll
+ System.Net.Security.dll
+ System.Net.ServicePoint.dll
+ System.Net.Sockets.dll
+ System.Net.WebClient.dll
+ System.Net.WebHeaderCollection.dll
+ System.Net.WebProxy.dll
+ System.Net.WebSockets.Client.dll
+ System.Net.WebSockets.dll
+ System.Numerics.dll
+ System.Numerics.Vectors.dll
+ System.ObjectModel.dll
+ System.Reflection.DispatchProxy.dll
+ System.Reflection.dll
+ System.Reflection.Emit.dll
+ System.Reflection.Emit.ILGeneration.dll
+ System.Reflection.Emit.Lightweight.dll
+ System.Reflection.Extensions.dll
+ System.Reflection.Metadata.dll
+ System.Reflection.Primitives.dll
+ System.Reflection.TypeExtensions.dll
+ System.Resources.Reader.dll
+ System.Resources.ResourceManager.dll
+ System.Resources.Writer.dll
+ System.Runtime.CompilerServices.Unsafe.dll
+ System.Runtime.CompilerServices.VisualC.dll
+ System.Runtime.dll
+ System.Runtime.Extensions.dll
+ System.Runtime.Handles.dll
+ System.Runtime.InteropServices.dll
+ System.Runtime.InteropServices.JavaScript.dll
+ System.Runtime.InteropServices.RuntimeInformation.dll
+ System.Runtime.Intrinsics.dll
+ System.Runtime.Loader.dll
+ System.Runtime.Numerics.dll
+ System.Runtime.Serialization.dll
+ System.Runtime.Serialization.Formatters.dll
+ System.Runtime.Serialization.Json.dll
+ System.Runtime.Serialization.Primitives.dll
+ System.Runtime.Serialization.Xml.dll
+ System.Security.AccessControl.dll
+ System.Security.Claims.dll
+ System.Security.Cryptography.Algorithms.dll
+ System.Security.Cryptography.Cng.dll
+ System.Security.Cryptography.Csp.dll
+ System.Security.Cryptography.dll
+ System.Security.Cryptography.Encoding.dll
+ System.Security.Cryptography.OpenSsl.dll
+ System.Security.Cryptography.Primitives.dll
+ System.Security.Cryptography.X509Certificates.dll
+ System.Security.dll
+ System.Security.Principal.dll
+ System.Security.Principal.Windows.dll
+ System.Security.SecureString.dll
+ System.ServiceModel.Web.dll
+ System.ServiceProcess.dll
+ System.Text.Encoding.CodePages.dll
+ System.Text.Encoding.dll
+ System.Text.Encoding.Extensions.dll
+ System.Text.Encodings.Web.dll
+ System.Text.Json.dll
+ System.Text.RegularExpressions.dll
+ System.Threading.Channels.dll
+ System.Threading.dll
+ System.Threading.Overlapped.dll
+ System.Threading.Tasks.Dataflow.dll
+ System.Threading.Tasks.dll
+ System.Threading.Tasks.Extensions.dll
+ System.Threading.Tasks.Parallel.dll
+ System.Threading.Thread.dll
+ System.Threading.ThreadPool.dll
+ System.Threading.Timer.dll
+ System.Transactions.dll
+ System.Transactions.Local.dll
+ System.ValueTuple.dll
+ System.Web.dll
+ System.Web.HttpUtility.dll
+ System.Windows.dll
+ System.Xml.dll
+ System.Xml.Linq.dll
+ System.Xml.ReaderWriter.dll
+ System.Xml.Serialization.dll
+ System.Xml.XDocument.dll
+ System.Xml.XmlDocument.dll
+ System.Xml.XmlSerializer.dll
+ System.Xml.XPath.dll
+ System.Xml.XPath.XDocument.dll
+ WindowsBase.dll
+/
+ dapper/2.0.123/lib/net5.0/Dapper.dll
+ hangfire.aspnetcore/1.8.23/lib/netcoreapp3.0/Hangfire.AspNetCore.dll
+ hangfire.core/1.8.23/lib/netstandard2.0/Hangfire.Core.dll
+ hangfire.netcore/1.8.23/lib/netstandard2.1/Hangfire.NetCore.dll
+ hangfire.sqlserver/1.8.23/lib/netstandard2.0/Hangfire.SqlServer.dll
+ humanizer.core/2.8.26/lib/netstandard2.0/Humanizer.dll
+ microsoft.aspnetcore.authentication.jwtbearer/9.0.0/lib/net9.0/Microsoft.AspNetCore.Authentication.JwtBearer.dll
+ microsoft.bcl.cryptography/9.0.13/lib/net9.0/Microsoft.Bcl.Cryptography.dll
+ microsoft.data.sqlclient.extensions.abstractions/1.0.0/lib/netstandard2.0/Microsoft.Data.SqlClient.Extensions.Abstractions.dll
+ microsoft.data.sqlclient.internal.logging/1.0.0/lib/netstandard2.0/Microsoft.Data.SqlClient.Internal.Logging.dll
+ microsoft.data.sqlclient/7.0.0/ref/net9.0/Microsoft.Data.SqlClient.dll
+ microsoft.extensions.dependencyinjection.abstractions/9.0.16/lib/net9.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll
+ microsoft.extensions.logging.abstractions/9.0.13/lib/net9.0/Microsoft.Extensions.Logging.Abstractions.dll
+ microsoft.extensions.options/9.0.13/lib/net9.0/Microsoft.Extensions.Options.dll
+ microsoft.extensions.primitives/9.0.13/lib/net9.0/Microsoft.Extensions.Primitives.dll
+ microsoft.identitymodel.abstractions/8.16.0/lib/net9.0/Microsoft.IdentityModel.Abstractions.dll
+ microsoft.identitymodel.jsonwebtokens/8.16.0/lib/net9.0/Microsoft.IdentityModel.JsonWebTokens.dll
+ microsoft.identitymodel.logging/8.16.0/lib/net9.0/Microsoft.IdentityModel.Logging.dll
+ microsoft.identitymodel.protocols.openidconnect/8.16.0/lib/net9.0/Microsoft.IdentityModel.Protocols.OpenIdConnect.dll
+ microsoft.identitymodel.protocols/8.16.0/lib/net9.0/Microsoft.IdentityModel.Protocols.dll
+ microsoft.identitymodel.tokens/8.16.0/lib/net9.0/Microsoft.IdentityModel.Tokens.dll
+ microsoft.sqlserver.server/1.0.0/lib/netstandard2.0/Microsoft.SqlServer.Server.dll
+ newtonsoft.json/13.0.3/lib/net6.0/Newtonsoft.Json.dll
+ npgsql/9.0.0/lib/net8.0/Npgsql.dll
+ serilog.enrichers.environment/3.0.1/lib/net8.0/Serilog.Enrichers.Environment.dll
+ serilog.sinks.console/6.1.1/lib/net8.0/Serilog.Sinks.Console.dll
+ serilog.sinks.mssqlserver/8.0.0/lib/net8.0/Serilog.Sinks.MSSqlServer.dll
+ serilog/4.3.1/lib/net9.0/Serilog.dll
+ sqlkata.execution/4.0.1/lib/net8.0/SqlKata.Execution.dll
+ sqlkata/4.0.1/lib/net8.0/SqlKata.Core.dll
+ system.configuration.configurationmanager/9.0.13/lib/net9.0/System.Configuration.ConfigurationManager.dll
+ system.diagnostics.eventlog/9.0.13/lib/net9.0/System.Diagnostics.EventLog.dll
+ system.identitymodel.tokens.jwt/8.16.0/lib/net9.0/System.IdentityModel.Tokens.Jwt.dll
+ system.security.cryptography.pkcs/9.0.13/lib/net9.0/System.Security.Cryptography.Pkcs.dll
+ system.security.cryptography.protecteddata/9.0.13/lib/net9.0/System.Security.Cryptography.ProtectedData.dll
+
+[analyzerReferences]
+/packs/Microsoft.AspNetCore.App.Ref/9.0.0/analyzers/dotnet/cs/
+ Microsoft.AspNetCore.App.Analyzers.dll
+ Microsoft.AspNetCore.App.CodeFixes.dll
+ Microsoft.AspNetCore.Components.Analyzers.dll
+/packs/Microsoft.NETCore.App.Ref/9.0.0/analyzers/dotnet/cs/
+ Microsoft.Interop.ComInterfaceGenerator.dll
+ Microsoft.Interop.JavaScript.JSImportGenerator.dll
+ Microsoft.Interop.LibraryImportGenerator.dll
+ Microsoft.Interop.SourceGeneration.dll
+ System.Text.Json.SourceGeneration.dll
+ System.Text.RegularExpressions.Generator.dll
+/sdk/9.0.100/Sdks/Microsoft.NET.Sdk.Razor/source-generators/
+ Microsoft.AspNetCore.Razor.Utilities.Shared.dll
+ Microsoft.CodeAnalysis.Razor.Compiler.dll
+ Microsoft.Extensions.ObjectPool.dll
+ System.Collections.Immutable.dll
+/sdk/9.0.100/Sdks/Microsoft.NET.Sdk.Web/analyzers/cs/
+ Microsoft.AspNetCore.Analyzers.dll
+ Microsoft.AspNetCore.Mvc.Analyzers.dll
+/sdk/9.0.100/Sdks/Microsoft.NET.Sdk/analyzers/
+ Microsoft.CodeAnalysis.CSharp.NetAnalyzers.dll
+ Microsoft.CodeAnalysis.NetAnalyzers.dll
+/
+ microsoft.extensions.logging.abstractions/9.0.13/analyzers/dotnet/roslyn4.4/cs/Microsoft.Extensions.Logging.Generators.dll
+ microsoft.extensions.options/9.0.13/analyzers/dotnet/roslyn4.4/cs/Microsoft.Extensions.Options.SourceGeneration.dll
+
+[analyzerConfigFiles]
+/sdk/9.0.100/Sdks/Microsoft.NET.Sdk/
+ analyzers/build/config/analysislevel_9_default.globalconfig
+ codestyle/cs/build/config/analysislevelstyle_default.globalconfig
+obj/Debug/net9.0/BackgroundService.GeneratedMSBuildEditorConfig.editorconfig
diff --git a/fullstack2026BE/BackgroundService/Config/CronJobConfig.cs b/fullstack2026BE/BackgroundService/Config/CronJobConfig.cs
new file mode 100644
index 0000000..49bcda7
--- /dev/null
+++ b/fullstack2026BE/BackgroundService/Config/CronJobConfig.cs
@@ -0,0 +1,7 @@
+namespace BackgroundService.Config;
+
+public class CronJobConfig
+{
+ public string CronExpression { get; set; } = string.Empty;
+ public bool Enabled { get; set; } = true;
+}
\ No newline at end of file
diff --git a/fullstack2026BE/BackgroundService/Config/CronJobDispatcher.cs b/fullstack2026BE/BackgroundService/Config/CronJobDispatcher.cs
new file mode 100644
index 0000000..7155ad8
--- /dev/null
+++ b/fullstack2026BE/BackgroundService/Config/CronJobDispatcher.cs
@@ -0,0 +1,16 @@
+using BackgroundService.Jobs;
+using Infrastructure.DependencyInjection;
+
+namespace BackgroundService.Config;
+
+// Hangfire resolves this type from DI; it routes the call to the correct ICronJob by ID.
+public class CronJobDispatcher(IEnumerable jobs) : IScopedDependency
+{
+ public Task Dispatch(string jobId)
+ {
+ var job = jobs.FirstOrDefault(j => j.JobId == jobId)
+ ?? throw new InvalidOperationException($"No cron job registered with id '{jobId}'");
+
+ return job.ExecuteAsync();
+ }
+}
\ No newline at end of file
diff --git a/fullstack2026BE/BackgroundService/Config/CronJobRegistrar.cs b/fullstack2026BE/BackgroundService/Config/CronJobRegistrar.cs
new file mode 100644
index 0000000..3ad1e04
--- /dev/null
+++ b/fullstack2026BE/BackgroundService/Config/CronJobRegistrar.cs
@@ -0,0 +1,32 @@
+using Hangfire;
+using Infrastructure.Logging;
+
+namespace BackgroundService.Config;
+
+public static class CronJobRegistrar
+{
+ public static void RegisterCronJobs(this WebApplication app)
+ {
+ using var scope = app.Services.CreateScope();
+ var jobs = scope.ServiceProvider.GetServices().ToList();
+ if (jobs.Count == 0) return;
+
+ var config = app.Configuration.GetSection("CronJobs");
+ foreach (var job in jobs)
+ {
+ var jobConfig = config.GetSection(job.JobId).Get() ?? new CronJobConfig();
+
+ if (!jobConfig.Enabled || string.IsNullOrEmpty(jobConfig.CronExpression))
+ {
+ RecurringJob.RemoveIfExists(job.JobId);
+ continue;
+ }
+
+ RecurringJob.AddOrUpdate(
+ job.JobId,
+ dispatcher => dispatcher.Dispatch(job.JobId),
+ jobConfig.CronExpression);
+ }
+ Logger.Info($"Registered jobs on build: {string.Join(", ", jobs.Select(j => j.JobId))}");
+ }
+}
\ No newline at end of file
diff --git a/fullstack2026BE/BackgroundService/Config/ICronJob.cs b/fullstack2026BE/BackgroundService/Config/ICronJob.cs
new file mode 100644
index 0000000..de85c68
--- /dev/null
+++ b/fullstack2026BE/BackgroundService/Config/ICronJob.cs
@@ -0,0 +1,7 @@
+namespace BackgroundService.Config;
+
+public interface ICronJob
+{
+ string JobId { get; }
+ Task ExecuteAsync();
+}
\ No newline at end of file
diff --git a/fullstack2026BE/BackgroundService/Dockerfile b/fullstack2026BE/BackgroundService/Dockerfile
new file mode 100644
index 0000000..131cc07
--- /dev/null
+++ b/fullstack2026BE/BackgroundService/Dockerfile
@@ -0,0 +1,23 @@
+FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
+WORKDIR /src
+
+# Copy project files for layer-cached restore
+COPY fullstack2026BE.sln ./
+COPY BackgroundService/BackgroundService.csproj BackgroundService/
+COPY BLL/BLL.csproj BLL/
+COPY Data.Access.Contracts/Data.Access.Contracts.csproj Data.Access.Contracts/
+COPY Data.Access.Dapper/Data.Access.Dapper.csproj Data.Access.Dapper/
+COPY Data.Model/Data.Model.csproj Data.Model/
+COPY Infrastructure/Infrastructure.csproj Infrastructure/
+RUN dotnet restore BackgroundService/BackgroundService.csproj
+
+COPY . .
+
+FROM build AS publish
+RUN dotnet publish BackgroundService/BackgroundService.csproj -c Release -o /app/publish
+
+FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS runtime
+WORKDIR /app
+COPY --from=publish /app/publish .
+EXPOSE 8080
+ENTRYPOINT ["dotnet", "BackgroundService.dll"]
\ No newline at end of file
diff --git a/fullstack2026BE/BackgroundService/HangfireBuilder.cs b/fullstack2026BE/BackgroundService/HangfireBuilder.cs
new file mode 100644
index 0000000..e7dcdda
--- /dev/null
+++ b/fullstack2026BE/BackgroundService/HangfireBuilder.cs
@@ -0,0 +1,81 @@
+using BackgroundService.Config;
+using BackgroundService.Jobs;
+using Hangfire;
+using Hangfire.Dashboard;
+using Hangfire.SqlServer;
+using Infrastructure.Core;
+using Infrastructure.Helpers;
+using Infrastructure.Logging;
+using Microsoft.Data.SqlClient;
+using Serilog;
+
+namespace BackgroundService;
+
+internal class HangfireBuilder(string[] args) : IAppBuilder
+{
+ public WebApplication Build()
+ {
+ var builder = WebApplication.CreateBuilder(args);
+ var appName = builder.Configuration["AppName"] ?? throw new NullReferenceException("AppName configuration is missing.");
+ var emv = builder.Environment.EnvironmentName;
+ var connectionString = builder.Configuration.GetConnectionString("DefaultConnection")
+ ?? throw new InvalidOperationException(
+ "DefaultConnection connection string is missing.");
+
+ MainDb.Initialize(() => new SqlConnection(connectionString));
+
+ builder.Services.AddAppLogger(
+ builder.Configuration,
+ new MsSqlLogSink(connectionString));
+
+ builder.Services.AddHangfire(config => config
+
+ .SetDataCompatibilityLevel(CompatibilityLevel.Version_180)
+ .UseSimpleAssemblyNameTypeSerializer()
+ .UseRecommendedSerializerSettings()
+ .UseSqlServerStorage(connectionString, new SqlServerStorageOptions
+ {
+ CommandBatchMaxTimeout = TimeSpan.FromMinutes(5),
+ SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5),
+ QueuePollInterval = TimeSpan.Zero,
+ UseRecommendedIsolationLevel = true,
+ DisableGlobalLocks = true
+ }));
+
+ builder.Services.AddHangfireServer(options => { options.WorkerCount = Environment.ProcessorCount * 2; });
+ //var jobs = GetJobClassesFromSettings(builder.Configuration);
+
+ builder.Services.ConfigureAppsDi(appName);
+
+ var app = builder.Build();
+
+ // Dashboard is open for internal/dev access — add auth middleware in production
+ app.UseHangfireDashboard("/hangfire", new DashboardOptions
+ {
+ Authorization = [new AllowAllDashboardAuthorizationFilter(builder.Environment.IsDevelopment())]
+ });
+
+ app.MapGet("/health", () => Results.Ok());
+
+ app.RegisterCronJobs();
+ Logger.Info(emv);
+ return app;
+ }
+
+ private static IEnumerable GetJobClassesFromSettings(IConfiguration configuration)
+ {
+ return configuration
+ .GetSection("CronJobs")
+ .GetChildren()
+ .Select(s => s["JobClassName"])
+ .Where(name => !string.IsNullOrWhiteSpace(name))!;
+ }
+}
+
+file class AllowAllDashboardAuthorizationFilter(bool isDev) : IDashboardAuthorizationFilter
+{
+ public bool Authorize(DashboardContext context)
+ {
+ return isDev;
+ }
+}
\ No newline at end of file
diff --git a/fullstack2026BE/BackgroundService/Jobs/DeleteLogsFromPastJob.cs b/fullstack2026BE/BackgroundService/Jobs/DeleteLogsFromPastJob.cs
new file mode 100644
index 0000000..668cc11
--- /dev/null
+++ b/fullstack2026BE/BackgroundService/Jobs/DeleteLogsFromPastJob.cs
@@ -0,0 +1,19 @@
+using BackgroundService.Config;
+using Data.Access.Contracts.JobCalls;
+using Hangfire;
+using Infrastructure.DependencyInjection;
+using Infrastructure.Logging;
+
+namespace BackgroundService.Jobs;
+
+[AutomaticRetry(Attempts = 2)]
+public class DeleteLogsFromPastJob(IJobActor jobActor) : ICronJob, IScopedDependency
+{
+ public string JobId => "database-delete-logs-from-past-job";
+
+ public async Task ExecuteAsync()
+ {
+ var entitiesDeleted = await jobActor.ClearLogsFromDaysAgoAsync(60);
+ Logger.Info($"Job {JobId} performed at {DateTimeOffset.UtcNow}, entities deleted: {entitiesDeleted}");
+ }
+}
\ No newline at end of file
diff --git a/fullstack2026BE/BackgroundService/Jobs/DeletesExpiredRefreshTokensJob.cs b/fullstack2026BE/BackgroundService/Jobs/DeletesExpiredRefreshTokensJob.cs
new file mode 100644
index 0000000..9e626bd
--- /dev/null
+++ b/fullstack2026BE/BackgroundService/Jobs/DeletesExpiredRefreshTokensJob.cs
@@ -0,0 +1,18 @@
+using BackgroundService.Config;
+using Data.Access.Contracts.JobCalls;
+using Hangfire;
+using Infrastructure.DependencyInjection;
+using Infrastructure.Logging;
+
+namespace BackgroundService.Jobs;
+
+[AutomaticRetry(Attempts = 1)]
+public class DeletesExpiredRefreshTokensJob(IJobActor jobActor) : ICronJob, IScopedDependency
+{
+ public string JobId => "database-delete-expired-refresh-tokens-job";
+ public async Task ExecuteAsync()
+ {
+ var entitiesDeleted = await jobActor.ClearLogsFromDaysAgoAsync(60);
+ Logger.Info($"Job {JobId} performed at {DateTimeOffset.UtcNow}, entities deleted: {entitiesDeleted}");
+ }
+}
\ No newline at end of file
diff --git a/fullstack2026BE/BackgroundService/Program.cs b/fullstack2026BE/BackgroundService/Program.cs
new file mode 100644
index 0000000..66b4bd2
--- /dev/null
+++ b/fullstack2026BE/BackgroundService/Program.cs
@@ -0,0 +1,6 @@
+using BackgroundService;
+
+var builder = new HangfireBuilder(args);
+var app = builder.Build();
+
+app.Run();
diff --git a/fullstack2026BE/BackgroundService/Properties/launchSettings.json b/fullstack2026BE/BackgroundService/Properties/launchSettings.json
new file mode 100644
index 0000000..46df0be
--- /dev/null
+++ b/fullstack2026BE/BackgroundService/Properties/launchSettings.json
@@ -0,0 +1,12 @@
+{
+ "$schema": "https://json.schemastore.org/launchsettings.json",
+ "profiles": {
+ "BackgroundService": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "environmentVariables": {
+ "DOTNET_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/fullstack2026BE/BackgroundService/appsettings.Development.json b/fullstack2026BE/BackgroundService/appsettings.Development.json
new file mode 100644
index 0000000..ff93df1
--- /dev/null
+++ b/fullstack2026BE/BackgroundService/appsettings.Development.json
@@ -0,0 +1,17 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ },
+ "ConnectionStrings": {
+ "DefaultConnection": "Server=localhost,1433;Database=FullStackApp;User Id=sa;Password=jnwKUghLAUWGh3t73t;TrustServerCertificate=True"
+ },
+ "CronJobs": {
+ "database-delete-logs-from-past-job": {
+ "CronExpression": "0 2 * * *",
+ "Enabled": true
+ }
+ }
+}
\ No newline at end of file
diff --git a/fullstack2026BE/BackgroundService/appsettings.json b/fullstack2026BE/BackgroundService/appsettings.json
new file mode 100644
index 0000000..c91cf70
--- /dev/null
+++ b/fullstack2026BE/BackgroundService/appsettings.json
@@ -0,0 +1,19 @@
+{
+ "AppName": "BackgroundService",
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ },
+ "CronJobs": {
+ "database-delete-logs-from-past-job": {
+ "CronExpression": "0 0 1 */2 *",
+ "Enabled": true
+ },
+ "database-delete-expired-refresh-tokens-job": {
+ "CronExpression": "0 0 1 * *",
+ "Enabled": true
+ }
+ }
+}
\ No newline at end of file
diff --git a/fullstack2026BE/Data.Access.Contracts/Data.Access.Contracts.csproj.lscache b/fullstack2026BE/Data.Access.Contracts/Data.Access.Contracts.csproj.lscache
index b71d541..d51bfac 100644
--- a/fullstack2026BE/Data.Access.Contracts/Data.Access.Contracts.csproj.lscache
+++ b/fullstack2026BE/Data.Access.Contracts/Data.Access.Contracts.csproj.lscache
@@ -53,6 +53,7 @@ TemporaryDependencyNodeTargetIdentifier=net9.0
/warnaserror+:NU1605,SYSLIB0011
[sourceFiles]
+JobCalls/IJobActor.cs
obj/Debug/net9.0/
.NETCoreApp,Version=v9.0.AssemblyAttributes.cs
Data.Access.Contracts.AssemblyInfo.cs
diff --git a/fullstack2026BE/Data.Access.Contracts/JobCalls/IJobActor.cs b/fullstack2026BE/Data.Access.Contracts/JobCalls/IJobActor.cs
new file mode 100644
index 0000000..199c458
--- /dev/null
+++ b/fullstack2026BE/Data.Access.Contracts/JobCalls/IJobActor.cs
@@ -0,0 +1,18 @@
+namespace Data.Access.Contracts.JobCalls;
+
+public interface IJobActor
+{
+ ///
+ /// Method to remove logs from specific days ago.
+ ///
+ /// Till what time will the last logs be kept.
+ /// How many entities were deleted.
+ Task ClearLogsFromDaysAgoAsync(int daysAgo);
+
+ ///
+ /// Method to delete refresh tokens created before specific days ago.
+ ///
+ ///
+ ///
+ Task ClearRefreshTokensAsync(int daysAgo);
+}
\ No newline at end of file
diff --git a/fullstack2026BE/Data.Access.Dapper/Data.Access.Dapper.csproj.lscache b/fullstack2026BE/Data.Access.Dapper/Data.Access.Dapper.csproj.lscache
index 1d206ec..671f814 100644
--- a/fullstack2026BE/Data.Access.Dapper/Data.Access.Dapper.csproj.lscache
+++ b/fullstack2026BE/Data.Access.Dapper/Data.Access.Dapper.csproj.lscache
@@ -53,7 +53,10 @@ TemporaryDependencyNodeTargetIdentifier=net9.0
/warnaserror+:NU1605,SYSLIB0011
[sourceFiles]
-Helpers/InsertHelper.cs
+Helpers/
+ InsertHelper.cs
+ SqlKataDbConnectionExtensions.cs
+JobCalls/JobActor.cs
obj/Debug/net9.0/
.NETCoreApp,Version=v9.0.AssemblyAttributes.cs
Data.Access.Dapper.AssemblyInfo.cs
diff --git a/fullstack2026BE/Infrastructure/Helpers/SqlKataDbConnectionExtensions.cs b/fullstack2026BE/Data.Access.Dapper/Helpers/SqlKataDbConnectionExtensions.cs
similarity index 92%
rename from fullstack2026BE/Infrastructure/Helpers/SqlKataDbConnectionExtensions.cs
rename to fullstack2026BE/Data.Access.Dapper/Helpers/SqlKataDbConnectionExtensions.cs
index d0a00c7..e4d7433 100644
--- a/fullstack2026BE/Infrastructure/Helpers/SqlKataDbConnectionExtensions.cs
+++ b/fullstack2026BE/Data.Access.Dapper/Helpers/SqlKataDbConnectionExtensions.cs
@@ -2,7 +2,7 @@
using SqlKata.Compilers;
using SqlKata.Execution;
-namespace Infrastructure.Helpers;
+namespace Data.Access.Dapper.Helpers;
public static class SqlKataDbConnectionExtensions
{
diff --git a/fullstack2026BE/Data.Access.Dapper/JobCalls/JobActor.cs b/fullstack2026BE/Data.Access.Dapper/JobCalls/JobActor.cs
new file mode 100644
index 0000000..190544c
--- /dev/null
+++ b/fullstack2026BE/Data.Access.Dapper/JobCalls/JobActor.cs
@@ -0,0 +1,30 @@
+using Data.Access.Contracts.JobCalls;
+using Data.Access.Dapper.Helpers;
+using Infrastructure.DependencyInjection;
+using Infrastructure.Helpers;
+using SqlKata.Execution;
+
+namespace Data.Access.Dapper.JobCalls;
+
+public class JobActor : IJobActor, IScopedDependency
+{
+ public async Task ClearLogsFromDaysAgoAsync(int daysAgo)
+ {
+ var cutoffDate = DateTimeOffset.UtcNow.AddDays(-daysAgo);
+ await using var conn = await MainDb.OpenConnectionAsync();
+ return await conn.SqlKata()
+ .Query("Logs")
+ .WhereDate("Timestamp", "<", cutoffDate)
+ .DeleteAsync();
+ }
+
+ public async Task ClearRefreshTokensAsync(int daysAgo)
+ {
+ var cutoffDate = DateTimeOffset.UtcNow.AddDays(-daysAgo);
+ await using var conn = await MainDb.OpenConnectionAsync();
+ return await conn.SqlKata()
+ .Query("RefreshTokens")
+ .WhereDate("CreatedAt", "<", cutoffDate)
+ .DeleteAsync();
+ }
+}
\ No newline at end of file
diff --git a/fullstack2026BE/Data.Access.Dapper/Repositories/NotificationRepository.cs b/fullstack2026BE/Data.Access.Dapper/Repositories/NotificationRepository.cs
index 99cf061..f40ef8a 100644
--- a/fullstack2026BE/Data.Access.Dapper/Repositories/NotificationRepository.cs
+++ b/fullstack2026BE/Data.Access.Dapper/Repositories/NotificationRepository.cs
@@ -1,5 +1,6 @@
using Dapper;
using Data.Access.Contracts.Repositories;
+using Data.Access.Dapper.Helpers;
using Data.Model.BusinessStructs;
using Data.Model.Entities;
using Data.Model.SignalR;
diff --git a/fullstack2026BE/Infrastructure/Helpers/DependencyInjectionHelper.cs b/fullstack2026BE/Infrastructure/Helpers/DependencyInjectionHelper.cs
index 5d3c97a..377c374 100644
--- a/fullstack2026BE/Infrastructure/Helpers/DependencyInjectionHelper.cs
+++ b/fullstack2026BE/Infrastructure/Helpers/DependencyInjectionHelper.cs
@@ -1,5 +1,6 @@
using System.Reflection;
using Infrastructure.DependencyInjection;
+using Infrastructure.Models;
using Microsoft.Extensions.DependencyInjection;
namespace Infrastructure.Helpers;
@@ -13,14 +14,15 @@ public static class DependencyInjectionHelper
typeof(ISingletonDependency)
];
- public static void ConfigureAppsDi(this IServiceCollection services) where T : class
+ public static void ConfigureAppsDi(this IServiceCollection services, string appName) where T : class
{
- var assemblies = LoadAllReferencedAssemblies(services);
+ var assemblies = LoadAllReferencedAssemblies();
var concreteTypes = assemblies
.SelectMany(a => a.GetTypes())
.Where(t => t is { IsClass: true, IsAbstract: false });
-
+
+
foreach (var type in concreteTypes)
{
if (typeof(IScopedDependency).IsAssignableFrom(type))
@@ -32,43 +34,30 @@ public static void ConfigureAppsDi(this IServiceCollection services) where T
}
}
- private static List LoadAllReferencedAssemblies(IServiceCollection services) where T: class
+ private static List LoadAllReferencedAssemblies()
{
- // Start from the entry point assembly and recursively load all referenced assemblies.
- // This ensures lazy-loaded project assemblies (BLL, Data.Access.Dapper, etc.) are included.
- var loaded = new Dictionary();
- var queue = new Queue();
+ // MSBuild copies all ProjectReference outputs to the same directory, so scanning it
+ // is equivalent to walking all transitive project references of the top-level assembly.
+ // This avoids relying on GetReferencedAssemblies(), which only includes assemblies whose
+ // types are directly used in code and would miss indirectly-referenced projects.
+ var result = new List();
- var entry = typeof(T).Assembly;
- queue.Enqueue(entry);
- while (queue.Count > 0)
+ foreach (var dll in Directory.GetFiles(AppContext.BaseDirectory, "*.dll"))
{
- var asm = queue.Dequeue();
- if (loaded.ContainsKey(asm.FullName!))
- continue;
-
- loaded[asm.FullName!] = asm;
-
- foreach (var refName in asm.GetReferencedAssemblies())
- {
- // Only walk assemblies that look like project assemblies,
- // skip framework / NuGet assemblies for performance
- if (!loaded.ContainsKey(refName.FullName) && IsProjectAssembly(refName.Name))
- {
- try { queue.Enqueue(Assembly.Load(refName)); }
- catch { /* ignore assemblies that can't be loaded */ }
- }
- }
+ var name = Path.GetFileNameWithoutExtension(dll);
+ if (!IsAcceptableAssembly(name)) continue;
+ try { result.Add(Assembly.LoadFrom(dll)); }
+ catch { /* ignore assemblies that can't be loaded */ }
}
- return [.. loaded.Values];
+ return result;
}
///
/// Returns true for assemblies that belong to this solution.
/// Filters out used packages (like Newtonsoft, Dapper etc.)
///
- private static bool IsProjectAssembly(string? name)
+ private static bool IsAcceptableAssembly(string? name)
{
if (string.IsNullOrEmpty(name)) return false;
return !name.StartsWith("System", StringComparison.Ordinal)
diff --git a/fullstack2026BE/Infrastructure/Infrastructure.csproj.lscache b/fullstack2026BE/Infrastructure/Infrastructure.csproj.lscache
index 57233d2..8d7ef83 100644
--- a/fullstack2026BE/Infrastructure/Infrastructure.csproj.lscache
+++ b/fullstack2026BE/Infrastructure/Infrastructure.csproj.lscache
@@ -61,7 +61,6 @@ DependencyInjection/
Helpers/
DependencyInjectionHelper.cs
MainDb.cs
- SqlKataDbConnectionExtensions.cs
StronglyTypedIdConverter.cs
StronglyTypedIdHelper.cs
StronglyTypedIdTypeHandler.cs
@@ -71,6 +70,7 @@ Logging/
LoggerSetup.cs
MsSqlLogSink.cs
Models/
+ ApplicationName.cs
EUserRoles.cs
LoggingErrorLevel.cs
StronglyTypedId.cs
diff --git a/fullstack2026BE/Infrastructure/Logging/Logger.cs b/fullstack2026BE/Infrastructure/Logging/Logger.cs
index 43045db..48a3c61 100644
--- a/fullstack2026BE/Infrastructure/Logging/Logger.cs
+++ b/fullstack2026BE/Infrastructure/Logging/Logger.cs
@@ -29,6 +29,7 @@ public static void LogBasedOnStatus(LoggingErrorLevel errorLevel, string message
switch (errorLevel)
{
case LoggingErrorLevel.Info:
+ Info(message, args);
break;
case LoggingErrorLevel.Warning:
Warning(message, args);
@@ -37,8 +38,6 @@ public static void LogBasedOnStatus(LoggingErrorLevel errorLevel, string message
case LoggingErrorLevel.Critical:
Error(message, ex, args);
break;
- default:
- break;
}
}
}
\ No newline at end of file
diff --git a/fullstack2026BE/Infrastructure/Logging/MsSqlLogSink.cs b/fullstack2026BE/Infrastructure/Logging/MsSqlLogSink.cs
index b50f22c..1adcf47 100644
--- a/fullstack2026BE/Infrastructure/Logging/MsSqlLogSink.cs
+++ b/fullstack2026BE/Infrastructure/Logging/MsSqlLogSink.cs
@@ -12,13 +12,13 @@ public LoggerConfiguration Apply(LoggerConfiguration configuration)
{
TimeStamp =
{
- ColumnName = "CreatedAt",
+ ColumnName = "Timestamp",
DataType = SqlDbType.DateTimeOffset
},
Level =
{
ColumnName = "Level",
- StoreAsEnum = true // stores as int, matches your int column
+ StoreAsEnum = false
},
Message =
{
@@ -27,19 +27,15 @@ public LoggerConfiguration Apply(LoggerConfiguration configuration)
Exception =
{
ColumnName = "Exception"
- },
- MessageTemplate =
- {
- ColumnName = "MessageTemplate"
}
};
columnOptions.Store.Remove(StandardColumn.Properties);
+ columnOptions.Store.Remove(StandardColumn.MessageTemplate);
columnOptions.AdditionalColumns =
[
new SqlColumn { ColumnName = "ExceptionType", DataType = SqlDbType.NVarChar, DataLength = 256, AllowNull = true },
- new SqlColumn { ColumnName = "StackTrace", DataType = SqlDbType.NVarChar, DataLength = -1, AllowNull = true },
new SqlColumn { ColumnName = "CorrelationId", DataType = SqlDbType.NVarChar, DataLength = 128, AllowNull = true },
new SqlColumn { ColumnName = "UserId", DataType = SqlDbType.NVarChar, DataLength = 128, AllowNull = true },
new SqlColumn { ColumnName = "MachineName", DataType = SqlDbType.NVarChar, DataLength = 256, AllowNull = true },
diff --git a/fullstack2026BE/Infrastructure/Models/ApplicationName.cs b/fullstack2026BE/Infrastructure/Models/ApplicationName.cs
new file mode 100644
index 0000000..44c7247
--- /dev/null
+++ b/fullstack2026BE/Infrastructure/Models/ApplicationName.cs
@@ -0,0 +1,7 @@
+namespace Infrastructure.Models;
+
+public static class ApplicationName
+{
+ public const string Tripster = nameof(Tripster);
+ public const string BackgroundService = nameof(BackgroundService);
+}
\ No newline at end of file
diff --git a/fullstack2026BE/UnitTests/UnitTests.csproj.lscache b/fullstack2026BE/UnitTests/UnitTests.csproj.lscache
index 19bc221..4fd20c6 100644
--- a/fullstack2026BE/UnitTests/UnitTests.csproj.lscache
+++ b/fullstack2026BE/UnitTests/UnitTests.csproj.lscache
@@ -370,6 +370,10 @@ obj/Debug/net9.0/
emptyfiles/4.4.0/lib/net7.0/EmptyFiles.dll
fluentmigrator.abstractions/6.2.0/lib/netstandard2.0/FluentMigrator.Abstractions.dll
fluentmigrator/6.2.0/lib/netstandard2.0/FluentMigrator.dll
+ hangfire.aspnetcore/1.8.23/lib/netcoreapp3.0/Hangfire.AspNetCore.dll
+ hangfire.core/1.8.23/lib/netstandard2.0/Hangfire.Core.dll
+ hangfire.netcore/1.8.23/lib/netstandard2.1/Hangfire.NetCore.dll
+ hangfire.sqlserver/1.8.23/lib/netstandard2.0/Hangfire.SqlServer.dll
humanizer.core/2.14.1/lib/net6.0/Humanizer.dll
microsoft.aspnetcore.authentication.jwtbearer/9.0.0/lib/net9.0/Microsoft.AspNetCore.Authentication.JwtBearer.dll
microsoft.aspnetcore.jsonpatch/9.0.0/lib/net9.0/Microsoft.AspNetCore.JsonPatch.dll
diff --git a/fullstack2026BE/WebApp/Controllers/v1/BaseController.cs b/fullstack2026BE/WebApp/Controllers/v1/BaseController.cs
index 4ee2e34..3cbb761 100644
--- a/fullstack2026BE/WebApp/Controllers/v1/BaseController.cs
+++ b/fullstack2026BE/WebApp/Controllers/v1/BaseController.cs
@@ -1,6 +1,5 @@
using Data.Model.BusinessStructs;
using Data.Model.Utils;
-using Infrastructure.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using WebApp.Helpers;
diff --git a/fullstack2026BE/WebApp/TripAppBuilder.cs b/fullstack2026BE/WebApp/TripAppBuilder.cs
index 3d95c9b..ba6feef 100644
--- a/fullstack2026BE/WebApp/TripAppBuilder.cs
+++ b/fullstack2026BE/WebApp/TripAppBuilder.cs
@@ -2,6 +2,8 @@
using BLL.SignalR.Hubs;
using Data.Access.Contracts.Repositories;
using Data.Access.Dapper.Repositories;
+using Hangfire;
+using Hangfire.SqlServer;
using Infrastructure.Core;
using Infrastructure.Helpers;
using Infrastructure.Logging;
@@ -24,11 +26,23 @@ public WebApplication Build()
{
var builder = WebApplication.CreateBuilder(args);
+ var appName = builder.Configuration["AppName"] ?? throw new NullReferenceException("No name set for application.");
+
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection")
?? throw new InvalidOperationException("DefaultConnection connection string is missing.");
MainDb.Initialize(() => new SqlConnection(connectionString));
+ builder.Services.AddHangfire(config => config
+ .SetDataCompatibilityLevel(CompatibilityLevel.Version_180)
+ .UseSimpleAssemblyNameTypeSerializer()
+ .UseRecommendedSerializerSettings()
+ .UseSqlServerStorage(connectionString, new SqlServerStorageOptions
+ {
+ UseRecommendedIsolationLevel = true,
+ DisableGlobalLocks = true
+ }));
+
builder.Services.AddSignalR();
builder.Services.AddSingleton();
@@ -92,8 +106,7 @@ public WebApplication Build()
});
// Add services to the container.
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
- ConfigureDi(builder);
- builder.Services.AddScoped();
+ ConfigureDi(builder, appName);
builder.Services.AddControllers()
.AddNewtonsoftJson(options =>
@@ -171,9 +184,9 @@ public WebApplication Build()
return app;
}
- private static void ConfigureDi(WebApplicationBuilder builder)
+ private static void ConfigureDi(WebApplicationBuilder builder, string appName)
{
var services = builder.Services;
- services.ConfigureAppsDi();
+ services.ConfigureAppsDi(appName);
}
}
\ No newline at end of file
diff --git a/fullstack2026BE/WebApp/WebApp.csproj b/fullstack2026BE/WebApp/WebApp.csproj
index 2eb4e1e..94c0585 100644
--- a/fullstack2026BE/WebApp/WebApp.csproj
+++ b/fullstack2026BE/WebApp/WebApp.csproj
@@ -8,6 +8,8 @@
+
+
diff --git a/fullstack2026BE/WebApp/WebApp.csproj.lscache b/fullstack2026BE/WebApp/WebApp.csproj.lscache
index 99e74e5..d700540 100644
--- a/fullstack2026BE/WebApp/WebApp.csproj.lscache
+++ b/fullstack2026BE/WebApp/WebApp.csproj.lscache
@@ -382,6 +382,10 @@ TripAppBuilder.cs
dapper/2.0.123/lib/net5.0/Dapper.dll
fluentmigrator.abstractions/6.2.0/lib/netstandard2.0/FluentMigrator.Abstractions.dll
fluentmigrator/6.2.0/lib/netstandard2.0/FluentMigrator.dll
+ hangfire.aspnetcore/1.8.23/lib/netcoreapp3.0/Hangfire.AspNetCore.dll
+ hangfire.core/1.8.23/lib/netstandard2.0/Hangfire.Core.dll
+ hangfire.netcore/1.8.23/lib/netstandard2.1/Hangfire.NetCore.dll
+ hangfire.sqlserver/1.8.23/lib/netstandard2.0/Hangfire.SqlServer.dll
humanizer.core/2.14.1/lib/net6.0/Humanizer.dll
microsoft.aspnetcore.authentication.jwtbearer/9.0.0/lib/net9.0/Microsoft.AspNetCore.Authentication.JwtBearer.dll
microsoft.aspnetcore.jsonpatch/9.0.0/lib/net9.0/Microsoft.AspNetCore.JsonPatch.dll
diff --git a/fullstack2026BE/WebApp/appsettings.json b/fullstack2026BE/WebApp/appsettings.json
index 28bad1a..d8f76ae 100644
--- a/fullstack2026BE/WebApp/appsettings.json
+++ b/fullstack2026BE/WebApp/appsettings.json
@@ -1,4 +1,5 @@
{
+ "AppName": "Tripster",
"Logging": {
"LogLevel": {
"Default": "Information",
diff --git a/fullstack2026BE/fullstack2026BE.csproj.lscache b/fullstack2026BE/fullstack2026BE.csproj.lscache
index 676d182..b859832 100644
--- a/fullstack2026BE/fullstack2026BE.csproj.lscache
+++ b/fullstack2026BE/fullstack2026BE.csproj.lscache
@@ -53,32 +53,54 @@ TemporaryDependencyNodeTargetIdentifier=net9.0
/warnaserror+:NU1605,SYSLIB0011
[sourceFiles]
-BLL/obj/Debug/net9.0/
- .NETCoreApp,Version=v9.0.AssemblyAttributes.cs
- BLL.AssemblyInfo.cs
- BLL.GlobalUsings.g.cs
-BLL/Services/
- AcquaintanceService.cs
- Auth/
- AuthService.cs
- IAuthService.cs
- IAcquaintanceService.cs
- IUserService.cs
- Notifications/
- INotificationService.cs
- NotificationService.cs
- UserService.cs
-BLL/SignalR/Hubs/MainHub.cs
-Data.Access.Contracts/obj/Debug/net9.0/
- .NETCoreApp,Version=v9.0.AssemblyAttributes.cs
- Data.Access.Contracts.AssemblyInfo.cs
- Data.Access.Contracts.GlobalUsings.g.cs
-Data.Access.Contracts/Repositories/
- IAcquaintanceRepository.cs
- INotificationRepository.cs
- IUserRepository.cs
+BackgroundService/Config/
+ CronJobConfig.cs
+ CronJobDispatcher.cs
+ CronJobRegistrar.cs
+ ICronJob.cs
+BackgroundService/
+ HangfireBuilder.cs
+ Jobs/
+ DeleteLogsTill60DaysAgoJob.cs
+ DeletesExpiredRefreshTokensJob.cs
+ obj/Debug/net9.0/
+ .NETCoreApp,Version=v9.0.AssemblyAttributes.cs
+ BackgroundService.AssemblyInfo.cs
+ BackgroundService.GlobalUsings.g.cs
+ Program.cs
+BLL/
+ Jobs/INotificationJob.cs
+ obj/Debug/net9.0/
+ .NETCoreApp,Version=v9.0.AssemblyAttributes.cs
+ BLL.AssemblyInfo.cs
+ BLL.GlobalUsings.g.cs
+ Services/
+ AcquaintanceService.cs
+ Auth/
+ AuthService.cs
+ IAuthService.cs
+ IAcquaintanceService.cs
+ IUserService.cs
+ Notifications/
+ INotificationService.cs
+ NotificationService.cs
+ UserService.cs
+ SignalR/Hubs/MainHub.cs
+Data.Access.Contracts/
+ JobCalls/IJobActor.cs
+ obj/Debug/net9.0/
+ .NETCoreApp,Version=v9.0.AssemblyAttributes.cs
+ Data.Access.Contracts.AssemblyInfo.cs
+ Data.Access.Contracts.GlobalUsings.g.cs
+ Repositories/
+ IAcquaintanceRepository.cs
+ INotificationRepository.cs
+ IUserRepository.cs
+Data.Access.Dapper/Helpers/
+ InsertHelper.cs
+ SqlKataDbConnectionExtensions.cs
Data.Access.Dapper/
- Helpers/InsertHelper.cs
+ JobCalls/JobActor.cs
obj/Debug/net9.0/
.NETCoreApp,Version=v9.0.AssemblyAttributes.cs
Data.Access.Dapper.AssemblyInfo.cs
@@ -136,7 +158,6 @@ Infrastructure/
Helpers/
DependencyInjectionHelper.cs
MainDb.cs
- SqlKataDbConnectionExtensions.cs
StronglyTypedIdConverter.cs
StronglyTypedIdHelper.cs
StronglyTypedIdTypeHandler.cs
@@ -146,6 +167,7 @@ Infrastructure/
LoggerSetup.cs
MsSqlLogSink.cs
Models/
+ ApplicationName.cs
EUserRoles.cs
LoggingErrorLevel.cs
StronglyTypedId.cs
@@ -160,7 +182,6 @@ obj/Debug/net9.0/
fullstack2026BE.GlobalUsings.g.cs
SignalRTester/obj/Debug/net9.0/
.NETCoreApp,Version=v9.0.AssemblyAttributes.cs
- SignalRTester.AssemblyInfo.cs
SignalRTester.GlobalUsings.g.cs
SignalRTester/Program.cs
Temp/obj/Debug/net9.0/
@@ -169,7 +190,6 @@ Temp/obj/Debug/net9.0/
Temp.GlobalUsings.g.cs
Tools/MigrationsCli/obj/Debug/net9.0/
.NETCoreApp,Version=v9.0.AssemblyAttributes.cs
- MigrationsCli.AssemblyInfo.cs
MigrationsCli.GlobalUsings.g.cs
Tools/MigrationsCli/Program.cs
UnitTests/
@@ -178,7 +198,6 @@ UnitTests/
.NETCoreApp,Version=v9.0.AssemblyAttributes.cs
UniTests.AssemblyInfo.cs
UniTests.GlobalUsings.g.cs
- UnitTests.AssemblyInfo.cs
UnitTests.GlobalUsings.g.cs
WebApp/
Configs/DapperConfig.cs
diff --git a/fullstack2026BE/fullstack2026BE.sln b/fullstack2026BE/fullstack2026BE.sln
index 4e0f229..0030b1c 100644
--- a/fullstack2026BE/fullstack2026BE.sln
+++ b/fullstack2026BE/fullstack2026BE.sln
@@ -25,6 +25,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Data.Access.Contracts", "Da
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Data.Access.Dapper", "Data.Access.Dapper\Data.Access.Dapper.csproj", "{745F26CD-F5DC-427B-B765-B0161F3AD93E}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BackgroundService", "BackgroundService\BackgroundService.csproj", "{475D6F60-C203-4349-A229-1D53676CA402}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -71,5 +73,9 @@ Global
{745F26CD-F5DC-427B-B765-B0161F3AD93E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{745F26CD-F5DC-427B-B765-B0161F3AD93E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{745F26CD-F5DC-427B-B765-B0161F3AD93E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {475D6F60-C203-4349-A229-1D53676CA402}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {475D6F60-C203-4349-A229-1D53676CA402}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {475D6F60-C203-4349-A229-1D53676CA402}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {475D6F60-C203-4349-A229-1D53676CA402}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal