App name refactor
Some checks failed
Build Bimix / Build WebAPI and WebUI (push) Failing after 12s

This commit is contained in:
Michał Zieliński
2025-10-11 11:33:46 +02:00
parent b4edaf007e
commit 6d2c46d971
88 changed files with 437 additions and 419 deletions

View File

@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\BimAI.Domain\BimAI.Domain.csproj" />
<ProjectReference Include="..\BimAI.Application\BimAI.Application.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.17" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.17" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.17">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,90 @@
using Microsoft.EntityFrameworkCore;
using BimAI.Domain.Entities;
namespace BimAI.Infrastructure.Data;
public class BimAIDbContext(DbContextOptions<BimAIDbContext> options) : DbContext(options)
{
public DbSet<Product> Products { get; set; }
public DbSet<SyncState> SyncStates { get; set; }
public DbSet<User> Users { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// Product properties
modelBuilder.Entity<Product>().HasKey(x => x.Id);
modelBuilder.Entity<Product>().Property(x => x.Name).IsRequired().HasMaxLength(512);
modelBuilder.Entity<Product>().Property(x => x.Code).IsRequired().HasMaxLength(40);
modelBuilder.Entity<Product>().Property(x => x.Ean).IsRequired().HasMaxLength(50);
modelBuilder.Entity<Product>().Property(x => x.StockAddresses).IsRequired().HasMaxLength(512);
// SyncState properties
modelBuilder.Entity<SyncState>().HasKey((x => x.Entity));
// User properties
modelBuilder.Entity<User>().HasKey(x => x.Id);
modelBuilder.Entity<User>().Property(x => x.GoogleId).IsRequired().HasMaxLength(100);
modelBuilder.Entity<User>().Property(x => x.Email).IsRequired().HasMaxLength(255);
modelBuilder.Entity<User>().Property(x => x.FullName).IsRequired().HasMaxLength(255);
modelBuilder.Entity<User>().Property(x => x.IsActive).IsRequired().HasDefaultValue(false);
modelBuilder.Entity<User>().Property(x => x.LastLoginAt).IsRequired(false);
// User indexes
modelBuilder.Entity<User>().HasIndex(x => x.GoogleId).IsUnique().HasDatabaseName("IX_Users_GoogleId");
modelBuilder.Entity<User>().HasIndex(x => x.Email).IsUnique().HasDatabaseName("IX_Users_Email");
// Configure defaults for all CreatedAt and UpdatedAt in entities
ConfigureBaseEntity(modelBuilder);
}
private void ConfigureBaseEntity(ModelBuilder modelBuilder)
{
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
if (typeof(BaseEntity).IsAssignableFrom(entityType.ClrType))
{
modelBuilder.Entity(entityType.ClrType)
.Property(nameof(BaseEntity.CreatedAt))
.HasDefaultValueSql("GETUTCDATE()");
modelBuilder.Entity(entityType.ClrType)
.Property(nameof(BaseEntity.UpdatedAt))
.HasDefaultValueSql("GETUTCDATE()");
}
}
}
public override int SaveChanges()
{
UpdateTimestamps();
return base.SaveChanges();
}
public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
UpdateTimestamps();
return base.SaveChangesAsync(cancellationToken);
}
private void UpdateTimestamps()
{
var entities = ChangeTracker.Entries<BaseEntity>();
foreach (var entity in entities)
{
if (entity.State == EntityState.Added)
{
entity.Entity.CreatedAt = DateTime.UtcNow;
entity.Entity.UpdatedAt = DateTime.UtcNow;
break;
}
else if (entity.State == EntityState.Modified)
{
entity.Entity.UpdatedAt = DateTime.UtcNow;
break;
}
}
}
}

View File

