Compare commits

...

14 Commits

Author SHA1 Message Date
c88ad43530 Merge branch 'dev' into agent/hex/CUB-31-filament-usage-tracking-model
Some checks failed
Dev Build / build-test (pull_request) Failing after 54s
Dev Build / deploy-dev (pull_request) Has been skipped
Dev Build / notify-success (pull_request) Has been skipped
Dev Build / notify-failure (pull_request) Successful in 3s
2026-04-27 14:09:13 -04:00
8168d25bdf Merge pull request 'CUB-41: Add low stock indicators to filament UI' (#5) from agent/rex/CUB-41-low-stock-indicators into dev
Some checks failed
Dev Build / build-test (push) Failing after 57s
Dev Build / deploy-dev (push) Has been skipped
Dev Build / notify-success (push) Has been skipped
Dev Build / notify-failure (push) Successful in 3s
Reviewed-on: #5
Reviewed-by: Joshua <joshua@cnjmail.com>
2026-04-26 17:23:40 -04:00
fc4c9cf397 Merge branch 'dev' into agent/rex/CUB-41-low-stock-indicators
Some checks failed
Dev Build / build-test (pull_request) Failing after 1m0s
Dev Build / deploy-dev (pull_request) Has been skipped
Dev Build / notify-success (pull_request) Has been skipped
Dev Build / notify-failure (pull_request) Successful in 3s
2026-04-26 17:23:32 -04:00
d5b5b44dc2 Merge pull request 'CUB-30: Implement PUT /filaments/{id} update endpoint' (#4) from agent/dex/CUB-30-put-filament-endpoint into dev
Some checks failed
Dev Build / deploy-dev (push) Has been cancelled
Dev Build / notify-success (push) Has been cancelled
Dev Build / notify-failure (push) Has been cancelled
Dev Build / build-test (push) Has been cancelled
Reviewed-on: #4
Reviewed-by: Joshua <joshua@cnjmail.com>
2026-04-26 17:23:12 -04:00
0cd8bb1939 Merge branch 'dev' into agent/dex/CUB-30-put-filament-endpoint
Some checks failed
Dev Build / build-test (pull_request) Failing after 1m2s
Dev Build / deploy-dev (pull_request) Has been skipped
Dev Build / notify-success (pull_request) Has been skipped
Dev Build / notify-failure (pull_request) Successful in 3s
2026-04-26 17:22:15 -04:00
311dd2ee7f feat(CUB-31): [Extrudex] Add filament usage tracking model
Some checks failed
Dev Build / build-test (pull_request) Failing after 58s
Dev Build / deploy-dev (pull_request) Has been skipped
Dev Build / notify-success (pull_request) Has been skipped
Dev Build / notify-failure (pull_request) Successful in 4s
2026-04-26 18:35:37 +00:00
7d0369b8e9 chore: add Docker deployment setup and health check wiring
Some checks failed
Dev Build / build-test (push) Failing after 1m1s
Dev Build / deploy-dev (push) Has been skipped
Dev Build / notify-success (push) Has been skipped
Dev Build / notify-failure (push) Successful in 4s
- Add multi-stage Dockerfile for backend (SDK build → ASP.NET runtime,
  non-root user, /health HEALTHCHECK)
- Add docker-compose.dev.yml orchestrating extrudex-api + control-center-web
- Add deploy.sh convenience script wrapping docker compose up --build
- Wire ASP.NET health checks: AddHealthChecks().AddNpgSql() + MapHealthChecks("/health")
- Add backend .dockerignore (comprehensive pattern list)
- Exclude frontend/dist, frontend/node_modules, frontend/.angular from git

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 17:28:06 +00:00
ff1fb621d7 Merge pull request 'CUB-29: Create filament inventory database migration' (#9) from agent/hex/CUB-29-filament-migration into dev
Some checks failed
Dev Build / build-test (push) Failing after 59s
Dev Build / deploy-dev (push) Has been skipped
Dev Build / notify-success (push) Has been skipped
Dev Build / notify-failure (push) Successful in 3s
Reviewed-on: #9
Reviewed-by: Joshua <joshua@cnjmail.com>
2026-04-26 13:01:05 -04:00
c8ac1fa283 Merge branch 'dev' into agent/hex/CUB-29-filament-migration
Some checks failed
Dev Build / build-test (pull_request) Failing after 59s
Dev Build / deploy-dev (pull_request) Has been skipped
Dev Build / notify-success (pull_request) Has been skipped
Dev Build / notify-failure (pull_request) Successful in 4s
2026-04-26 13:00:29 -04:00
Joshua King
4172e21fd1 update dev workflow to use ubuntu-latest for build-test job
Some checks failed
Dev Build / build-test (push) Failing after 3m19s
Dev Build / deploy-dev (push) Has been skipped
Dev Build / notify-success (push) Has been skipped
Dev Build / notify-failure (push) Successful in 3s
Co-authored-by: Copilot <copilot@github.com>
2026-04-26 12:56:06 -04:00
Joshua King
c3def21220 add notifications for Slack on success and failure of dev deployment
Some checks failed
Dev Build / build-test (push) Has been cancelled
Dev Build / deploy-dev (push) Has been cancelled
Dev Build / notify-success (push) Has been cancelled
Dev Build / notify-failure (push) Has been cancelled
2026-04-26 12:52:22 -04:00
Joshua King
458fc9a4e1 add dev workflow for building and deploying backend and frontend
Some checks failed
Dev Build / build-test (push) Has been cancelled
Dev Build / deploy-dev (push) Has been cancelled
2026-04-26 11:51:31 -04:00
cubecraft-agents[bot]
9cd27e213b CUB-30: Implement PUT /filaments/{id} update endpoint
- Add FluentValidation validators for CreateFilamentRequest and UpdateFilamentRequest
  with comprehensive validation rules (required fields, string lengths, hex color format,
  weight constraints including WeightRemainingGrams <= WeightTotalGrams, purchase price range)
- Add FluentValidationFilter action filter that auto-runs FluentValidation validators
  for all API controller actions before execution, returning 400 with structured error details
- Register FluentValidationFilter in DI and add it to MVC controller filters in Program.cs
- PUT endpoint was already implemented in FilamentsController with proper validation,
  404 handling, FK existence checks, serial uniqueness check, and weight constraint check
- This change ensures FluentValidation rules are enforced consistently via the pipeline
2026-04-26 13:26:26 +00:00
cubecraft-agents[bot]
a0cdacc7be CUB-29: Create filament inventory database migration 2026-04-26 13:16:13 +00:00
23 changed files with 4647 additions and 4 deletions

77
.gitea/workflows/dev.yml Normal file
View File

@@ -0,0 +1,77 @@
name: Dev Build
on:
pull_request:
branches: [dev]
push:
branches: [dev]
jobs:
build-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.0.x'
- name: Restore backend
run: dotnet restore
- name: Build backend
run: dotnet build --no-restore --configuration Release
- name: Test backend
run: dotnet test --no-build --configuration Release
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "24"
- name: Install frontend deps
run: npm ci
working-directory: ./frontend
- name: Build frontend
run: npm run build
working-directory: ./frontend
deploy-dev:
needs: build-test
if: gitea.event_name == 'push'
runs-on: ubuntu-latest
steps:
- name: Deploy dev
run: |
echo "${{ secrets.DEV_DEPLOY_SSH_KEY }}" > /tmp/dev_key
chmod 600 /tmp/dev_key
ssh -i /tmp/dev_key -o StrictHostKeyChecking=no \
${{ secrets.DEV_DEPLOY_USER }}@${{ secrets.DEV_DEPLOY_HOST }} \
"${{ secrets.DEV_DEPLOY_PATH }}/deploy.sh"
notify-success:
needs: [build-test, deploy-dev]
if: success() && gitea.event_name == 'push'
runs-on: ubuntu-latest
steps:
- name: Notify Slack success
run: |
curl -X POST -H 'Content-type: application/json' \
--data "{\"text\":\"✅ Extrudex dev deployed successfully from dev branch.\"}" \
"${{ secrets.SLACK_WEBHOOK_URL }}"
notify-failure:
needs: [build-test, deploy-dev]
if: failure()
runs-on: ubuntu-latest
steps:
- name: Notify Slack failure
run: |
curl -X POST -H 'Content-type: application/json' \
--data "{\"text\":\"🚨 Extrudex dev pipeline failed. Check Gitea Actions for details.\"}" \
"${{ secrets.SLACK_WEBHOOK_URL }}"

7
.gitignore vendored
View File

@@ -2,4 +2,9 @@ bin/
obj/ obj/
*.user *.user
*.suo *.suo
.vs/ .vs/
# Frontend build artifacts
frontend/dist/
frontend/node_modules/
frontend/.angular/

27
backend/.dockerignore Normal file
View File

@@ -0,0 +1,27 @@
# Build artifacts
bin/
obj/
# IDE / editor
.vs/
.vscode/
*.user
*.suo
.idea/
# Environment & secrets
appsettings.Development.json
.env
.env.*
# Docker
Dockerfile
.dockerignore
# OS
.DS_Store
Thumbs.db
# Misc
*.md
*.log

View File

@@ -0,0 +1,69 @@
using FluentValidation;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace Extrudex.API.Filters;
/// <summary>
/// Action filter that automatically validates request DTOs using FluentValidation
/// validators registered in DI. Runs before the controller action executes.
/// Returns 400 Bad Request with validation errors if validation fails.
/// </summary>
public class FluentValidationFilter : IAsyncActionFilter
{
private readonly IServiceProvider _serviceProvider;
private readonly ILogger<FluentValidationFilter> _logger;
public FluentValidationFilter(IServiceProvider serviceProvider, ILogger<FluentValidationFilter> logger)
{
_serviceProvider = serviceProvider;
_logger = logger;
}
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
foreach (var argument in context.ActionArguments.Values)
{
if (argument is null) continue;
var argumentType = argument.GetType();
var validatorType = typeof(IValidator<>).MakeGenericType(argumentType);
// Try to resolve a validator for this argument type
var validator = _serviceProvider.GetService(validatorType) as IValidator;
if (validator is null) continue;
_logger.LogDebug("Validating {Type} with {Validator}", argumentType.Name, validator.GetType().Name);
var validationResult = await validator.ValidateAsync(
new ValidationContext<object>(argument), context.HttpContext.RequestAborted);
if (!validationResult.IsValid)
{
foreach (var error in validationResult.Errors)
{
context.ModelState.AddModelError(error.PropertyName, error.ErrorMessage);
}
}
}
if (!context.ModelState.IsValid)
{
var errors = context.ModelState
.Where(kvp => kvp.Value?.Errors.Count > 0)
.ToDictionary(
kvp => kvp.Key,
kvp => kvp.Value!.Errors.Select(e => e.ErrorMessage).ToArray());
context.Result = new BadRequestObjectResult(new
{
title = "Validation failed",
status = 400,
errors
});
return;
}
await next();
}
}

View File

@@ -0,0 +1,108 @@
using Extrudex.API.DTOs.Filaments;
using FluentValidation;
namespace Extrudex.API.Validators;
/// <summary>
/// Validation rules for creating a Filament (Spool) via the /filaments route.
/// Mirrors the domain rules enforced in the controller and ensures consistent
/// validation regardless of the request pipeline entry point.
/// </summary>
public class CreateFilamentRequestValidator : AbstractValidator<CreateFilamentRequest>
{
public CreateFilamentRequestValidator()
{
RuleFor(x => x.MaterialBaseId)
.NotEmpty().WithMessage("MaterialBaseId is required.");
RuleFor(x => x.MaterialFinishId)
.NotEmpty().WithMessage("MaterialFinishId is required.");
RuleFor(x => x.Brand)
.NotEmpty().WithMessage("Brand is required.")
.MaximumLength(200).WithMessage("Brand must not exceed 200 characters.");
RuleFor(x => x.ColorName)
.NotEmpty().WithMessage("ColorName is required.")
.MaximumLength(200).WithMessage("ColorName must not exceed 200 characters.");
RuleFor(x => x.ColorHex)
.NotEmpty().WithMessage("ColorHex is required.")
.Matches(@"^#[0-9A-Fa-f]{6}$").WithMessage("ColorHex must be a valid hex color code (e.g., #FF0000).");
RuleFor(x => x.WeightTotalGrams)
.GreaterThan(0).WithMessage("Total weight must be greater than zero.");
RuleFor(x => x.WeightRemainingGrams)
.GreaterThanOrEqualTo(0).WithMessage("Remaining weight must be non-negative.");
RuleFor(x => x.WeightRemainingGrams)
.LessThanOrEqualTo(x => x.WeightTotalGrams)
.WithMessage("WeightRemainingGrams cannot exceed WeightTotalGrams.");
RuleFor(x => x.FilamentDiameterMm)
.GreaterThan(0).WithMessage("Filament diameter must be greater than zero.");
RuleFor(x => x.SpoolSerial)
.NotEmpty().WithMessage("SpoolSerial is required.")
.MaximumLength(200).WithMessage("SpoolSerial must not exceed 200 characters.");
When(x => x.PurchasePrice.HasValue, () =>
{
RuleFor(x => x.PurchasePrice!.Value)
.GreaterThanOrEqualTo(0).WithMessage("Purchase price must be non-negative.");
});
}
}
/// <summary>
/// Validation rules for updating a Filament (Spool) via the /filaments route.
/// Enforces the same domain rules as creation, plus ensures the updated
/// WeightRemainingGrams does not exceed the updated WeightTotalGrams.
/// </summary>
public class UpdateFilamentRequestValidator : AbstractValidator<UpdateFilamentRequest>
{
public UpdateFilamentRequestValidator()
{
RuleFor(x => x.MaterialBaseId)
.NotEmpty().WithMessage("MaterialBaseId is required.");
RuleFor(x => x.MaterialFinishId)
.NotEmpty().WithMessage("MaterialFinishId is required.");
RuleFor(x => x.Brand)
.NotEmpty().WithMessage("Brand is required.")
.MaximumLength(200).WithMessage("Brand must not exceed 200 characters.");
RuleFor(x => x.ColorName)
.NotEmpty().WithMessage("ColorName is required.")
.MaximumLength(200).WithMessage("ColorName must not exceed 200 characters.");
RuleFor(x => x.ColorHex)
.NotEmpty().WithMessage("ColorHex is required.")
.Matches(@"^#[0-9A-Fa-f]{6}$").WithMessage("ColorHex must be a valid hex color code (e.g., #FF0000).");
RuleFor(x => x.WeightTotalGrams)
.GreaterThan(0).WithMessage("Total weight must be greater than zero.");
RuleFor(x => x.WeightRemainingGrams)
.GreaterThanOrEqualTo(0).WithMessage("Remaining weight must be non-negative.");
RuleFor(x => x.WeightRemainingGrams)
.LessThanOrEqualTo(x => x.WeightTotalGrams)
.WithMessage("WeightRemainingGrams cannot exceed WeightTotalGrams.");
RuleFor(x => x.FilamentDiameterMm)
.GreaterThan(0).WithMessage("Filament diameter must be greater than zero.");
RuleFor(x => x.SpoolSerial)
.NotEmpty().WithMessage("SpoolSerial is required.")
.MaximumLength(200).WithMessage("SpoolSerial must not exceed 200 characters.");
When(x => x.PurchasePrice.HasValue, () =>
{
RuleFor(x => x.PurchasePrice!.Value)
.GreaterThanOrEqualTo(0).WithMessage("Purchase price must be non-negative.");
});
}
}

34
backend/Dockerfile Normal file
View File

@@ -0,0 +1,34 @@
# ── Stage 1: Build ──────────────────────────────────────────
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
WORKDIR /src
# Copy csproj first for layer caching — restores before copying source
COPY Extrudex.csproj .
RUN dotnet restore
# Copy the rest of the source
COPY . .
RUN dotnet publish Extrudex.csproj \
-c Release \
-o /app/publish \
--no-restore
# ── Stage 2: Runtime ────────────────────────────────────────
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS runtime
WORKDIR /app
# Non-root user for security
RUN adduser --disabled-password --gecos "" appuser
USER appuser
# Copy published output from build stage
COPY --from=build /app/publish .
# ASP.NET Core listens on 8080 by default in .NET 8+
EXPOSE 8080
# Health check against /health endpoint
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD curl --fail http://localhost:8080/health || exit 1
ENTRYPOINT ["dotnet", "Extrudex.dll"]

View File

@@ -0,0 +1,73 @@
using Extrudex.Domain.Base;
namespace Extrudex.Domain.Entities;
/// <summary>
/// Tracks filament consumption for a specific print job on a specific spool.
/// Each record captures the grams used, which printer consumed it, and when the
/// usage was recorded. This enables granular per-job usage analytics, COGS
/// reconciliation, and spool weight depletion tracking.
///
/// A single PrintJob may have multiple FilamentUsage records if multiple spools
/// were consumed (e.g., multi-material prints via AMS).
/// </summary>
public class FilamentUsage : AuditableEntity
{
/// <summary>
/// Foreign key to the print job that consumed this filament.
/// A usage record is always tied to a print job.
/// </summary>
public Guid PrintJobId { get; set; }
/// <summary>
/// Navigation to the print job that consumed this filament.
/// </summary>
public PrintJob PrintJob { get; set; } = null!;
/// <summary>
/// Foreign key to the spool (filament) that provided the material.
/// Links usage back to the specific physical spool for inventory tracking.
/// </summary>
public Guid SpoolId { get; set; }
/// <summary>
/// Navigation to the spool that provided the material.
/// </summary>
public Spool Spool { get; set; } = null!;
/// <summary>
/// Foreign key to the printer that executed the print job.
/// Denormalized from PrintJob for direct querying of per-printer usage.
/// </summary>
public Guid PrinterId { get; set; }
/// <summary>
/// Navigation to the printer that executed the print job.
/// </summary>
public Printer Printer { get; set; } = null!;
/// <summary>
/// Grams of filament consumed during this print job.
/// Derived from mm_extruded × cross_section_area × material_density,
/// or measured directly from AMS weight delta.
/// </summary>
public decimal GramsUsed { get; set; }
/// <summary>
/// Millimeters of filament extruded for this usage record.
/// The primary physical measurement; grams_used is derived from this.
/// </summary>
public decimal MmExtruded { get; set; }
/// <summary>
/// Timestamp when this usage record was created (UTC).
/// Represents when the usage was first logged, which may differ from
/// the print job's started_at or completed_at timestamps.
/// </summary>
public DateTime RecordedAt { get; set; } = DateTime.UtcNow;
/// <summary>
/// Optional notes about this usage record (e.g., "AMS tray 3", "manual weight check").
/// </summary>
public string? Notes { get; set; }
}

View File

@@ -97,4 +97,10 @@ public class PrintJob : AuditableEntity
/// Optional notes about the print job (e.g., "First layer adhesion issues"). /// Optional notes about the print job (e.g., "First layer adhesion issues").
/// </summary> /// </summary>
public string? Notes { get; set; } public string? Notes { get; set; }
/// <summary>
/// Navigation collection of filament usage records for this print job.
/// Enables tracking granular per-spool consumption within a print.
/// </summary>
public ICollection<FilamentUsage> FilamentUsages { get; set; } = new List<FilamentUsage>();
} }

