From b24aaab6797ae683e4e9f69c447c0b9f21ec69f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Zieli=C5=84ski?= Date: Sun, 12 Oct 2025 18:28:14 +0200 Subject: [PATCH] Add hangfire --- BimAI.API/BimAI.API.csproj | 3 + BimAI.API/Program.cs | 41 +++++++++++++ .../Services/HangfireAuthorizationFilter.cs | 20 ++++++ BimAI.API/appsettings.Development.json | Bin 903 -> 1047 bytes BimAI.Infrastructure/Jobs/ProductSyncJob.cs | 31 ++++++++++ docker-compose.yml | 57 ++++++++++++++++++ 6 files changed, 152 insertions(+) create mode 100644 BimAI.API/Services/HangfireAuthorizationFilter.cs create mode 100644 BimAI.Infrastructure/Jobs/ProductSyncJob.cs create mode 100644 docker-compose.yml diff --git a/BimAI.API/BimAI.API.csproj b/BimAI.API/BimAI.API.csproj index 2ac61ec..2374ce7 100644 --- a/BimAI.API/BimAI.API.csproj +++ b/BimAI.API/BimAI.API.csproj @@ -8,6 +8,9 @@ + + + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/BimAI.API/Program.cs b/BimAI.API/Program.cs index b0c7e75..1c72fed 100644 --- a/BimAI.API/Program.cs +++ b/BimAI.API/Program.cs @@ -1,7 +1,10 @@ using System.Text; using BimAI.API.Services; using BimAI.Infrastructure.Data; +using BimAI.Infrastructure.Jobs; using BimAI.Infrastructure.Sync; +using Hangfire; +using Hangfire.SqlServer; using Microsoft.EntityFrameworkCore; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.IdentityModel.Tokens; @@ -18,6 +21,29 @@ builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); +// Start Hangfire section +builder.Services.AddHangfire(configuration => configuration + .SetDataCompatibilityLevel(CompatibilityLevel.Version_180) + .UseSimpleAssemblyNameTypeSerializer() + .UseRecommendedSerializerSettings() + .UseSqlServerStorage(builder.Configuration.GetConnectionString("HangfireConnection"), + new SqlServerStorageOptions + { + CommandBatchMaxTimeout = TimeSpan.FromMinutes(5), + SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5), + QueuePollInterval = TimeSpan.Zero, + UseRecommendedIsolationLevel = true, + DisableGlobalLocks = true, + SchemaName = "Hangfire" + } + ) +); +builder.Services.AddHangfireServer(options => +{ + options.ServerName = builder.Configuration["Hangfire:ServerName"]; + options.WorkerCount = builder.Configuration.GetValue("Hangfire:WorkerCount", 5); +}); +// End Hangfire section // Start auth section var jwtSettings = builder.Configuration.GetSection("JwtSettings"); var secretKey = jwtSettings["SecretKey"]; @@ -65,7 +91,22 @@ if (app.Environment.IsDevelopment()) app.UseHttpsRedirection(); app.UseCors("AllowAll"); +app.UseHangfireDashboard(builder.Configuration["Hangfire:DashboardPath"] ?? "/hangfire", new DashboardOptions +{ + AsyncAuthorization = new[] { new HangfireAuthorizationFilter() }, + DashboardTitle = "BimAI - Job Dashboard" +}); app.UseAuthorization(); app.UseAuthorization(); app.MapControllers(); + +RecurringJob.AddOrUpdate( + "product-sync", + job => job.ExecuteAsync(), + Cron.Daily(2, 0), // Every day at 2:00 AM + new RecurringJobOptions + { + TimeZone = TimeZoneInfo.Local + }); + app.Run(); \ No newline at end of file diff --git a/BimAI.API/Services/HangfireAuthorizationFilter.cs b/BimAI.API/Services/HangfireAuthorizationFilter.cs new file mode 100644 index 0000000..23e563b --- /dev/null +++ b/BimAI.API/Services/HangfireAuthorizationFilter.cs @@ -0,0 +1,20 @@ +using Hangfire.Dashboard; + +namespace BimAI.API.Services; + +public class HangfireAuthorizationFilter: IDashboardAsyncAuthorizationFilter +{ + public Task AuthorizeAsync(DashboardContext context) + { + var httpContext = context.GetHttpContext(); + + var env = httpContext.RequestServices.GetService(); + if (env.IsDevelopment()) + { + return Task.FromResult(true); + } + + var isAuthenticated = httpContext.User.Identity?.IsAuthenticated ?? false; + return Task.FromResult(isAuthenticated); + } +} \ No newline at end of file diff --git a/BimAI.API/appsettings.Development.json b/BimAI.API/appsettings.Development.json index 15779f6994a8bdd74097209feae5d96efd013725..4030be7e98736f35e2f7fe3c3fc3e559e2b5fded 100644 GIT binary patch literal 1047 zcmV+y1nBz!M@dveQdv+`0Q{{#t=W~BBvc`=(u+!bp94A-G>H8(5q|ATF9AcVFh1vE z+$prwWCZ$8R3wki-Vv^wF?)4uo#=_jsCF~0!?}dn^9xjUG%w(y1%kMG|4wdRzsIJ3fkyF}*ti|DpOJzRj0vXu0@1rd@;RKrVWkyc ziptC}Io#v5hzro*l$aXcA$1`|Nw+^DgZ>qaL_>F~-9&sSCwb!k+r>+yptCSc zQ=)$f&L#vVu9?I`TmxWGMJzs8;h6Zh|Jwvx53A=uuuF)zjDmXooBiH`829Z|d$RO5 zQO{Z93!mBL0BhX^rE5>^;gB;G-6nVsM!ppY?w2>Av^$Hcpx33nxHUb$)BiMf&%?YS z3$tpLmo+==4%?1i)M;2o&mB)(1x)!WqjhLJ=%z3m^8MVM=6)$kfdsQ*hbV!(dav+q zf5+uHcjt&4ekH>9UQ#ts3=6@@s^qq31L1Zaq&9&okf?+Inq`;?~s=xqD3tl6k9+Hp4z=;ZzN@Hcr4 z@HsG}z7Z7~eg?vuUekP{*a+SnD*@xa!^s>vhZ$*N$`lh%QyhaI>Cdpb47z^}vTj>A zu;YxwlWQE_PS{igM-KKvXzVxLL%4M?oP>n3OV;>XGaM;c(v>T5e2^#bQb4*6V=JD! z;ng2;0W-fW2)wNBQP>$r@J=`mf_S2$-%`la{>)kzC|s}E)Wjjt?61ixoHlLD>+9&Un zn(_yh9~Si%6U^}MKODF8M+n0mp@<%@+IW(MmoZz1#;Aiw=c^>sC$!&^6?o%0w8N<| RShk*u(Xv+t{C~@ee%$qQ3t#{M literal 903 zcmV;219@fO*cKZ*>lJ5yw@0BS2C^aySr^D(JM_`-4h+Dq9!g zQu+n;=TPW~=411f^721T2io{Alq~?5CL=0tOlHeHKquW&A@)o9(~<7Nt%+5Bj(4Qn zM)e2v7$ROSHF3UdAau1Tmy2vLciQ#%Wq{rfT%w%lSo-n++RN5dY%aZb(GPD)TMzj= zPKtvD&Q1T`>==Umq=Cf--$4IQ%O>5VgjRVV(N2Eutq4@Hr=DS*oK%>&iB)WM?g0_T z95pe%NBEk|`2tP`7mIj~gyi;1e6&KTuAVg@sEe)!aDQ#Wj|+V25K4P;jhQGw&uhha z3{)rd!j62Wf3Pqr4R(+naIJ@$qL>Gh%b>XBZ+ZCD3q&rX%TA2w((`aS8f0n_Pq7h1 zms7vo5^e-YPwSBPWmZq40X3Q#imcQ<>n)qsmKZ;PMkY*#w@CJoxP?bK{Vr9(TNZJR z83?2;dx|XFKz?Xcv3CS&F_W|uZGvrXvn4zB8#>gaGbE`{t4S&97#{{32f1J@2ZOlH zN)BEMN5}MZJ778p$%*qvtE<0?jY$RxbT5AQU^m-gYc`cB!9L}+E2VLC6cM-OS*rjK zZXYax|KK%5G5i2pCj$l&p)+KnQ_uO0uk7k-WHOOTBYIf*b6&s=XFKHi^OFX2!S|I^ ziRyrGN(k5}!s}mg;sC~3IuO!P4fQe*@fZ1J83BvWA$2<7p+VWwpdTpg zToR8_2i(n5vAQ|9aCo}WwvoB&+7=mf` zQ?MyZ&nf??w;eS)NkIP|c#i;*zC^w4!nZgJNGdf**)TmfK>|hddzDMCmg0pA$b6E6 z9J?w?i!(9|N=lgN6ERJvf6a{t`+KWX+~zO7llH%I*nmt2ob226;cg5FAB62}(VFKr zcd!qu%h_|VNIU8qc7Et~Fr@c#HWcV4At@0ytUl+v5X*voX`iM$nm1Y=f6nKzN3H|Z d7x#stQ5&Y3@5qEfs%;Z%Cv?CK9t(=6;q _logger; + + public ProductSyncJob(ProductSyncService productSyncService, ILogger logger) + { + _productSyncService = productSyncService; + _logger = logger; + } + + public async Task ExecuteAsync() + { + _logger.LogInformation("Starting product sync..."); + + try + { + await _productSyncService.RunAsync(); + _logger.LogInformation("Product sync finished."); + } catch (Exception ex) + { + _logger.LogError(ex, "Error during product sync."); + throw; + } + } +} \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..96ad1b5 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,57 @@ +services: + mssql: + image: mcr.microsoft.com/mssql/server:2022-latest + container_name: bimai-mssql + hostname: bimai-mssql + environment: + - ACCEPT_EULA=Y + - SA_PASSWORD=BimAI_Dev_Pass_2024! + - MSSQL_PID=Developer + ports: + - "1433:1433" + volumes: + - mssql-data:/var/opt/mssql + - ./docker/mssql/init:/docker-entrypoint-initdb.d + networks: + - bimai-network + healthcheck: + test: /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P "BimAI_Dev_Pass_2024!" -Q "SELECT 1" || exit 1 + interval: 10s + timeout: 3s + retries: 10 + start_period: 10s + + mongodb: + image: mongo:7.0 + container_name: bimai-mongodb + hostname: bimai-mongodb + environment: + - MONGO_INITDB_ROOT_USERNAME=admin + - MONGO_INITDB_ROOT_PASSWORD=BimAI_Mongo_2024! + - MONGO_INITDB_DATABASE=bimai + ports: + - "27017:27017" + volumes: + - mongodb-data:/data/db + - mongodb-config:/data/configdb + - ./docker/mongodb/init:/docker-entrypoint-initdb.d + networks: + - bimai-network + healthcheck: + test: echo 'db.runCommand("ping").ok' | mongosh localhost:27017/test --quiet + interval: 10s + timeout: 5s + retries: 5 + start_period: 10s + +networks: + bimai-network: + driver: bridge + +volumes: + mssql-data: + name: bimai-mssql-data + mongodb-data: + name: bimai-mongodb-data + mongodb-config: + name: bimai-mongodb-config \ No newline at end of file