From 63b7ed51d1bd5f6314ac0cd2dd31684400951c7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Zieli=C5=84ski?= Date: Mon, 23 Jun 2025 21:52:09 +0200 Subject: [PATCH] Sync products with old e5 CRM --- Bimix.API/Controllers/ProductsController.cs | 2 +- Bimix.API/Controllers/SyncController.cs | 16 ++++ Bimix.API/Program.cs | 3 + Bimix.API/Properties/launchSettings.json | 33 +------- Bimix.API/appsettings.Development.json | Bin 333 -> 408 bytes Bimix.Application/Bimix.Application.csproj | 4 +- Bimix.Application/Class1.cs | 6 -- Bimix.Domain/Entities/SyncState.cs | 7 ++ Bimix.Infrastructure/Data/BimixDbContext.cs | 7 +- .../20250623184943_AddSyncState.Designer.cs | 65 +++++++++++++++ .../Migrations/20250623184943_AddSyncState.cs | 33 ++++++++ ...250623194653_ResizeProductName.Designer.cs | 65 +++++++++++++++ .../20250623194653_ResizeProductName.cs | 38 +++++++++ .../Migrations/BimixDbContextModelSnapshot.cs | 17 +++- .../Sync/ProductSyncService.cs | 74 ++++++++++++++++++ .../Properties/launchSettings.json | 6 +- Bimix.UI.Web/Properties/launchSettings.json | 43 +++------- 17 files changed, 339 insertions(+), 80 deletions(-) create mode 100644 Bimix.API/Controllers/SyncController.cs delete mode 100644 Bimix.Application/Class1.cs create mode 100644 Bimix.Domain/Entities/SyncState.cs create mode 100644 Bimix.Infrastructure/Migrations/20250623184943_AddSyncState.Designer.cs create mode 100644 Bimix.Infrastructure/Migrations/20250623184943_AddSyncState.cs create mode 100644 Bimix.Infrastructure/Migrations/20250623194653_ResizeProductName.Designer.cs create mode 100644 Bimix.Infrastructure/Migrations/20250623194653_ResizeProductName.cs create mode 100644 Bimix.Infrastructure/Sync/ProductSyncService.cs diff --git a/Bimix.API/Controllers/ProductsController.cs b/Bimix.API/Controllers/ProductsController.cs index 11397b0..573f12a 100644 --- a/Bimix.API/Controllers/ProductsController.cs +++ b/Bimix.API/Controllers/ProductsController.cs @@ -5,8 +5,8 @@ using Microsoft.EntityFrameworkCore; namespace Bimix.API.Controllers; -[Route("api/[controller]")] [ApiController] +[Route("api/[controller]")] public class ProductsController(BimixDbContext context) : ControllerBase { private readonly BimixDbContext _context = context; diff --git a/Bimix.API/Controllers/SyncController.cs b/Bimix.API/Controllers/SyncController.cs new file mode 100644 index 0000000..af13761 --- /dev/null +++ b/Bimix.API/Controllers/SyncController.cs @@ -0,0 +1,16 @@ +using Bimix.Infrastructure.Sync; +using Microsoft.AspNetCore.Mvc; + +namespace Bimix.API.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class SyncController(ProductSyncService productSyncService) : ControllerBase +{ + [HttpPost("run-product-sync")] + public async Task RunProductSync() + { + await productSyncService.RunAsync(); + return Ok(); + } +} \ No newline at end of file diff --git a/Bimix.API/Program.cs b/Bimix.API/Program.cs index e9e885d..72aa51f 100644 --- a/Bimix.API/Program.cs +++ b/Bimix.API/Program.cs @@ -1,11 +1,14 @@ using Bimix.Infrastructure.Data; +using Bimix.Infrastructure.Sync; using Microsoft.EntityFrameworkCore; var builder = WebApplication.CreateBuilder(args); var connectionString = builder.Configuration.GetConnectionString("DefaultConnection"); builder.Services.AddDbContext(options => options.UseSqlServer(connectionString)); +builder.Services.AddScoped(); +builder.Services.AddHttpClient(); builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); diff --git a/Bimix.API/Properties/launchSettings.json b/Bimix.API/Properties/launchSettings.json index d92ae0d..8e97836 100644 --- a/Bimix.API/Properties/launchSettings.json +++ b/Bimix.API/Properties/launchSettings.json @@ -1,41 +1,14 @@ { - "$schema": "http://json.schemastore.org/launchsettings.json", - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:54415", - "sslPort": 44366 - } - }, "profiles": { - "http": { + "dev": { "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": true, "launchUrl": "swagger", - "applicationUrl": "http://localhost:5090", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "https": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "launchUrl": "swagger", - "applicationUrl": "https://localhost:7139;http://localhost:5090", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "launchUrl": "swagger", + "applicationUrl": "https://localhost:7142;http://localhost:5142", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } -} +} \ No newline at end of file diff --git a/Bimix.API/appsettings.Development.json b/Bimix.API/appsettings.Development.json index 3c9a03004291bae5909db880992cf9025568bf01..5d472c11911b974f959b2de87c6a005ebfff6070 100644 GIT binary patch literal 408 zcmV;J0cZXIM@dveQdv+`05$o+@}RFloOt*^nrMe03DUG8$`r?df1gA#T?tGD7q8=x z_uf#P1`J}T{9+{m_-e(}H&>yok!7B6D|iS#{IFGQIt zEDeH?Sg)xFoTxVI8hJ?KyA@q;W4f>Jz(!pjp?`7F@kM4oOu$+CCc~zDo*&*M4t=Ld z0%jhHQa;3a$k%7qgT_)sKqKeG)19?fQa`>H#(TQ3K|3WqfPxOZZ@@v^_m;R@2Wn69 zMd~N+8RI|}YfH2ENXWQ3rHRK~^4pzFe`{x~aINlcMqda)C97tN`!CECt2C)~J)qPf z-Rg?gld9x_hiB0BbT&u;8Y+9i43k8+>G1%2>o|;LOWS=PHpB-|EBTP$AbTba zjUk8Ey-?626#WF6WccBm^#1F&HaP#&w#gxVXO|jDPgb4-^V(W57pm;BO)hNImjTX_ CN6eD| literal 333 zcmV-T0kZx8M@dveQdv+`0EPk~Bn2seOm=gXiN_>$15f?7xYURQLqWU%Q4hZJZP)Zi z>WB4+(kQ@GRtE9VCmnGvPtGly0Ke@==!Uccfy3jY;czw>T^H77zkPhSK?DE4kg>b^ z|Ix=OG@FYMx>f&kMxxT08^!8Cq%pg(Ol&yH%6lF}vjKcoVwQ#`2-bvU;;_5t4SsMo zUFRj`F92svqO?&m|F3OD0I{S{JR<^EJt>GRMlK%JDKaxKS~EHL+6IDwZTWiOxQ^hq zEBeZql{IAn&=#?FjRJ%EJAW9o@irKS6*LgcbKn{jQ|1HobpQb4koo~E<62q$3s9R@ zwsmh@z^(F6BU1Z2hhXa#hotCc>`M&?;bryr_23;F!--(h3nAQMfV}~=Vq@uR^u)@=_pN5~f diff --git a/Bimix.Application/Bimix.Application.csproj b/Bimix.Application/Bimix.Application.csproj index f97e5bb..e6b0ca9 100644 --- a/Bimix.Application/Bimix.Application.csproj +++ b/Bimix.Application/Bimix.Application.csproj @@ -1,7 +1,7 @@  - - + + diff --git a/Bimix.Application/Class1.cs b/Bimix.Application/Class1.cs deleted file mode 100644 index 8b43d7c..0000000 --- a/Bimix.Application/Class1.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Bimix.Application; - -public class Class1 -{ - -} diff --git a/Bimix.Domain/Entities/SyncState.cs b/Bimix.Domain/Entities/SyncState.cs new file mode 100644 index 0000000..0149952 --- /dev/null +++ b/Bimix.Domain/Entities/SyncState.cs @@ -0,0 +1,7 @@ +namespace Bimix.Domain.Entities; + +public class SyncState +{ + public required string Entity { get; set; } + public required long LastSynced { get; set; } // UnixTimestamp +} \ No newline at end of file diff --git a/Bimix.Infrastructure/Data/BimixDbContext.cs b/Bimix.Infrastructure/Data/BimixDbContext.cs index a794375..dd09f5f 100644 --- a/Bimix.Infrastructure/Data/BimixDbContext.cs +++ b/Bimix.Infrastructure/Data/BimixDbContext.cs @@ -6,12 +6,15 @@ namespace Bimix.Infrastructure.Data; public class BimixDbContext(DbContextOptions options) : DbContext(options) { public DbSet Products { get; set; } + public DbSet SyncStates { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); - modelBuilder.Entity().HasKey(p => p.Id); - modelBuilder.Entity().Property(p => p.Name).IsRequired().HasMaxLength(200); + modelBuilder.Entity().HasKey(x => x.Id); + modelBuilder.Entity().Property(x => x.Name).IsRequired().HasMaxLength(512); + + modelBuilder.Entity().HasKey((x => x.Entity)); } } \ No newline at end of file diff --git a/Bimix.Infrastructure/Migrations/20250623184943_AddSyncState.Designer.cs b/Bimix.Infrastructure/Migrations/20250623184943_AddSyncState.Designer.cs new file mode 100644 index 0000000..2b3e72a --- /dev/null +++ b/Bimix.Infrastructure/Migrations/20250623184943_AddSyncState.Designer.cs @@ -0,0 +1,65 @@ +// +using System; +using Bimix.Infrastructure.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Bimix.Infrastructure.Migrations +{ + [DbContext(typeof(BimixDbContext))] + [Migration("20250623184943_AddSyncState")] + partial class AddSyncState + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.17") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Bimix.Domain.Entities.Product", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("Products"); + }); + + modelBuilder.Entity("Bimix.Domain.Entities.SyncState", b => + { + b.Property("Entity") + .HasColumnType("nvarchar(450)"); + + b.Property("LastSynced") + .HasColumnType("bigint"); + + b.HasKey("Entity"); + + b.ToTable("SyncStates"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Bimix.Infrastructure/Migrations/20250623184943_AddSyncState.cs b/Bimix.Infrastructure/Migrations/20250623184943_AddSyncState.cs new file mode 100644 index 0000000..456a24e --- /dev/null +++ b/Bimix.Infrastructure/Migrations/20250623184943_AddSyncState.cs @@ -0,0 +1,33 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bimix.Infrastructure.Migrations +{ + /// + public partial class AddSyncState : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "SyncStates", + columns: table => new + { + Entity = table.Column(type: "nvarchar(450)", nullable: false), + LastSynced = table.Column(type: "bigint", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SyncStates", x => x.Entity); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "SyncStates"); + } + } +} diff --git a/Bimix.Infrastructure/Migrations/20250623194653_ResizeProductName.Designer.cs b/Bimix.Infrastructure/Migrations/20250623194653_ResizeProductName.Designer.cs new file mode 100644 index 0000000..f14b2cf --- /dev/null +++ b/Bimix.Infrastructure/Migrations/20250623194653_ResizeProductName.Designer.cs @@ -0,0 +1,65 @@ +// +using System; +using Bimix.Infrastructure.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Bimix.Infrastructure.Migrations +{ + [DbContext(typeof(BimixDbContext))] + [Migration("20250623194653_ResizeProductName")] + partial class ResizeProductName + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.17") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Bimix.Domain.Entities.Product", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("nvarchar(512)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("Products"); + }); + + modelBuilder.Entity("Bimix.Domain.Entities.SyncState", b => + { + b.Property("Entity") + .HasColumnType("nvarchar(450)"); + + b.Property("LastSynced") + .HasColumnType("bigint"); + + b.HasKey("Entity"); + + b.ToTable("SyncStates"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Bimix.Infrastructure/Migrations/20250623194653_ResizeProductName.cs b/Bimix.Infrastructure/Migrations/20250623194653_ResizeProductName.cs new file mode 100644 index 0000000..fb882ec --- /dev/null +++ b/Bimix.Infrastructure/Migrations/20250623194653_ResizeProductName.cs @@ -0,0 +1,38 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bimix.Infrastructure.Migrations +{ + /// + public partial class ResizeProductName : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "Name", + table: "Products", + type: "nvarchar(512)", + maxLength: 512, + nullable: false, + oldClrType: typeof(string), + oldType: "nvarchar(200)", + oldMaxLength: 200); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "Name", + table: "Products", + type: "nvarchar(200)", + maxLength: 200, + nullable: false, + oldClrType: typeof(string), + oldType: "nvarchar(512)", + oldMaxLength: 512); + } + } +} diff --git a/Bimix.Infrastructure/Migrations/BimixDbContextModelSnapshot.cs b/Bimix.Infrastructure/Migrations/BimixDbContextModelSnapshot.cs index a5add14..e7b2441 100644 --- a/Bimix.Infrastructure/Migrations/BimixDbContextModelSnapshot.cs +++ b/Bimix.Infrastructure/Migrations/BimixDbContextModelSnapshot.cs @@ -33,8 +33,8 @@ namespace Bimix.Infrastructure.Migrations b.Property("Name") .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); + .HasMaxLength(512) + .HasColumnType("nvarchar(512)"); b.Property("UpdatedAt") .HasColumnType("datetime2"); @@ -43,6 +43,19 @@ namespace Bimix.Infrastructure.Migrations b.ToTable("Products"); }); + + modelBuilder.Entity("Bimix.Domain.Entities.SyncState", b => + { + b.Property("Entity") + .HasColumnType("nvarchar(450)"); + + b.Property("LastSynced") + .HasColumnType("bigint"); + + b.HasKey("Entity"); + + b.ToTable("SyncStates"); + }); #pragma warning restore 612, 618 } } diff --git a/Bimix.Infrastructure/Sync/ProductSyncService.cs b/Bimix.Infrastructure/Sync/ProductSyncService.cs new file mode 100644 index 0000000..6afa9ac --- /dev/null +++ b/Bimix.Infrastructure/Sync/ProductSyncService.cs @@ -0,0 +1,74 @@ +using System.Text.Json; +using Bimix.Domain.Entities; +using Bimix.Infrastructure.Data; +using Microsoft.Extensions.Configuration; + +namespace Bimix.Infrastructure.Sync; + +public class ProductSyncService(HttpClient httpClient, BimixDbContext db, IConfiguration configuration) +{ + private readonly HttpClient _httpClient = httpClient; + private readonly BimixDbContext _db = db; + private readonly IConfiguration _configuration = configuration; + + public async Task RunAsync() + { + var apiKey = _configuration["E5_CRM:ApiKey"]; + var syncState = _db.SyncStates.FirstOrDefault(x => x.Entity == "Product") ?? new SyncState { Entity = "Product", LastSynced = 0}; + + var url = $"https://crm.e5.pl/REST/index.php?key={apiKey}&action=export.products.list&since={syncState.LastSynced}"; + var response = await _httpClient.GetStringAsync(url); + + var products = JsonSerializer.Deserialize>(response); + if (products == null) return; + + var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + + foreach (var p in products) + { + var idStr = p.GetProperty("id").GetString() ?? ""; + var name = p.GetProperty("name").GetString() ?? ""; + + if (!Guid.TryParse(idStr, out Guid id)) + { + Console.WriteLine($"[SYNC] Skipping product with wrong ID: '{idStr}', Name: '{name}'"); + continue; + } + + var existing = _db.Products.FirstOrDefault(x => x.Id == id); + + if (existing == null) + { + var product = new Product + { + Id = id, + Name = name, + CreatedAt = DateTime.UtcNow, + UpdatedAt = DateTime.UtcNow + }; + _db.Products.Add(product); + } + else + { + existing.Name = name; + existing.UpdatedAt = DateTime.UtcNow; + } + + Console.WriteLine($"[SYNC] Updated product ID: '{idStr}', Name: '{name}'"); + + var exportedAt = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + var updateUrl = $"https://crm.e5.pl/REST/index.php?key={apiKey}&action=export.products.setExportedAt&id={id}&exportedAt={exportedAt}"; + await _httpClient.GetAsync(updateUrl); + } + syncState.LastSynced = now; + if (_db.SyncStates.FirstOrDefault(x => x.Entity == "Product") == null) + { + _db.SyncStates.Add(syncState); + } + else + { + _db.SyncStates.Update(syncState); + } + await _db.SaveChangesAsync(); + } +} \ No newline at end of file diff --git a/Bimix.UI.Mobile/Properties/launchSettings.json b/Bimix.UI.Mobile/Properties/launchSettings.json index edf8aad..8d6c11a 100644 --- a/Bimix.UI.Mobile/Properties/launchSettings.json +++ b/Bimix.UI.Mobile/Properties/launchSettings.json @@ -1,8 +1,8 @@ { "profiles": { - "Windows Machine": { - "commandName": "MsixPackage", - "nativeDebugging": false + "dev": { + "commandName": "Project", + "dotnetRunMessages": true } } } \ No newline at end of file diff --git a/Bimix.UI.Web/Properties/launchSettings.json b/Bimix.UI.Web/Properties/launchSettings.json index e809b95..2e11377 100644 --- a/Bimix.UI.Web/Properties/launchSettings.json +++ b/Bimix.UI.Web/Properties/launchSettings.json @@ -1,38 +1,13 @@ { - "$schema": "http://json.schemastore.org/launchsettings.json", - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:9600", - "sslPort": 44314 - } - }, - "profiles": { - "http": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "applicationUrl": "http://localhost:5250", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "https": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "applicationUrl": "https://localhost:7094;http://localhost:5250", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } + "profiles": { + "dev": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:7246;http://localhost:5246", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" } } } +} \ No newline at end of file