View File

@@ -94,4 +94,10 @@ public class Printer : AuditableEntity
/// Navigation collection of print jobs executed on this printer. /// Navigation collection of print jobs executed on this printer.
/// </summary> /// </summary>
public ICollection<PrintJob> PrintJobs { get; set; } = new List<PrintJob>(); public ICollection<PrintJob> PrintJobs { get; set; } = new List<PrintJob>();
/// <summary>
/// Navigation collection of filament usage records tracking consumption on this printer.
/// Enables querying per-printer filament usage and COGS.
/// </summary>
public ICollection<FilamentUsage> FilamentUsages { get; set; } = new List<FilamentUsage>();
} }

View File

@@ -102,4 +102,10 @@ public class Spool : AuditableEntity
/// Navigation collection of print jobs that consumed filament from this spool. /// Navigation collection of print jobs that consumed filament from this spool.
/// </summary> /// </summary>
public ICollection<PrintJob> PrintJobs { get; set; } = new List<PrintJob>(); public ICollection<PrintJob> PrintJobs { get; set; } = new List<PrintJob>();
/// <summary>
/// Navigation collection of filament usage records tracking consumption from this spool.
/// Enables querying how much filament was consumed per print job.
/// </summary>
public ICollection<FilamentUsage> FilamentUsages { get; set; } = new List<FilamentUsage>();
} }

View File

@@ -9,6 +9,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="AspNetCore.HealthChecks.NpgSql" Version="9.0.0" />
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="12.1.1" /> <PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="12.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.3" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.3" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.3" />

View File