@@ -0,0 +1,52 @@
// <auto-generated />
using System;
using BimAI.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 BimAI.Infrastructure.Migrations
{
[DbContext(typeof(BimAIDbContext))]
[Migration("20250619185202_InitDatabase")]
partial class InitDatabase
{
/// <inheritdoc />
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("BimAI.Domain.Entities.Product", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("datetime2");
b.HasKey("Id");
b.ToTable("Products");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,36 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace BimAI.Infrastructure.Migrations
{
/// <inheritdoc />
public partial class InitDatabase : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Products",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Name = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "datetime2", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Products", x => x.Id);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Products");
}
}
}

View File

@@ -0,0 +1,65 @@
// <auto-generated />
using System;
using BimAI.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 BimAI.Infrastructure.Migrations
{
[DbContext(typeof(BimAIDbContext))]
[Migration("20250623184943_AddSyncState")]
partial class AddSyncState
{
/// <inheritdoc />
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("BimAI.Domain.Entities.Product", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("datetime2");
b.HasKey("Id");
b.ToTable("Products");
});
modelBuilder.Entity("BimAI.Domain.Entities.SyncState", b =>
{
b.Property<string>("Entity")
.HasColumnType("nvarchar(450)");
b.Property<long>("LastSynced")
.HasColumnType("bigint");
b.HasKey("Entity");
b.ToTable("SyncStates");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,33 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace BimAI.Infrastructure.Migrations
{
/// <inheritdoc />
public partial class AddSyncState : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "SyncStates",
columns: table => new
{
Entity = table.Column<string>(type: "nvarchar(450)", nullable: false),
LastSynced = table.Column<long>(type: "bigint", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_SyncStates", x => x.Entity);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "SyncStates");
}
}
}

View File

@@ -0,0 +1,65 @@
// <auto-generated />
using System;
using BimAI.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 BimAI.Infrastructure.Migrations
{
[DbContext(typeof(BimAIDbContext))]
[Migration("20250623194653_ResizeProductName")]
partial class ResizeProductName
{
/// <inheritdoc />
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("BimAI.Domain.Entities.Product", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(512)
.HasColumnType("nvarchar(512)");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("datetime2");
b.HasKey("Id");
b.ToTable("Products");
});
modelBuilder.Entity("BimAI.Domain.Entities.SyncState", b =>
{
b.Property<string>("Entity")
.HasColumnType("nvarchar(450)");
b.Property<long>("LastSynced")
.HasColumnType("bigint");
b.HasKey("Entity");
b.ToTable("SyncStates");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,38 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace BimAI.Infrastructure.Migrations
{
/// <inheritdoc />
public partial class ResizeProductName : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "Name",
table: "Products",
type: "nvarchar(512)",
maxLength: 512,
nullable: false,
oldClrType: typeof(string),
oldType: "nvarchar(200)",
oldMaxLength: 200);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "Name",
table: "Products",
type: "nvarchar(200)",
maxLength: 200,
nullable: false,
oldClrType: typeof(string),
oldType: "nvarchar(512)",
oldMaxLength: 512);
}
}
}

View File

@@ -0,0 +1,80 @@
// <auto-generated />
using System;
using BimAI.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 BimAI.Infrastructure.Migrations
{
[DbContext(typeof(BimAIDbContext))]
[Migration("20250624193445_Products-NewFields")]
partial class ProductsNewFields
{
/// <inheritdoc />
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("BimAI.Domain.Entities.Product", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<string>("Code")
.IsRequired()
.HasMaxLength(40)
.HasColumnType("nvarchar(40)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.Property<string>("Ean")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(512)
.HasColumnType("nvarchar(512)");
b.Property<string>("StockAddresses")
.IsRequired()
.HasMaxLength(512)
.HasColumnType("nvarchar(512)");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("datetime2");
b.HasKey("Id");
b.ToTable("Products");
});
modelBuilder.Entity("BimAI.Domain.Entities.SyncState", b =>
{
b.Property<string>("Entity")
.HasColumnType("nvarchar(450)");
b.Property<long>("LastSynced")
.HasColumnType("bigint");
b.HasKey("Entity");
b.ToTable("SyncStates");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,54 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace BimAI.Infrastructure.Migrations
{
/// <inheritdoc />
public partial class ProductsNewFields : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "Code",
table: "Products",
type: "nvarchar(40)",
maxLength: 40,
nullable: false,
defaultValue: "");
migrationBuilder.AddColumn<string>(
name: "Ean",
table: "Products",
type: "nvarchar(50)",
maxLength: 50,
nullable: false,
defaultValue: "");
migrationBuilder.AddColumn<string>(
name: "StockAddresses",
table: "Products",
type: "nvarchar(512)",
maxLength: 512,
nullable: false,
defaultValue: "");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Code",
table: "Products");
migrationBuilder.DropColumn(
name: "Ean",
table: "Products");
migrationBuilder.DropColumn(
name: "StockAddresses",
table: "Products");
}
}
}

View File

@@ -0,0 +1,136 @@
// <auto-generated />
using System;
using BimAI.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 BimAI.Infrastructure.Migrations
{
[DbContext(typeof(BimAIDbContext))]
[Migration("20250718162313_AddUsersTable")]
partial class AddUsersTable
{
/// <inheritdoc />
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("BimAI.Domain.Entities.Product", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<string>("Code")
.IsRequired()
.HasMaxLength(40)
.HasColumnType("nvarchar(40)");
b.Property<DateTime>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("datetime2")
.HasDefaultValueSql("GETUTCDATE()");
b.Property<string>("Ean")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(512)
.HasColumnType("nvarchar(512)");
b.Property<string>("StockAddresses")
.IsRequired()
.HasMaxLength(512)
.HasColumnType("nvarchar(512)");
b.Property<DateTime>("UpdatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("datetime2")
.HasDefaultValueSql("GETUTCDATE()");
b.HasKey("Id");
b.ToTable("Products");
});
modelBuilder.Entity("BimAI.Domain.Entities.SyncState", b =>
{
b.Property<string>("Entity")
.HasColumnType("nvarchar(450)");
b.Property<long>("LastSynced")
.HasColumnType("bigint");
b.HasKey("Entity");
b.ToTable("SyncStates");
});
modelBuilder.Entity("BimAI.Domain.Entities.User", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("datetime2")
.HasDefaultValueSql("GETUTCDATE()");
b.Property<string>("Email")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("nvarchar(255)");
b.Property<string>("FullName")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("nvarchar(255)");
b.Property<string>("GoogleId")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<bool>("IsActive")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false);
b.Property<DateTime?>("LastLoginAt")
.HasColumnType("datetime2");
b.Property<DateTime>("UpdatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("datetime2")
.HasDefaultValueSql("GETUTCDATE()");
b.HasKey("Id");
b.HasIndex("Email")
.IsUnique()
.HasDatabaseName("IX_Users_Email");
b.HasIndex("GoogleId")
.IsUnique()
.HasDatabaseName("IX_Users_GoogleId");
b.ToTable("Users");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,88 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace BimAI.Infrastructure.Migrations
{
/// <inheritdoc />
public partial class AddUsersTable : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<DateTime>(
name: "UpdatedAt",
table: "Products",
type: "datetime2",
nullable: false,
defaultValueSql: "GETUTCDATE()",
oldClrType: typeof(DateTime),
oldType: "datetime2");
migrationBuilder.AlterColumn<DateTime>(
name: "CreatedAt",
table: "Products",
type: "datetime2",
nullable: false,
defaultValueSql: "GETUTCDATE()",
oldClrType: typeof(DateTime),
oldType: "datetime2");
migrationBuilder.CreateTable(
name: "Users",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
GoogleId = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
Email = table.Column<string>(type: "nvarchar(255)", maxLength: 255, nullable: false),
FullName = table.Column<string>(type: "nvarchar(255)", maxLength: 255, nullable: false),
IsActive = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
LastLoginAt = table.Column<DateTime>(type: "datetime2", nullable: true),
CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false, defaultValueSql: "GETUTCDATE()"),
UpdatedAt = table.Column<DateTime>(type: "datetime2", nullable: false, defaultValueSql: "GETUTCDATE()")
},
constraints: table =>
{
table.PrimaryKey("PK_Users", x => x.Id);
});
migrationBuilder.CreateIndex(
name: "IX_Users_Email",
table: "Users",
column: "Email",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_Users_GoogleId",
table: "Users",
column: "GoogleId",
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Users");
migrationBuilder.AlterColumn<DateTime>(
name: "UpdatedAt",
table: "Products",
type: "datetime2",
nullable: false,
oldClrType: typeof(DateTime),
oldType: "datetime2",
oldDefaultValueSql: "GETUTCDATE()");
migrationBuilder.AlterColumn<DateTime>(
name: "CreatedAt",
table: "Products",
type: "datetime2",
nullable: false,
oldClrType: typeof(DateTime),
oldType: "datetime2",
oldDefaultValueSql: "GETUTCDATE()");
}
}
}

View File

@@ -0,0 +1,133 @@
// <auto-generated />
using System;
using BimAI.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace BimAI.Infrastructure.Migrations
{
[DbContext(typeof(BimAIDbContext))]
partial class BimAIDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.17")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("BimAI.Domain.Entities.Product", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<string>("Code")
.IsRequired()
.HasMaxLength(40)
.HasColumnType("nvarchar(40)");
b.Property<DateTime>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("datetime2")
.HasDefaultValueSql("GETUTCDATE()");
b.Property<string>("Ean")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(512)
.HasColumnType("nvarchar(512)");
b.Property<string>("StockAddresses")
.IsRequired()
.HasMaxLength(512)
.HasColumnType("nvarchar(512)");
b.Property<DateTime>("UpdatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("datetime2")
.HasDefaultValueSql("GETUTCDATE()");
b.HasKey("Id");
b.ToTable("Products");
});
modelBuilder.Entity("BimAI.Domain.Entities.SyncState", b =>
{
b.Property<string>("Entity")
.HasColumnType("nvarchar(450)");
b.Property<long>("LastSynced")
.HasColumnType("bigint");
b.HasKey("Entity");
b.ToTable("SyncStates");
});
modelBuilder.Entity("BimAI.Domain.Entities.User", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("datetime2")
.HasDefaultValueSql("GETUTCDATE()");
b.Property<string>("Email")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("nvarchar(255)");
b.Property<string>("FullName")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("nvarchar(255)");
b.Property<string>("GoogleId")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<bool>("IsActive")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false);
b.Property<DateTime?>("LastLoginAt")
.HasColumnType("datetime2");
b.Property<DateTime>("UpdatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("datetime2")
.HasDefaultValueSql("GETUTCDATE()");
b.HasKey("Id");
b.HasIndex("Email")
.IsUnique()
.HasDatabaseName("IX_Users_Email");
b.HasIndex("GoogleId")
.IsUnique()
.HasDatabaseName("IX_Users_GoogleId");
b.ToTable("Users");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,87 @@
using System.Text.Json;
using System.Web;
using BimAI.Domain.Entities;
using BimAI.Infrastructure.Data;
using Microsoft.Extensions.Configuration;
namespace BimAI.Infrastructure.Sync;
public class ProductSyncService(HttpClient httpClient, BimAIDbContext db, IConfiguration configuration)
{
/// <summary>
/// Dekoduje encje HTML w ciągu znaków (np. &quot; na ")
/// </summary>
private string DecodeHtmlEntities(string text)
{
if (string.IsNullOrEmpty(text))
return text;
return HttpUtility.HtmlDecode(text);
}
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<List<JsonElement>>(response);
if (products == null) return;
var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
foreach (var p in products)
{
var idStr = p.GetProperty("id").GetString() ?? "";
var name = DecodeHtmlEntities(p.GetProperty("name").GetString() ?? "");
var code = DecodeHtmlEntities(p.GetProperty("code").GetString() ?? "");
var stockAddresses = DecodeHtmlEntities(p.GetProperty("stock_addresses").GetString() ?? "");
var ean = DecodeHtmlEntities(p.GetProperty("ean").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,
Ean = ean,
Code = code,
StockAddresses = stockAddresses,
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();
}
}