@@ -49,15 +49,26 @@ public abstract class BaseEntityConfiguration<TEntity> : IEntityTypeConfiguratio
} }
/// <summary> /// <summary>
/// Converts PascalCase or camelCase to snake_case. /// Converts PascalCase or camelCase entity name to plural snake_case table name.
/// e.g. MaterialBase → material_bases, AmsSlot → ams_slots
/// </summary> /// </summary>
protected static string ToSnakeCase(string name) protected static string ToSnakeCase(string name)
{ {
return string.Concat( var snake = string.Concat(
name.Select((ch, i) => name.Select((ch, i) =>
i > 0 && char.IsUpper(ch) && (char.IsLower(name[i - 1]) || (i + 1 < name.Length && char.IsLower(name[i + 1]))) i > 0 && char.IsUpper(ch) && (char.IsLower(name[i - 1]) || (i + 1 < name.Length && char.IsLower(name[i + 1])))
? "_" + ch ? "_" + ch
: ch.ToString())) : ch.ToString()))
.ToLowerInvariant(); .ToLowerInvariant();
// Pluralize: add 's' (handles most cases; irregular plurals handled explicitly if needed)
// Special cases: already_plural stays, 'y' → 'ies', 's'/'x'/'ch'/'sh' → 'es'
if (snake.EndsWith("s"))
return snake; // Already plural or ambiguous — leave as-is
if (snake.EndsWith("y") && !snake.EndsWith("ay") && !snake.EndsWith("ey") && !snake.EndsWith("oy") && !snake.EndsWith("uy"))
return snake[..^1] + "ies";
if (snake.EndsWith("x") || snake.EndsWith("ch") || snake.EndsWith("sh"))
return snake + "es";
return snake + "s";
} }
} }

View File

@@ -0,0 +1,83 @@
using Extrudex.Domain.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace Extrudex.Infrastructure.Data.Configurations;
public class FilamentUsageConfiguration : BaseEntityConfiguration<FilamentUsage>
{
public override void Configure(EntityTypeBuilder<FilamentUsage> builder)
{
base.Configure(builder);
builder.Property(e => e.PrintJobId)
.HasColumnName("print_job_id")
.IsRequired();
builder.Property(e => e.SpoolId)
.HasColumnName("spool_id")
.IsRequired();
builder.Property(e => e.PrinterId)
.HasColumnName("printer_id")
.IsRequired();
builder.Property(e => e.GramsUsed)
.HasColumnName("grams_used")
.HasPrecision(10, 2)
.IsRequired();
builder.Property(e => e.MmExtruded)
.HasColumnName("mm_extruded")
.HasPrecision(12, 2)
.IsRequired();
builder.Property(e => e.RecordedAt)
.HasColumnName("recorded_at")
.HasDefaultValueSql("now() at time zone 'utc'")
.IsRequired();
builder.Property(e => e.Notes)
.HasColumnName("notes")
.HasMaxLength(2000);
// Index on print_job_id for querying usage by print job
builder.HasIndex(e => e.PrintJobId)
.HasDatabaseName("ix_filament_usages_print_job_id");
// Index on spool_id for querying usage by spool (filament)
builder.HasIndex(e => e.SpoolId)
.HasDatabaseName("ix_filament_usages_spool_id");
// Index on printer_id for querying usage by printer
builder.HasIndex(e => e.PrinterId)
.HasDatabaseName("ix_filament_usages_printer_id");
// Index on recorded_at for time-range queries
builder.HasIndex(e => e.RecordedAt)
.HasDatabaseName("ix_filament_usages_recorded_at");
// Composite index for querying usage by spool within a date range
builder.HasIndex(e => new { e.SpoolId, e.RecordedAt })
.HasDatabaseName("ix_filament_usages_spool_id_recorded_at");
// Relationships
builder.HasOne(e => e.PrintJob)
.WithMany(e => e.FilamentUsages)
.HasForeignKey(e => e.PrintJobId)
.HasConstraintName("fk_filament_usages_print_job")
.OnDelete(DeleteBehavior.Cascade);
builder.HasOne(e => e.Spool)
.WithMany(e => e.FilamentUsages)
.HasForeignKey(e => e.SpoolId)
.HasConstraintName("fk_filament_usages_spool")
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(e => e.Printer)
.WithMany(e => e.FilamentUsages)
.HasForeignKey(e => e.PrinterId)
.HasConstraintName("fk_filament_usages_printer")
.OnDelete(DeleteBehavior.Restrict);
}
}

View File

@@ -77,6 +77,14 @@ public class SpoolConfiguration : BaseEntityConfiguration<Spool>
builder.HasIndex(e => e.MaterialBaseId) builder.HasIndex(e => e.MaterialBaseId)
.HasDatabaseName("ix_spools_material_base_id"); .HasDatabaseName("ix_spools_material_base_id");
// Index on material_finish_id for spool filtering
builder.HasIndex(e => e.MaterialFinishId)
.HasDatabaseName("ix_spools_material_finish_id");
// Index on material_modifier_id for spool filtering
builder.HasIndex(e => e.MaterialModifierId)
.HasDatabaseName("ix_spools_material_modifier_id");
// Index on is_active for active spool queries // Index on is_active for active spool queries
builder.HasIndex(e => e.IsActive) builder.HasIndex(e => e.IsActive)
.HasDatabaseName("ix_spools_is_active"); .HasDatabaseName("ix_spools_is_active");

View File

@@ -23,6 +23,7 @@ public class ExtrudexDbContext : DbContext
public DbSet<AmsUnit> AmsUnits => Set<AmsUnit>(); public DbSet<AmsUnit> AmsUnits => Set<AmsUnit>();
public DbSet<AmsSlot> AmsSlots => Set<AmsSlot>(); public DbSet<AmsSlot> AmsSlots => Set<AmsSlot>();
public DbSet<PrintJob> PrintJobs => Set<PrintJob>(); public DbSet<PrintJob> PrintJobs => Set<PrintJob>();
public DbSet<FilamentUsage> FilamentUsages => Set<FilamentUsage>();
protected override void OnModelCreating(ModelBuilder modelBuilder) protected override void OnModelCreating(ModelBuilder modelBuilder)
{ {

View File

@@ -0,0 +1,958 @@
// <auto-generated />
using System;
using Extrudex.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Extrudex.Infrastructure.Data.Migrations
{
[DbContext(typeof(ExtrudexDbContext))]
[Migration("20260426131419_InitialCreate")]
partial class InitialCreate
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "9.0.3")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Extrudex.Domain.Entities.AmsSlot", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uuid")
.HasColumnName("id");
b.Property<Guid>("AmsUnitId")
.HasColumnType("uuid")
.HasColumnName("ams_unit_id");
b.Property<DateTime>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at")
.HasDefaultValueSql("now() at time zone 'utc'");
b.Property<decimal?>("RemainingWeightG")
.HasPrecision(10, 2)
.HasColumnType("numeric(10,2)")
.HasColumnName("remaining_weight_g");
b.Property<Guid?>("SpoolId")
.HasColumnType("uuid")
.HasColumnName("spool_id");
b.Property<int>("TrayIndex")
.HasColumnType("integer")
.HasColumnName("tray_index");
b.Property<DateTime>("UpdatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at")
.HasDefaultValueSql("now() at time zone 'utc'");
b.HasKey("Id");
b.HasIndex("SpoolId")
.HasDatabaseName("ix_ams_slots_spool_id");
b.HasIndex("AmsUnitId", "TrayIndex")
.IsUnique()
.HasDatabaseName("ix_ams_slots_ams_unit_id_tray_index");
b.ToTable("ams_slots", (string)null);
});
modelBuilder.Entity("Extrudex.Domain.Entities.AmsUnit", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uuid")
.HasColumnName("id");
b.Property<DateTime>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at")
.HasDefaultValueSql("now() at time zone 'utc'");
b.Property<Guid>("PrinterId")
.HasColumnType("uuid")
.HasColumnName("printer_id");
b.Property<int>("UnitIndex")
.HasColumnType("integer")
.HasColumnName("unit_index");
b.Property<DateTime>("UpdatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at")
.HasDefaultValueSql("now() at time zone 'utc'");
b.HasKey("Id");
b.HasIndex("PrinterId", "UnitIndex")
.IsUnique()
.HasDatabaseName("ix_ams_units_printer_id_unit_index");
b.ToTable("ams_units", (string)null);
});
modelBuilder.Entity("Extrudex.Domain.Entities.MaterialBase", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uuid")
.HasColumnName("id");
b.Property<DateTime>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at")
.HasDefaultValueSql("now() at time zone 'utc'");
b.Property<decimal>("DensityGperCm3")
.HasPrecision(10, 4)
.HasColumnType("numeric(10,4)")
.HasColumnName("density_g_per_cm3");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("character varying(100)")
.HasColumnName("name");
b.Property<DateTime>("UpdatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at")
.HasDefaultValueSql("now() at time zone 'utc'");
b.HasKey("Id");
b.HasIndex("Name")
.IsUnique()
.HasDatabaseName("ix_material_bases_name");
b.ToTable("material_bases", (string)null);
b.HasData(
new
{
Id = new Guid("10000000-0000-0000-0000-000000000001"),
CreatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(1096),
DensityGperCm3 = 1.24m,
Name = "PLA",
UpdatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(1096)
},
new
{
Id = new Guid("10000000-0000-0000-0000-000000000002"),
CreatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(1620),
DensityGperCm3 = 1.27m,
Name = "PETG",
UpdatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(1620)
},
new
{
Id = new Guid("10000000-0000-0000-0000-000000000003"),
CreatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(1630),
DensityGperCm3 = 1.04m,
Name = "ABS",
UpdatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(1630)
},
new
{
Id = new Guid("10000000-0000-0000-0000-000000000004"),
CreatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(1638),
DensityGperCm3 = 1.07m,
Name = "ASA",
UpdatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(1638)
},
new
{
Id = new Guid("10000000-0000-0000-0000-000000000005"),
CreatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(1645),
DensityGperCm3 = 1.21m,
Name = "TPU",
UpdatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(1645)
},
new
{
Id = new Guid("10000000-0000-0000-0000-000000000006"),
CreatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(1651),
DensityGperCm3 = 1.14m,
Name = "Nylon",
UpdatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(1652)
});
});
modelBuilder.Entity("Extrudex.Domain.Entities.MaterialFinish", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uuid")
.HasColumnName("id");
b.Property<DateTime>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at")
.HasDefaultValueSql("now() at time zone 'utc'");
b.Property<Guid>("MaterialBaseId")
.HasColumnType("uuid")
.HasColumnName("material_base_id");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("character varying(100)")
.HasColumnName("name");
b.Property<DateTime>("UpdatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at")
.HasDefaultValueSql("now() at time zone 'utc'");
b.HasKey("Id");
b.HasIndex("MaterialBaseId", "Name")
.IsUnique()
.HasDatabaseName("ix_material_finishes_material_base_id_name");
b.ToTable("material_finishes", (string)null);
b.HasData(
new
{
Id = new Guid("20000000-0000-0000-0000-000000000001"),
CreatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(1850),
MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000001"),
Name = "Basic",
UpdatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(1850)
},
new
{
Id = new Guid("20000000-0000-0000-0000-000000000002"),
CreatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2041),
MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000001"),
Name = "Matte",
UpdatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2041)
},
new
{
Id = new Guid("20000000-0000-0000-0000-000000000003"),
CreatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2049),
MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000001"),
Name = "Silk",
UpdatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2049)
},
new
{
Id = new Guid("20000000-0000-0000-0000-000000000004"),
CreatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2055),
MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000001"),
Name = "Glitter",
UpdatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2056)
},
new
{
Id = new Guid("20000000-0000-0000-0000-000000000005"),
CreatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2062),
MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000001"),
Name = "Marble",
UpdatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2062)
},
new
{
Id = new Guid("20000000-0000-0000-0000-000000000006"),
CreatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2068),
MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000001"),
Name = "Sparkle",
UpdatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2068)
},
new
{
Id = new Guid("20000000-0000-0000-0000-000000000007"),
CreatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2075),
MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000002"),
Name = "Basic",
UpdatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2075)
},
new
{
Id = new Guid("20000000-0000-0000-0000-000000000008"),
CreatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2081),
MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000002"),
Name = "Matte",
UpdatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2081)
},
new
{
Id = new Guid("20000000-0000-0000-0000-000000000009"),
CreatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2100),
MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000002"),
Name = "Silk",
UpdatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2100)
},
new
{
Id = new Guid("20000000-0000-0000-0000-000000000010"),
CreatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2107),
MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000003"),
Name = "Basic",
UpdatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2107)
},
new
{
Id = new Guid("20000000-0000-0000-0000-000000000011"),
CreatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2113),
MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000003"),
Name = "Matte",
UpdatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2113)
},
new
{
Id = new Guid("20000000-0000-0000-0000-000000000012"),
CreatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2120),
MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000004"),
Name = "Basic",
UpdatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2120)
},
new
{
Id = new Guid("20000000-0000-0000-0000-000000000013"),
CreatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2126),
MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000004"),
Name = "Matte",
UpdatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2126)
},
new
{
Id = new Guid("20000000-0000-0000-0000-000000000014"),
CreatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2132),
MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000005"),
Name = "Basic",
UpdatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2133)
},
new
{
Id = new Guid("20000000-0000-0000-0000-000000000015"),
CreatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2139),
MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000006"),
Name = "Basic",
UpdatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2139)
});
});
modelBuilder.Entity("Extrudex.Domain.Entities.MaterialModifier", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uuid")
.HasColumnName("id");
b.Property<DateTime>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at")
.HasDefaultValueSql("now() at time zone 'utc'");
b.Property<Guid>("MaterialBaseId")
.HasColumnType("uuid")
.HasColumnName("material_base_id");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("character varying(100)")
.HasColumnName("name");
b.Property<DateTime>("UpdatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at")
.HasDefaultValueSql("now() at time zone 'utc'");
b.HasKey("Id");
b.HasIndex("MaterialBaseId", "Name")
.IsUnique()
.HasDatabaseName("ix_material_modifiers_material_base_id_name");
b.ToTable("material_modifiers", (string)null);
b.HasData(
new
{
Id = new Guid("30000000-0000-0000-0000-000000000001"),
CreatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2304),
MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000001"),
Name = "Carbon Fiber",
UpdatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2304)
},
new
{
Id = new Guid("30000000-0000-0000-0000-000000000002"),
CreatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2463),
MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000001"),
Name = "Glass Fiber",
UpdatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2463)
},
new
{
Id = new Guid("30000000-0000-0000-0000-000000000003"),
CreatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2471),
MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000001"),
Name = "Wood Fill",
UpdatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2471)
},
new
{
Id = new Guid("30000000-0000-0000-0000-000000000004"),
CreatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2477),
MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000001"),
Name = "Glow-in-the-Dark",
UpdatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2478)
},
new
{
Id = new Guid("30000000-0000-0000-0000-000000000005"),
CreatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2484),
MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000002"),
Name = "Carbon Fiber",
UpdatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2484)
},
new
{
Id = new Guid("30000000-0000-0000-0000-000000000006"),
CreatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2490),
MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000002"),
Name = "Glass Fiber",
UpdatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2491)
},
new
{
Id = new Guid("30000000-0000-0000-0000-000000000007"),
CreatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2497),
MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000003"),
Name = "Carbon Fiber",
UpdatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2497)
},
new
{
Id = new Guid("30000000-0000-0000-0000-000000000008"),
CreatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2503),
MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000003"),
Name = "Glass Fiber",
UpdatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2503)
},
new
{
Id = new Guid("30000000-0000-0000-0000-000000000009"),
CreatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2510),
MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000004"),
Name = "Carbon Fiber",
UpdatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2510)
},
new
{
Id = new Guid("30000000-0000-0000-0000-000000000010"),
CreatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2516),
MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000006"),
Name = "Carbon Fiber",
UpdatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2516)
},
new
{
Id = new Guid("30000000-0000-0000-0000-000000000011"),
CreatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2522),
MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000006"),
Name = "Glass Fiber",
UpdatedAt = new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2523)
});
});
modelBuilder.Entity("Extrudex.Domain.Entities.PrintJob", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uuid")
.HasColumnName("id");
b.Property<DateTime?>("CompletedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("completed_at");
b.Property<decimal?>("CostPerPrint")
.HasPrecision(10, 4)
.HasColumnType("numeric(10,4)")
.HasColumnName("cost_per_print");
b.Property<DateTime>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at")
.HasDefaultValueSql("now() at time zone 'utc'");
b.Property<string>("DataSource")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("data_source");
b.Property<decimal>("FilamentDiameterAtPrintMm")
.HasPrecision(6, 3)
.HasColumnType("numeric(6,3)")
.HasColumnName("filament_diameter_at_print_mm");
b.Property<string>("GcodeFilePath")
.HasMaxLength(1000)
.HasColumnType("character varying(1000)")
.HasColumnName("gcode_file_path");
b.Property<decimal>("GramsDerived")
.HasPrecision(10, 2)
.HasColumnType("numeric(10,2)")
.HasColumnName("grams_derived");
b.Property<decimal>("MaterialDensityAtPrint")
.HasPrecision(10, 4)
.HasColumnType("numeric(10,4)")
.HasColumnName("material_density_at_print");
b.Property<decimal>("MmExtruded")
.HasPrecision(12, 2)
.HasColumnType("numeric(12,2)")
.HasColumnName("mm_extruded");
b.Property<string>("Notes")
.HasMaxLength(2000)
.HasColumnType("character varying(2000)")
.HasColumnName("notes");
b.Property<string>("PrintName")
.IsRequired()
.HasMaxLength(500)
.HasColumnType("character varying(500)")
.HasColumnName("print_name");
b.Property<Guid>("PrinterId")
.HasColumnType("uuid")
.HasColumnName("printer_id");
b.Property<Guid>("SpoolId")
.HasColumnType("uuid")
.HasColumnName("spool_id");
b.Property<DateTime?>("StartedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("started_at");
b.Property<string>("Status")
.IsRequired()
.ValueGeneratedOnAdd()
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasDefaultValue("Queued")
.HasColumnName("status");
b.Property<DateTime>("UpdatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at")
.HasDefaultValueSql("now() at time zone 'utc'");
b.HasKey("Id");
b.HasIndex("DataSource")
.HasDatabaseName("ix_print_jobs_data_source");
b.HasIndex("PrinterId")
.HasDatabaseName("ix_print_jobs_printer_id");
b.HasIndex("SpoolId")
.HasDatabaseName("ix_print_jobs_spool_id");
b.HasIndex("Status")
.HasDatabaseName("ix_print_jobs_status");
b.ToTable("print_jobs", (string)null);
});
modelBuilder.Entity("Extrudex.Domain.Entities.Printer", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uuid")
.HasColumnName("id");
b.Property<string>("ApiKey")
.IsRequired()
.HasMaxLength(500)
.HasColumnType("character varying(500)")
.HasColumnName("api_key");
b.Property<string>("ConnectionType")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("connection_type");
b.Property<DateTime>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at")
.HasDefaultValueSql("now() at time zone 'utc'");
b.Property<string>("HostnameOrIp")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("character varying(255)")
.HasColumnName("hostname_or_ip");
b.Property<bool>("IsActive")
.ValueGeneratedOnAdd()
.HasColumnType("boolean")
.HasDefaultValue(true)
.HasColumnName("is_active");
b.Property<DateTime?>("LastSeenAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("last_seen_at");
b.Property<string>("Manufacturer")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("character varying(200)")
.HasColumnName("manufacturer");
b.Property<string>("Model")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("character varying(200)")
.HasColumnName("model");
b.Property<string>("MqttPassword")
.IsRequired()
.HasMaxLength(500)
.HasColumnType("character varying(500)")
.HasColumnName("mqtt_password");
b.Property<bool>("MqttUseTls")
.ValueGeneratedOnAdd()
.HasColumnType("boolean")
.HasDefaultValue(false)
.HasColumnName("mqtt_use_tls");
b.Property<string>("MqttUsername")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("character varying(200)")
.HasColumnName("mqtt_username");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("character varying(200)")
.HasColumnName("name");
b.Property<int>("Port")
.HasColumnType("integer")
.HasColumnName("port");
b.Property<string>("PrinterType")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("printer_type");
b.Property<string>("Status")
.IsRequired()
.ValueGeneratedOnAdd()
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasDefaultValue("Offline")
.HasColumnName("status");
b.Property<DateTime>("UpdatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at")
.HasDefaultValueSql("now() at time zone 'utc'");
b.HasKey("Id");
b.HasIndex("ConnectionType")
.HasDatabaseName("ix_printers_connection_type");
b.HasIndex("IsActive")
.HasDatabaseName("ix_printers_is_active");
b.HasIndex("PrinterType")
.HasDatabaseName("ix_printers_printer_type");
b.HasIndex("Status")
.HasDatabaseName("ix_printers_status");
b.ToTable("printers", (string)null);
});
modelBuilder.Entity("Extrudex.Domain.Entities.Spool", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uuid")
.HasColumnName("id");
b.Property<string>("Brand")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("character varying(200)")
.HasColumnName("brand");
b.Property<string>("ColorHex")
.IsRequired()
.HasMaxLength(7)
.HasColumnType("character varying(7)")
.HasColumnName("color_hex");
b.Property<string>("ColorName")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("character varying(200)")
.HasColumnName("color_name");
b.Property<DateTime>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at")
.HasDefaultValueSql("now() at time zone 'utc'");
b.Property<decimal>("FilamentDiameterMm")
.HasPrecision(6, 3)
.HasColumnType("numeric(6,3)")
.HasColumnName("filament_diameter_mm");
b.Property<bool>("IsActive")
.ValueGeneratedOnAdd()
.HasColumnType("boolean")
.HasDefaultValue(true)
.HasColumnName("is_active");
b.Property<Guid>("MaterialBaseId")
.HasColumnType("uuid")
.HasColumnName("material_base_id");
b.Property<Guid>("MaterialFinishId")
.HasColumnType("uuid")
.HasColumnName("material_finish_id");
b.Property<Guid?>("MaterialModifierId")
.HasColumnType("uuid")
.HasColumnName("material_modifier_id");
b.Property<DateTime?>("PurchaseDate")
.HasColumnType("timestamp with time zone")
.HasColumnName("purchase_date");
b.Property<decimal?>("PurchasePrice")
.HasPrecision(10, 2)
.HasColumnType("numeric(10,2)")
.HasColumnName("purchase_price");
b.Property<string>("SpoolSerial")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("character varying(200)")
.HasColumnName("spool_serial");
b.Property<DateTime>("UpdatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at")
.HasDefaultValueSql("now() at time zone 'utc'");
b.Property<decimal>("WeightRemainingGrams")
.HasPrecision(10, 2)
.HasColumnType("numeric(10,2)")
.HasColumnName("weight_remaining_grams");
b.Property<decimal>("WeightTotalGrams")
.HasPrecision(10, 2)
.HasColumnType("numeric(10,2)")
.HasColumnName("weight_total_grams");
b.HasKey("Id");
b.HasIndex("IsActive")
.HasDatabaseName("ix_spools_is_active");
b.HasIndex("MaterialBaseId")
.HasDatabaseName("ix_spools_material_base_id");
b.HasIndex("MaterialFinishId")
.HasDatabaseName("ix_spools_material_finish_id");
b.HasIndex("MaterialModifierId")
.HasDatabaseName("ix_spools_material_modifier_id");
b.HasIndex("SpoolSerial")
.IsUnique()
.HasDatabaseName("ix_spools_spool_serial");
b.ToTable("spools", (string)null);
});
modelBuilder.Entity("Extrudex.Domain.Entities.AmsSlot", b =>
{
b.HasOne("Extrudex.Domain.Entities.AmsUnit", "AmsUnit")
.WithMany("Slots")
.HasForeignKey("AmsUnitId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_ams_slots_ams_unit");
b.HasOne("Extrudex.Domain.Entities.Spool", "Spool")
.WithMany("AmsSlots")
.HasForeignKey("SpoolId")
.OnDelete(DeleteBehavior.SetNull)
.HasConstraintName("fk_ams_slots_spool");
b.Navigation("AmsUnit");
b.Navigation("Spool");
});
modelBuilder.Entity("Extrudex.Domain.Entities.AmsUnit", b =>
{
b.HasOne("Extrudex.Domain.Entities.Printer", "Printer")
.WithMany("AmsUnits")
.HasForeignKey("PrinterId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_ams_units_printer");
b.Navigation("Printer");
});
modelBuilder.Entity("Extrudex.Domain.Entities.MaterialFinish", b =>
{
b.HasOne("Extrudex.Domain.Entities.MaterialBase", "MaterialBase")
.WithMany("Finishes")
.HasForeignKey("MaterialBaseId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired()
.HasConstraintName("fk_material_finishes_material_base");
b.Navigation("MaterialBase");
});
modelBuilder.Entity("Extrudex.Domain.Entities.MaterialModifier", b =>
{
b.HasOne("Extrudex.Domain.Entities.MaterialBase", "MaterialBase")
.WithMany("Modifiers")
.HasForeignKey("MaterialBaseId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired()
.HasConstraintName("fk_material_modifiers_material_base");
b.Navigation("MaterialBase");
});
modelBuilder.Entity("Extrudex.Domain.Entities.PrintJob", b =>
{
b.HasOne("Extrudex.Domain.Entities.Printer", "Printer")
.WithMany("PrintJobs")
.HasForeignKey("PrinterId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired()
.HasConstraintName("fk_print_jobs_printer");
b.HasOne("Extrudex.Domain.Entities.Spool", "Spool")
.WithMany("PrintJobs")
.HasForeignKey("SpoolId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired()
.HasConstraintName("fk_print_jobs_spool");
b.Navigation("Printer");
b.Navigation("Spool");
});
modelBuilder.Entity("Extrudex.Domain.Entities.Spool", b =>
{
b.HasOne("Extrudex.Domain.Entities.MaterialBase", "MaterialBase")
.WithMany("Spools")
.HasForeignKey("MaterialBaseId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired()
.HasConstraintName("fk_spools_material_base");
b.HasOne("Extrudex.Domain.Entities.MaterialFinish", "MaterialFinish")
.WithMany("Spools")
.HasForeignKey("MaterialFinishId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired()
.HasConstraintName("fk_spools_material_finish");
b.HasOne("Extrudex.Domain.Entities.MaterialModifier", "MaterialModifier")
.WithMany("Spools")
.HasForeignKey("MaterialModifierId")
.OnDelete(DeleteBehavior.SetNull)
.HasConstraintName("fk_spools_material_modifier");
b.Navigation("MaterialBase");
b.Navigation("MaterialFinish");
b.Navigation("MaterialModifier");
});
modelBuilder.Entity("Extrudex.Domain.Entities.AmsUnit", b =>
{
b.Navigation("Slots");
});
modelBuilder.Entity("Extrudex.Domain.Entities.MaterialBase", b =>
{
b.Navigation("Finishes");
b.Navigation("Modifiers");
b.Navigation("Spools");
});
modelBuilder.Entity("Extrudex.Domain.Entities.MaterialFinish", b =>
{
b.Navigation("Spools");
});
modelBuilder.Entity("Extrudex.Domain.Entities.MaterialModifier", b =>
{
b.Navigation("Spools");
});
modelBuilder.Entity("Extrudex.Domain.Entities.Printer", b =>
{
b.Navigation("AmsUnits");
b.Navigation("PrintJobs");
});
modelBuilder.Entity("Extrudex.Domain.Entities.Spool", b =>
{
b.Navigation("AmsSlots");
b.Navigation("PrintJobs");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,416 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional
namespace Extrudex.Infrastructure.Data.Migrations
{
/// <inheritdoc />
public partial class InitialCreate : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "material_bases",
columns: table => new
{
id = table.Column<Guid>(type: "uuid", nullable: false),
name = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: false),
density_g_per_cm3 = table.Column<decimal>(type: "numeric(10,4)", precision: 10, scale: 4, nullable: false),
created_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now() at time zone 'utc'"),
updated_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now() at time zone 'utc'")
},
constraints: table =>
{
table.PrimaryKey("PK_material_bases", x => x.id);
});
migrationBuilder.CreateTable(
name: "printers",
columns: table => new
{
id = table.Column<Guid>(type: "uuid", nullable: false),
status = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false, defaultValue: "Offline"),
name = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
manufacturer = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
model = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
printer_type = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false),
connection_type = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false),
hostname_or_ip = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
port = table.Column<int>(type: "integer", nullable: false),
mqtt_username = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
mqtt_password = table.Column<string>(type: "character varying(500)", maxLength: 500, nullable: false),
mqtt_use_tls = table.Column<bool>(type: "boolean", nullable: false, defaultValue: false),
api_key = table.Column<string>(type: "character varying(500)", maxLength: 500, nullable: false),
is_active = table.Column<bool>(type: "boolean", nullable: false, defaultValue: true),
last_seen_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
created_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now() at time zone 'utc'"),
updated_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now() at time zone 'utc'")
},
constraints: table =>
{
table.PrimaryKey("PK_printers", x => x.id);
});
migrationBuilder.CreateTable(
name: "material_finishes",
columns: table => new
{
id = table.Column<Guid>(type: "uuid", nullable: false),
name = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: false),
material_base_id = table.Column<Guid>(type: "uuid", nullable: false),
created_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now() at time zone 'utc'"),
updated_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now() at time zone 'utc'")
},
constraints: table =>
{
table.PrimaryKey("PK_material_finishes", x => x.id);
table.ForeignKey(
name: "fk_material_finishes_material_base",
column: x => x.material_base_id,
principalTable: "material_bases",
principalColumn: "id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "material_modifiers",
columns: table => new
{
id = table.Column<Guid>(type: "uuid", nullable: false),
name = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: false),
material_base_id = table.Column<Guid>(type: "uuid", nullable: false),
created_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now() at time zone 'utc'"),
updated_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now() at time zone 'utc'")
},
constraints: table =>
{
table.PrimaryKey("PK_material_modifiers", x => x.id);
table.ForeignKey(
name: "fk_material_modifiers_material_base",
column: x => x.material_base_id,
principalTable: "material_bases",
principalColumn: "id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "ams_units",
columns: table => new
{
id = table.Column<Guid>(type: "uuid", nullable: false),
unit_index = table.Column<int>(type: "integer", nullable: false),
printer_id = table.Column<Guid>(type: "uuid", nullable: false),
created_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now() at time zone 'utc'"),
updated_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now() at time zone 'utc'")
},
constraints: table =>
{
table.PrimaryKey("PK_ams_units", x => x.id);
table.ForeignKey(
name: "fk_ams_units_printer",
column: x => x.printer_id,
principalTable: "printers",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "spools",
columns: table => new
{
id = table.Column<Guid>(type: "uuid", nullable: false),
material_base_id = table.Column<Guid>(type: "uuid", nullable: false),
material_finish_id = table.Column<Guid>(type: "uuid", nullable: false),
material_modifier_id = table.Column<Guid>(type: "uuid", nullable: true),
brand = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
color_name = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
color_hex = table.Column<string>(type: "character varying(7)", maxLength: 7, nullable: false),
weight_total_grams = table.Column<decimal>(type: "numeric(10,2)", precision: 10, scale: 2, nullable: false),
weight_remaining_grams = table.Column<decimal>(type: "numeric(10,2)", precision: 10, scale: 2, nullable: false),
filament_diameter_mm = table.Column<decimal>(type: "numeric(6,3)", precision: 6, scale: 3, nullable: false),
spool_serial = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
purchase_price = table.Column<decimal>(type: "numeric(10,2)", precision: 10, scale: 2, nullable: true),
purchase_date = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
is_active = table.Column<bool>(type: "boolean", nullable: false, defaultValue: true),
created_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now() at time zone 'utc'"),
updated_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now() at time zone 'utc'")
},
constraints: table =>
{
table.PrimaryKey("PK_spools", x => x.id);
table.ForeignKey(
name: "fk_spools_material_base",
column: x => x.material_base_id,
principalTable: "material_bases",
principalColumn: "id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "fk_spools_material_finish",
column: x => x.material_finish_id,
principalTable: "material_finishes",
principalColumn: "id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "fk_spools_material_modifier",
column: x => x.material_modifier_id,
principalTable: "material_modifiers",
principalColumn: "id",
onDelete: ReferentialAction.SetNull);
});
migrationBuilder.CreateTable(
name: "ams_slots",
columns: table => new
{
id = table.Column<Guid>(type: "uuid", nullable: false),
tray_index = table.Column<int>(type: "integer", nullable: false),
ams_unit_id = table.Column<Guid>(type: "uuid", nullable: false),
spool_id = table.Column<Guid>(type: "uuid", nullable: true),
remaining_weight_g = table.Column<decimal>(type: "numeric(10,2)", precision: 10, scale: 2, nullable: true),
created_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now() at time zone 'utc'"),
updated_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now() at time zone 'utc'")
},
constraints: table =>
{
table.PrimaryKey("PK_ams_slots", x => x.id);
table.ForeignKey(
name: "fk_ams_slots_ams_unit",
column: x => x.ams_unit_id,
principalTable: "ams_units",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "fk_ams_slots_spool",
column: x => x.spool_id,
principalTable: "spools",
principalColumn: "id",
onDelete: ReferentialAction.SetNull);
});
migrationBuilder.CreateTable(
name: "print_jobs",
columns: table => new
{
id = table.Column<Guid>(type: "uuid", nullable: false),
printer_id = table.Column<Guid>(type: "uuid", nullable: false),
spool_id = table.Column<Guid>(type: "uuid", nullable: false),
print_name = table.Column<string>(type: "character varying(500)", maxLength: 500, nullable: false),
gcode_file_path = table.Column<string>(type: "character varying(1000)", maxLength: 1000, nullable: true),
mm_extruded = table.Column<decimal>(type: "numeric(12,2)", precision: 12, scale: 2, nullable: false),
grams_derived = table.Column<decimal>(type: "numeric(10,2)", precision: 10, scale: 2, nullable: false),
cost_per_print = table.Column<decimal>(type: "numeric(10,4)", precision: 10, scale: 4, nullable: true),
started_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
completed_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
status = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false, defaultValue: "Queued"),
data_source = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false),
filament_diameter_at_print_mm = table.Column<decimal>(type: "numeric(6,3)", precision: 6, scale: 3, nullable: false),
material_density_at_print = table.Column<decimal>(type: "numeric(10,4)", precision: 10, scale: 4, nullable: false),
notes = table.Column<string>(type: "character varying(2000)", maxLength: 2000, nullable: true),
created_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now() at time zone 'utc'"),
updated_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now() at time zone 'utc'")
},
constraints: table =>
{
table.PrimaryKey("PK_print_jobs", x => x.id);
table.ForeignKey(
name: "fk_print_jobs_printer",
column: x => x.printer_id,
principalTable: "printers",
principalColumn: "id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "fk_print_jobs_spool",
column: x => x.spool_id,
principalTable: "spools",
principalColumn: "id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.InsertData(
table: "material_bases",
columns: new[] { "id", "created_at", "density_g_per_cm3", "name", "updated_at" },
values: new object[,]
{
{ new Guid("10000000-0000-0000-0000-000000000001"), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(1096), 1.24m, "PLA", new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(1096) },
{ new Guid("10000000-0000-0000-0000-000000000002"), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(1620), 1.27m, "PETG", new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(1620) },
{ new Guid("10000000-0000-0000-0000-000000000003"), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(1630), 1.04m, "ABS", new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(1630) },
{ new Guid("10000000-0000-0000-0000-000000000004"), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(1638), 1.07m, "ASA", new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(1638) },
{ new Guid("10000000-0000-0000-0000-000000000005"), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(1645), 1.21m, "TPU", new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(1645) },
{ new Guid("10000000-0000-0000-0000-000000000006"), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(1651), 1.14m, "Nylon", new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(1652) }
});
migrationBuilder.InsertData(
table: "material_finishes",
columns: new[] { "id", "created_at", "material_base_id", "name", "updated_at" },
values: new object[,]
{
{ new Guid("20000000-0000-0000-0000-000000000001"), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(1850), new Guid("10000000-0000-0000-0000-000000000001"), "Basic", new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(1850) },
{ new Guid("20000000-0000-0000-0000-000000000002"), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2041), new Guid("10000000-0000-0000-0000-000000000001"), "Matte", new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2041) },
{ new Guid("20000000-0000-0000-0000-000000000003"), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2049), new Guid("10000000-0000-0000-0000-000000000001"), "Silk", new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2049) },
{ new Guid("20000000-0000-0000-0000-000000000004"), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2055), new Guid("10000000-0000-0000-0000-000000000001"), "Glitter", new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2056) },
{ new Guid("20000000-0000-0000-0000-000000000005"), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2062), new Guid("10000000-0000-0000-0000-000000000001"), "Marble", new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2062) },
{ new Guid("20000000-0000-0000-0000-000000000006"), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2068), new Guid("10000000-0000-0000-0000-000000000001"), "Sparkle", new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2068) },
{ new Guid("20000000-0000-0000-0000-000000000007"), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2075), new Guid("10000000-0000-0000-0000-000000000002"), "Basic", new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2075) },
{ new Guid("20000000-0000-0000-0000-000000000008"), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2081), new Guid("10000000-0000-0000-0000-000000000002"), "Matte", new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2081) },
{ new Guid("20000000-0000-0000-0000-000000000009"), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2100), new Guid("10000000-0000-0000-0000-000000000002"), "Silk", new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2100) },
{ new Guid("20000000-0000-0000-0000-000000000010"), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2107), new Guid("10000000-0000-0000-0000-000000000003"), "Basic", new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2107) },
{ new Guid("20000000-0000-0000-0000-000000000011"), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2113), new Guid("10000000-0000-0000-0000-000000000003"), "Matte", new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2113) },
{ new Guid("20000000-0000-0000-0000-000000000012"), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2120), new Guid("10000000-0000-0000-0000-000000000004"), "Basic", new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2120) },
{ new Guid("20000000-0000-0000-0000-000000000013"), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2126), new Guid("10000000-0000-0000-0000-000000000004"), "Matte", new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2126) },
{ new Guid("20000000-0000-0000-0000-000000000014"), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2132), new Guid("10000000-0000-0000-0000-000000000005"), "Basic", new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2133) },
{ new Guid("20000000-0000-0000-0000-000000000015"), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2139), new Guid("10000000-0000-0000-0000-000000000006"), "Basic", new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2139) }
});
migrationBuilder.InsertData(
table: "material_modifiers",
columns: new[] { "id", "created_at", "material_base_id", "name", "updated_at" },
values: new object[,]
{
{ new Guid("30000000-0000-0000-0000-000000000001"), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2304), new Guid("10000000-0000-0000-0000-000000000001"), "Carbon Fiber", new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2304) },
{ new Guid("30000000-0000-0000-0000-000000000002"), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2463), new Guid("10000000-0000-0000-0000-000000000001"), "Glass Fiber", new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2463) },
{ new Guid("30000000-0000-0000-0000-000000000003"), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2471), new Guid("10000000-0000-0000-0000-000000000001"), "Wood Fill", new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2471) },
{ new Guid("30000000-0000-0000-0000-000000000004"), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2477), new Guid("10000000-0000-0000-0000-000000000001"), "Glow-in-the-Dark", new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2478) },
{ new Guid("30000000-0000-0000-0000-000000000005"), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2484), new Guid("10000000-0000-0000-0000-000000000002"), "Carbon Fiber", new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2484) },
{ new Guid("30000000-0000-0000-0000-000000000006"), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2490), new Guid("10000000-0000-0000-0000-000000000002"), "Glass Fiber", new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2491) },
{ new Guid("30000000-0000-0000-0000-000000000007"), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2497), new Guid("10000000-0000-0000-0000-000000000003"), "Carbon Fiber", new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2497) },
{ new Guid("30000000-0000-0000-0000-000000000008"), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2503), new Guid("10000000-0000-0000-0000-000000000003"), "Glass Fiber", new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2503) },
{ new Guid("30000000-0000-0000-0000-000000000009"), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2510), new Guid("10000000-0000-0000-0000-000000000004"), "Carbon Fiber", new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2510) },
{ new Guid("30000000-0000-0000-0000-000000000010"), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2516), new Guid("10000000-0000-0000-0000-000000000006"), "Carbon Fiber", new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2516) },
{ new Guid("30000000-0000-0000-0000-000000000011"), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2522), new Guid("10000000-0000-0000-0000-000000000006"), "Glass Fiber", new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2523) }
});
migrationBuilder.CreateIndex(
name: "ix_ams_slots_ams_unit_id_tray_index",
table: "ams_slots",
columns: new[] { "ams_unit_id", "tray_index" },
unique: true);
migrationBuilder.CreateIndex(
name: "ix_ams_slots_spool_id",
table: "ams_slots",
column: "spool_id");
migrationBuilder.CreateIndex(
name: "ix_ams_units_printer_id_unit_index",
table: "ams_units",
columns: new[] { "printer_id", "unit_index" },
unique: true);
migrationBuilder.CreateIndex(
name: "ix_material_bases_name",
table: "material_bases",
column: "name",
unique: true);
migrationBuilder.CreateIndex(
name: "ix_material_finishes_material_base_id_name",
table: "material_finishes",
columns: new[] { "material_base_id", "name" },
unique: true);
migrationBuilder.CreateIndex(
name: "ix_material_modifiers_material_base_id_name",
table: "material_modifiers",
columns: new[] { "material_base_id", "name" },
unique: true);
migrationBuilder.CreateIndex(
name: "ix_print_jobs_data_source",
table: "print_jobs",
column: "data_source");
migrationBuilder.CreateIndex(
name: "ix_print_jobs_printer_id",
table: "print_jobs",
column: "printer_id");
migrationBuilder.CreateIndex(
name: "ix_print_jobs_spool_id",
table: "print_jobs",
column: "spool_id");
migrationBuilder.CreateIndex(
name: "ix_print_jobs_status",
table: "print_jobs",
column: "status");
migrationBuilder.CreateIndex(
name: "ix_printers_connection_type",
table: "printers",
column: "connection_type");
migrationBuilder.CreateIndex(
name: "ix_printers_is_active",
table: "printers",
column: "is_active");
migrationBuilder.CreateIndex(
name: "ix_printers_printer_type",
table: "printers",
column: "printer_type");
migrationBuilder.CreateIndex(
name: "ix_printers_status",
table: "printers",
column: "status");
migrationBuilder.CreateIndex(
name: "ix_spools_is_active",
table: "spools",
column: "is_active");
migrationBuilder.CreateIndex(
name: "ix_spools_material_base_id",
table: "spools",
column: "material_base_id");
migrationBuilder.CreateIndex(
name: "ix_spools_material_finish_id",
table: "spools",
column: "material_finish_id");
migrationBuilder.CreateIndex(
name: "ix_spools_material_modifier_id",
table: "spools",
column: "material_modifier_id");
migrationBuilder.CreateIndex(
name: "ix_spools_spool_serial",
table: "spools",
column: "spool_serial",
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "ams_slots");
migrationBuilder.DropTable(
name: "print_jobs");
migrationBuilder.DropTable(
name: "ams_units");
migrationBuilder.DropTable(
name: "spools");
migrationBuilder.DropTable(
name: "printers");
migrationBuilder.DropTable(
name: "material_finishes");
migrationBuilder.DropTable(
name: "material_modifiers");
migrationBuilder.DropTable(
name: "material_bases");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,533 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Extrudex.Infrastructure.Data.Migrations
{
/// <inheritdoc />
public partial class AddFilamentUsageTrackingModel : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "filament_usages",
columns: table => new
{
id = table.Column<Guid>(type: "uuid", nullable: false),
print_job_id = table.Column<Guid>(type: "uuid", nullable: false),
spool_id = table.Column<Guid>(type: "uuid", nullable: false),
printer_id = table.Column<Guid>(type: "uuid", nullable: false),
grams_used = table.Column<decimal>(type: "numeric(10,2)", precision: 10, scale: 2, nullable: false),
mm_extruded = table.Column<decimal>(type: "numeric(12,2)", precision: 12, scale: 2, nullable: false),
recorded_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now() at time zone 'utc'"),
notes = table.Column<string>(type: "character varying(2000)", maxLength: 2000, nullable: true),
created_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now() at time zone 'utc'"),
updated_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now() at time zone 'utc'")
},
constraints: table =>
{
table.PrimaryKey("PK_filament_usages", x => x.id);
table.ForeignKey(
name: "fk_filament_usages_print_job",
column: x => x.print_job_id,
principalTable: "print_jobs",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "fk_filament_usages_printer",
column: x => x.printer_id,
principalTable: "printers",
principalColumn: "id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "fk_filament_usages_spool",
column: x => x.spool_id,
principalTable: "spools",
principalColumn: "id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.UpdateData(
table: "material_bases",
keyColumn: "id",
keyValue: new Guid("10000000-0000-0000-0000-000000000001"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 18, 34, 33, 291, DateTimeKind.Utc).AddTicks(9388), new DateTime(2026, 4, 26, 18, 34, 33, 291, DateTimeKind.Utc).AddTicks(9388) });
migrationBuilder.UpdateData(
table: "material_bases",
keyColumn: "id",
keyValue: new Guid("10000000-0000-0000-0000-000000000002"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 18, 34, 33, 291, DateTimeKind.Utc).AddTicks(9871), new DateTime(2026, 4, 26, 18, 34, 33, 291, DateTimeKind.Utc).AddTicks(9871) });
migrationBuilder.UpdateData(
table: "material_bases",
keyColumn: "id",
keyValue: new Guid("10000000-0000-0000-0000-000000000003"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 18, 34, 33, 291, DateTimeKind.Utc).AddTicks(9881), new DateTime(2026, 4, 26, 18, 34, 33, 291, DateTimeKind.Utc).AddTicks(9881) });
migrationBuilder.UpdateData(
table: "material_bases",
keyColumn: "id",
keyValue: new Guid("10000000-0000-0000-0000-000000000004"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 18, 34, 33, 291, DateTimeKind.Utc).AddTicks(9888), new DateTime(2026, 4, 26, 18, 34, 33, 291, DateTimeKind.Utc).AddTicks(9888) });
migrationBuilder.UpdateData(
table: "material_bases",
keyColumn: "id",
keyValue: new Guid("10000000-0000-0000-0000-000000000005"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 18, 34, 33, 291, DateTimeKind.Utc).AddTicks(9895), new DateTime(2026, 4, 26, 18, 34, 33, 291, DateTimeKind.Utc).AddTicks(9895) });
migrationBuilder.UpdateData(
table: "material_bases",
keyColumn: "id",
keyValue: new Guid("10000000-0000-0000-0000-000000000006"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 18, 34, 33, 291, DateTimeKind.Utc).AddTicks(9901), new DateTime(2026, 4, 26, 18, 34, 33, 291, DateTimeKind.Utc).AddTicks(9902) });
migrationBuilder.UpdateData(
table: "material_finishes",
keyColumn: "id",
keyValue: new Guid("20000000-0000-0000-0000-000000000001"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(90), new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(90) });
migrationBuilder.UpdateData(
table: "material_finishes",
keyColumn: "id",
keyValue: new Guid("20000000-0000-0000-0000-000000000002"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(251), new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(251) });
migrationBuilder.UpdateData(
table: "material_finishes",
keyColumn: "id",
keyValue: new Guid("20000000-0000-0000-0000-000000000003"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(259), new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(259) });
migrationBuilder.UpdateData(
table: "material_finishes",
keyColumn: "id",
keyValue: new Guid("20000000-0000-0000-0000-000000000004"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(266), new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(266) });
migrationBuilder.UpdateData(
table: "material_finishes",
keyColumn: "id",
keyValue: new Guid("20000000-0000-0000-0000-000000000005"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(272), new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(272) });
migrationBuilder.UpdateData(
table: "material_finishes",
keyColumn: "id",
keyValue: new Guid("20000000-0000-0000-0000-000000000006"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(278), new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(278) });
migrationBuilder.UpdateData(
table: "material_finishes",
keyColumn: "id",
keyValue: new Guid("20000000-0000-0000-0000-000000000007"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(285), new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(285) });
migrationBuilder.UpdateData(
table: "material_finishes",
keyColumn: "id",
keyValue: new Guid("20000000-0000-0000-0000-000000000008"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(291), new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(291) });
migrationBuilder.UpdateData(
table: "material_finishes",
keyColumn: "id",
keyValue: new Guid("20000000-0000-0000-0000-000000000009"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(297), new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(298) });
migrationBuilder.UpdateData(
table: "material_finishes",
keyColumn: "id",
keyValue: new Guid("20000000-0000-0000-0000-000000000010"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(304), new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(304) });
migrationBuilder.UpdateData(
table: "material_finishes",
keyColumn: "id",
keyValue: new Guid("20000000-0000-0000-0000-000000000011"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(310), new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(310) });
migrationBuilder.UpdateData(
table: "material_finishes",
keyColumn: "id",
keyValue: new Guid("20000000-0000-0000-0000-000000000012"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(316), new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(317) });
migrationBuilder.UpdateData(
table: "material_finishes",
keyColumn: "id",
keyValue: new Guid("20000000-0000-0000-0000-000000000013"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(323), new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(323) });
migrationBuilder.UpdateData(
table: "material_finishes",
keyColumn: "id",
keyValue: new Guid("20000000-0000-0000-0000-000000000014"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(329), new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(329) });
migrationBuilder.UpdateData(
table: "material_finishes",
keyColumn: "id",
keyValue: new Guid("20000000-0000-0000-0000-000000000015"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(336), new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(336) });
migrationBuilder.UpdateData(
table: "material_modifiers",
keyColumn: "id",
keyValue: new Guid("30000000-0000-0000-0000-000000000001"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(482), new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(482) });
migrationBuilder.UpdateData(
table: "material_modifiers",
keyColumn: "id",
keyValue: new Guid("30000000-0000-0000-0000-000000000002"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(805), new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(806) });
migrationBuilder.UpdateData(
table: "material_modifiers",
keyColumn: "id",
keyValue: new Guid("30000000-0000-0000-0000-000000000003"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(815), new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(815) });
migrationBuilder.UpdateData(
table: "material_modifiers",
keyColumn: "id",
keyValue: new Guid("30000000-0000-0000-0000-000000000004"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(821), new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(821) });
migrationBuilder.UpdateData(
table: "material_modifiers",
keyColumn: "id",
keyValue: new Guid("30000000-0000-0000-0000-000000000005"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(828), new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(828) });
migrationBuilder.UpdateData(
table: "material_modifiers",
keyColumn: "id",
keyValue: new Guid("30000000-0000-0000-0000-000000000006"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(834), new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(834) });
migrationBuilder.UpdateData(
table: "material_modifiers",
keyColumn: "id",
keyValue: new Guid("30000000-0000-0000-0000-000000000007"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(840), new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(840) });
migrationBuilder.UpdateData(
table: "material_modifiers",
keyColumn: "id",
keyValue: new Guid("30000000-0000-0000-0000-000000000008"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(847), new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(847) });
migrationBuilder.UpdateData(
table: "material_modifiers",
keyColumn: "id",
keyValue: new Guid("30000000-0000-0000-0000-000000000009"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(853), new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(853) });
migrationBuilder.UpdateData(
table: "material_modifiers",
keyColumn: "id",
keyValue: new Guid("30000000-0000-0000-0000-000000000010"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(859), new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(860) });
migrationBuilder.UpdateData(
table: "material_modifiers",
keyColumn: "id",
keyValue: new Guid("30000000-0000-0000-0000-000000000011"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(866), new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(866) });
migrationBuilder.CreateIndex(
name: "ix_filament_usages_print_job_id",
table: "filament_usages",
column: "print_job_id");
migrationBuilder.CreateIndex(
name: "ix_filament_usages_printer_id",
table: "filament_usages",
column: "printer_id");
migrationBuilder.CreateIndex(
name: "ix_filament_usages_recorded_at",
table: "filament_usages",
column: "recorded_at");
migrationBuilder.CreateIndex(
name: "ix_filament_usages_spool_id",
table: "filament_usages",
column: "spool_id");
migrationBuilder.CreateIndex(
name: "ix_filament_usages_spool_id_recorded_at",
table: "filament_usages",
columns: new[] { "spool_id", "recorded_at" });
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "filament_usages");
migrationBuilder.UpdateData(
table: "material_bases",
keyColumn: "id",
keyValue: new Guid("10000000-0000-0000-0000-000000000001"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(1096), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(1096) });
migrationBuilder.UpdateData(
table: "material_bases",
keyColumn: "id",
keyValue: new Guid("10000000-0000-0000-0000-000000000002"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(1620), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(1620) });
migrationBuilder.UpdateData(
table: "material_bases",
keyColumn: "id",
keyValue: new Guid("10000000-0000-0000-0000-000000000003"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(1630), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(1630) });
migrationBuilder.UpdateData(
table: "material_bases",
keyColumn: "id",
keyValue: new Guid("10000000-0000-0000-0000-000000000004"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(1638), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(1638) });
migrationBuilder.UpdateData(
table: "material_bases",
keyColumn: "id",
keyValue: new Guid("10000000-0000-0000-0000-000000000005"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(1645), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(1645) });
migrationBuilder.UpdateData(
table: "material_bases",
keyColumn: "id",
keyValue: new Guid("10000000-0000-0000-0000-000000000006"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(1651), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(1652) });
migrationBuilder.UpdateData(
table: "material_finishes",
keyColumn: "id",
keyValue: new Guid("20000000-0000-0000-0000-000000000001"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(1850), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(1850) });
migrationBuilder.UpdateData(
table: "material_finishes",
keyColumn: "id",
keyValue: new Guid("20000000-0000-0000-0000-000000000002"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2041), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2041) });
migrationBuilder.UpdateData(
table: "material_finishes",
keyColumn: "id",
keyValue: new Guid("20000000-0000-0000-0000-000000000003"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2049), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2049) });
migrationBuilder.UpdateData(
table: "material_finishes",
keyColumn: "id",
keyValue: new Guid("20000000-0000-0000-0000-000000000004"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2055), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2056) });
migrationBuilder.UpdateData(
table: "material_finishes",
keyColumn: "id",
keyValue: new Guid("20000000-0000-0000-0000-000000000005"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2062), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2062) });
migrationBuilder.UpdateData(
table: "material_finishes",
keyColumn: "id",
keyValue: new Guid("20000000-0000-0000-0000-000000000006"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2068), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2068) });
migrationBuilder.UpdateData(
table: "material_finishes",
keyColumn: "id",
keyValue: new Guid("20000000-0000-0000-0000-000000000007"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2075), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2075) });
migrationBuilder.UpdateData(
table: "material_finishes",
keyColumn: "id",
keyValue: new Guid("20000000-0000-0000-0000-000000000008"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2081), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2081) });
migrationBuilder.UpdateData(
table: "material_finishes",
keyColumn: "id",
keyValue: new Guid("20000000-0000-0000-0000-000000000009"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2100), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2100) });
migrationBuilder.UpdateData(
table: "material_finishes",
keyColumn: "id",
keyValue: new Guid("20000000-0000-0000-0000-000000000010"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2107), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2107) });
migrationBuilder.UpdateData(
table: "material_finishes",
keyColumn: "id",
keyValue: new Guid("20000000-0000-0000-0000-000000000011"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2113), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2113) });
migrationBuilder.UpdateData(
table: "material_finishes",
keyColumn: "id",
keyValue: new Guid("20000000-0000-0000-0000-000000000012"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2120), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2120) });
migrationBuilder.UpdateData(
table: "material_finishes",
keyColumn: "id",
keyValue: new Guid("20000000-0000-0000-0000-000000000013"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2126), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2126) });
migrationBuilder.UpdateData(
table: "material_finishes",
keyColumn: "id",
keyValue: new Guid("20000000-0000-0000-0000-000000000014"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2132), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2133) });
migrationBuilder.UpdateData(
table: "material_finishes",
keyColumn: "id",
keyValue: new Guid("20000000-0000-0000-0000-000000000015"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2139), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2139) });
migrationBuilder.UpdateData(
table: "material_modifiers",
keyColumn: "id",
keyValue: new Guid("30000000-0000-0000-0000-000000000001"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2304), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2304) });
migrationBuilder.UpdateData(
table: "material_modifiers",
keyColumn: "id",
keyValue: new Guid("30000000-0000-0000-0000-000000000002"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2463), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2463) });
migrationBuilder.UpdateData(
table: "material_modifiers",
keyColumn: "id",
keyValue: new Guid("30000000-0000-0000-0000-000000000003"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2471), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2471) });
migrationBuilder.UpdateData(
table: "material_modifiers",
keyColumn: "id",
keyValue: new Guid("30000000-0000-0000-0000-000000000004"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2477), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2478) });
migrationBuilder.UpdateData(
table: "material_modifiers",
keyColumn: "id",
keyValue: new Guid("30000000-0000-0000-0000-000000000005"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2484), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2484) });
migrationBuilder.UpdateData(
table: "material_modifiers",
keyColumn: "id",
keyValue: new Guid("30000000-0000-0000-0000-000000000006"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2490), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2491) });
migrationBuilder.UpdateData(
table: "material_modifiers",
keyColumn: "id",
keyValue: new Guid("30000000-0000-0000-0000-000000000007"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2497), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2497) });
migrationBuilder.UpdateData(
table: "material_modifiers",
keyColumn: "id",
keyValue: new Guid("30000000-0000-0000-0000-000000000008"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2503), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2503) });
migrationBuilder.UpdateData(
table: "material_modifiers",
keyColumn: "id",
keyValue: new Guid("30000000-0000-0000-0000-000000000009"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2510), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2510) });
migrationBuilder.UpdateData(
table: "material_modifiers",
keyColumn: "id",
keyValue: new Guid("30000000-0000-0000-0000-000000000010"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2516), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2516) });
migrationBuilder.UpdateData(
table: "material_modifiers",
keyColumn: "id",
keyValue: new Guid("30000000-0000-0000-0000-000000000011"),
columns: new[] { "created_at", "updated_at" },
values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2522), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2523) });
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,5 @@
using System.Reflection; using System.Reflection;
using Extrudex.API.Filters;
using Extrudex.API.Hubs; using Extrudex.API.Hubs;
using Extrudex.Domain.Interfaces; using Extrudex.Domain.Interfaces;
using Extrudex.Infrastructure.Data; using Extrudex.Infrastructure.Data;
@@ -23,7 +24,10 @@ builder.Services.AddDbContext<ExtrudexDbContext>(options =>
options.UseNpgsql(connectionString)); options.UseNpgsql(connectionString));
// ── API Services ─────────────────────────────────────────── // ── API Services ───────────────────────────────────────────
builder.Services.AddControllers(); builder.Services.AddControllers(options =>
{
options.Filters.AddService<FluentValidationFilter>();
});
builder.Services.AddEndpointsApiExplorer(); builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c => builder.Services.AddSwaggerGen(c =>
{ {
@@ -50,6 +54,10 @@ builder.Services.AddSingleton<IQrCodeService, QrCodeService>();
// Registers all validators from the API assembly into DI. // Registers all validators from the API assembly into DI.
builder.Services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly()); builder.Services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());
// Register the FluentValidation action filter so validators run automatically
// on all API controller actions before the action executes.
builder.Services.AddScoped<FluentValidationFilter>();
// ── CORS (kiosk + remote browser) ───────────────────────── // ── CORS (kiosk + remote browser) ─────────────────────────
// AllowAnyOrigin disallows credentials by spec; this is fine for // AllowAnyOrigin disallows credentials by spec; this is fine for
// REST API calls. SignalR WebSockets negotiate without credentials // REST API calls. SignalR WebSockets negotiate without credentials
@@ -69,6 +77,10 @@ builder.Services.AddCors(options =>
// ── SignalR (real-time printer updates) ──────────────────── // ── SignalR (real-time printer updates) ────────────────────
builder.Services.AddSignalR(); builder.Services.AddSignalR();
// ── Health Checks ───────────────────────────────────────────
builder.Services.AddHealthChecks()
.AddNpgSql(connectionString);
var app = builder.Build(); var app = builder.Build();
// ── Middleware ────────────────────────────────────────────── // ── Middleware ──────────────────────────────────────────────
@@ -85,6 +97,9 @@ app.MapControllers();
// ── Hub Endpoints ─────────────────────────────────────────── // ── Hub Endpoints ───────────────────────────────────────────
app.MapHub<PrinterHub>("/hubs/printer"); app.MapHub<PrinterHub>("/hubs/printer");
// ── Health Check Endpoint ──────────────────────────────────
app.MapHealthChecks("/health");
app.Run(); app.Run();
// Helper: builds a connection string from individual env vars. // Helper: builds a connection string from individual env vars.

33
deploy.sh Executable file
View File

@@ -0,0 +1,33 @@
#!/bin/bash
set -e
echo "🔧 Deploying Extrudex Docker runtime..."
# Check if Docker Compose is available
if ! command -v docker-compose &> /dev/null && ! docker compose version &> /dev/null; then
echo "❌ Docker Compose is not installed"
exit 1
fi
COMPOSE_CMD="docker compose"
if command -v docker-compose &> /dev/null; then
COMPOSE_CMD="docker-compose"
fi
echo "📦 Building and starting services..."
$COMPOSE_CMD -f docker-compose.dev.yml up -d --build
echo "⏳ Waiting for services to become healthy..."
sleep 10
echo "✅ Deployment complete!"
echo ""
echo "Services running:"
echo " • Extrudex API: http://localhost:5080"
echo " • Control Center Web: http://localhost:5081"
echo ""
echo "To view logs:"
echo " $COMPOSE_CMD -f docker-compose.dev.yml logs -f"
echo ""
echo "To stop:"
echo " $COMPOSE_CMD -f docker-compose.dev.yml down"

40
docker-compose.dev.yml Normal file
View File

@@ -0,0 +1,40 @@
version: '3.8'
services:
extrudex-api:
build:
context: ./backend
dockerfile: Dockerfile
container_name: extrudex-api
ports:
- "5080:8080"
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ASPNETCORE_URLS=http://+:8080
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
networks:
- extrudex-network
control-center-web:
build:
context: ../Control-Center/frontend
dockerfile: Dockerfile
container_name: control-center-web
ports:
- "5081:80"
depends_on:
extrudex-api:
condition: service_healthy
restart: unless-stopped
networks:
- extrudex-network
networks:
extrudex-network:
driver: bridge