diff --git a/Database/Migrations/20241226131510_RenameCategoriesPuzzles2.Designer.cs b/Database/Migrations/20241226131510_RenameCategoriesPuzzles2.Designer.cs
new file mode 100644
index 0000000..38a8acc
--- /dev/null
+++ b/Database/Migrations/20241226131510_RenameCategoriesPuzzles2.Designer.cs
@@ -0,0 +1,135 @@
+//
+using System;
+using ConnectionsAPI.Database;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace ConnectionsAPI.Database.Migrations
+{
+ [DbContext(typeof(ConnectionsContext))]
+ [Migration("20241226131510_RenameCategoriesPuzzles2")]
+ partial class RenameCategoriesPuzzles2
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "8.0.4");
+
+ modelBuilder.Entity("ConnectionsAPI.Database.Entities.ConnectionsCard", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("ConnectionsCategoryId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Content")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("Position")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ConnectionsCategoryId");
+
+ b.ToTable("ConnectionsCard");
+ });
+
+ modelBuilder.Entity("ConnectionsAPI.Database.Entities.ConnectionsCategory", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("Color")
+ .HasColumnType("INTEGER");
+
+ b.Property("ConnectionsPuzzleId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ConnectionsPuzzleId");
+
+ b.ToTable("ConnectionsCategory");
+ });
+
+ modelBuilder.Entity("ConnectionsAPI.Database.Entities.ConnectionsPuzzle", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("ContentMD5")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("CreatedDate")
+ .HasColumnType("TEXT");
+
+ b.Property("EditorName")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("Index")
+ .HasColumnType("INTEGER");
+
+ b.Property("PrintDate")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("PrintDate")
+ .IsUnique();
+
+ b.ToTable("ConnectionsPuzzles");
+ });
+
+ modelBuilder.Entity("ConnectionsAPI.Database.Entities.ConnectionsCard", b =>
+ {
+ b.HasOne("ConnectionsAPI.Database.Entities.ConnectionsCategory", "Category")
+ .WithMany("Cards")
+ .HasForeignKey("ConnectionsCategoryId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Category");
+ });
+
+ modelBuilder.Entity("ConnectionsAPI.Database.Entities.ConnectionsCategory", b =>
+ {
+ b.HasOne("ConnectionsAPI.Database.Entities.ConnectionsPuzzle", "ConnectionsPuzzle")
+ .WithMany("Categories")
+ .HasForeignKey("ConnectionsPuzzleId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("ConnectionsPuzzle");
+ });
+
+ modelBuilder.Entity("ConnectionsAPI.Database.Entities.ConnectionsCategory", b =>
+ {
+ b.Navigation("Cards");
+ });
+
+ modelBuilder.Entity("ConnectionsAPI.Database.Entities.ConnectionsPuzzle", b =>
+ {
+ b.Navigation("Categories");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/Database/Migrations/20241226131510_RenameCategoriesPuzzles2.cs b/Database/Migrations/20241226131510_RenameCategoriesPuzzles2.cs
new file mode 100644
index 0000000..2dee41e
--- /dev/null
+++ b/Database/Migrations/20241226131510_RenameCategoriesPuzzles2.cs
@@ -0,0 +1,79 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace ConnectionsAPI.Database.Migrations
+{
+ ///
+ public partial class RenameCategoriesPuzzles2 : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropForeignKey(
+ name: "FK_ConnectionsCard_ConnectionsCategory_CategoryId",
+ table: "ConnectionsCard");
+
+ migrationBuilder.DropIndex(
+ name: "IX_ConnectionsCard_CategoryId",
+ table: "ConnectionsCard");
+
+ migrationBuilder.DropColumn(
+ name: "CategoryId",
+ table: "ConnectionsCard");
+
+ migrationBuilder.RenameColumn(
+ name: "CategoriesCategoryId",
+ table: "ConnectionsCard",
+ newName: "ConnectionsCategoryId");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_ConnectionsCard_ConnectionsCategoryId",
+ table: "ConnectionsCard",
+ column: "ConnectionsCategoryId");
+
+ migrationBuilder.AddForeignKey(
+ name: "FK_ConnectionsCard_ConnectionsCategory_ConnectionsCategoryId",
+ table: "ConnectionsCard",
+ column: "ConnectionsCategoryId",
+ principalTable: "ConnectionsCategory",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropForeignKey(
+ name: "FK_ConnectionsCard_ConnectionsCategory_ConnectionsCategoryId",
+ table: "ConnectionsCard");
+
+ migrationBuilder.DropIndex(
+ name: "IX_ConnectionsCard_ConnectionsCategoryId",
+ table: "ConnectionsCard");
+
+ migrationBuilder.RenameColumn(
+ name: "ConnectionsCategoryId",
+ table: "ConnectionsCard",
+ newName: "CategoriesCategoryId");
+
+ migrationBuilder.AddColumn(
+ name: "CategoryId",
+ table: "ConnectionsCard",
+ type: "INTEGER",
+ nullable: true);
+
+ migrationBuilder.CreateIndex(
+ name: "IX_ConnectionsCard_CategoryId",
+ table: "ConnectionsCard",
+ column: "CategoryId");
+
+ migrationBuilder.AddForeignKey(
+ name: "FK_ConnectionsCard_ConnectionsCategory_CategoryId",
+ table: "ConnectionsCard",
+ column: "CategoryId",
+ principalTable: "ConnectionsCategory",
+ principalColumn: "Id");
+ }
+ }
+}
diff --git a/Database/Migrations/ConnectionsContextModelSnapshot.cs b/Database/Migrations/ConnectionsContextModelSnapshot.cs
index 35a38fb..ab84cf7 100644
--- a/Database/Migrations/ConnectionsContextModelSnapshot.cs
+++ b/Database/Migrations/ConnectionsContextModelSnapshot.cs
@@ -23,10 +23,7 @@ namespace ConnectionsAPI.Database.Migrations
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
- b.Property("CategoriesCategoryId")
- .HasColumnType("INTEGER");
-
- b.Property("CategoryId")
+ b.Property("ConnectionsCategoryId")
.HasColumnType("INTEGER");
b.Property("Content")
@@ -38,7 +35,7 @@ namespace ConnectionsAPI.Database.Migrations
b.HasKey("Id");
- b.HasIndex("CategoryId");
+ b.HasIndex("ConnectionsCategoryId");
b.ToTable("ConnectionsCard");
});
@@ -102,7 +99,9 @@ namespace ConnectionsAPI.Database.Migrations
{
b.HasOne("ConnectionsAPI.Database.Entities.ConnectionsCategory", "Category")
.WithMany("Cards")
- .HasForeignKey("CategoryId");
+ .HasForeignKey("ConnectionsCategoryId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
b.Navigation("Category");
});
diff --git a/Features/Connections/ConnectionsGroup.cs b/Features/Connections/ConnectionsGroup.cs
new file mode 100644
index 0000000..957d9e1
--- /dev/null
+++ b/Features/Connections/ConnectionsGroup.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace ConnectionsAPI.Features.Connections;
+
+public class ConnectionsGroup : Group
+{
+ public ConnectionsGroup()
+ {
+ Configure("connections", ep =>
+ {
+ ep.AllowAnonymous();
+ });
+ }
+}
\ No newline at end of file
diff --git a/Features/Connections/Get/Endpoint.cs b/Features/Connections/Get/Endpoint.cs
new file mode 100644
index 0000000..2727e52
--- /dev/null
+++ b/Features/Connections/Get/Endpoint.cs
@@ -0,0 +1,35 @@
+using ConnectionsAPI.Database.Repository;
+using ConnectionsAPI.Models.Request;
+using ConnectionsAPI.Models.Response;
+
+namespace ConnectionsAPI.Features.Connections.Get;
+
+public class GetConnectionsEndpoint(PuzzleRepository _puzzleRepo) : Endpoint
+{
+ public override void Configure()
+ {
+ Get("{PrintDate}");
+ Group();
+ }
+
+ public override async Task HandleAsync(GetPuzzleRequest req, CancellationToken ct)
+ {
+ bool hideSolutions = Query("hideSolutions", isRequired: false);
+
+ // query for the puzzle
+ var puzzle = await _puzzleRepo.GetPuzzleByDateAsync(req.PrintDate, includeSolutions: !hideSolutions);
+
+ // if not found, done here
+ if (puzzle == null)
+ {
+ await SendNotFoundAsync(ct);
+ return;
+ }
+
+ // get response from cache
+ var response = ConnectionsPuzzleDTO.FromEntity(puzzle);
+
+ // done
+ await SendAsync(response, cancellation: ct);
+ }
+}
\ No newline at end of file
diff --git a/Features/Puzzle/Get/GetPuzzleEndpoint.cs b/Features/Puzzle/Get/GetPuzzleEndpoint.cs
index 834defd..96c06fa 100644
--- a/Features/Puzzle/Get/GetPuzzleEndpoint.cs
+++ b/Features/Puzzle/Get/GetPuzzleEndpoint.cs
@@ -1,67 +1,67 @@
-using ConnectionsAPI.Database.Repository;
-using ConnectionsAPI.Models;
-using FluentValidation;
-using LazyCache;
-using System.Text.RegularExpressions;
+// using ConnectionsAPI.Database.Repository;
+// using ConnectionsAPI.Models;
+// using FluentValidation;
+// using LazyCache;
+// using System.Text.RegularExpressions;
-namespace ConnectionsAPI.Features.Puzzle.Get
-{
- public record GetPuzzleEndpointRequest(string PuzzleDate);
+// namespace ConnectionsAPI.Features.Puzzle.Get
+// {
+// public record GetPuzzleEndpointRequest(string PuzzleDate);
- public partial class GetPuzzleEndpointRequestValidator : Validator
- {
- [GeneratedRegex("^\\d{4}-\\d{2}-\\d{2}$", RegexOptions.IgnoreCase)]
- private static partial Regex PrintDateGeneratedRegex();
+// public partial class GetPuzzleEndpointRequestValidator : Validator
+// {
+// [GeneratedRegex("^\\d{4}-\\d{2}-\\d{2}$", RegexOptions.IgnoreCase)]
+// private static partial Regex PrintDateGeneratedRegex();
- public GetPuzzleEndpointRequestValidator()
- {
- RuleFor(x => x.PuzzleDate)
- .NotEmpty().WithMessage("Puzzle date is required")
- .Must(x => PrintDateGeneratedRegex().IsMatch(x)).WithMessage("Puzzle date must be in the format yyyy-MM-dd");
- }
- }
+// public GetPuzzleEndpointRequestValidator()
+// {
+// RuleFor(x => x.PuzzleDate)
+// .NotEmpty().WithMessage("Puzzle date is required")
+// .Must(x => PrintDateGeneratedRegex().IsMatch(x)).WithMessage("Puzzle date must be in the format yyyy-MM-dd");
+// }
+// }
- public class GetPuzzleEndpoint(PuzzleRepository puzzleRepo, ILogger logger, IAppCache cache) : Endpoint
- {
- private readonly PuzzleRepository _puzzleRepo = puzzleRepo;
- private readonly ILogger _logger = logger;
- private readonly IAppCache _cache = cache;
+// public class GetPuzzleEndpoint(PuzzleRepository puzzleRepo, ILogger logger, IAppCache cache) : Endpoint
+// {
+// private readonly PuzzleRepository _puzzleRepo = puzzleRepo;
+// private readonly ILogger _logger = logger;
+// private readonly IAppCache _cache = cache;
- public override void Configure()
- {
- Get("/{PuzzleDate}.json",
- "/puzzle/{PuzzleDate}");
- AllowAnonymous();
- DontThrowIfValidationFails();
- }
+// public override void Configure()
+// {
+// Get("/{PuzzleDate}.json",
+// "/puzzle/{PuzzleDate}");
+// AllowAnonymous();
+// DontThrowIfValidationFails();
+// }
- public override async Task HandleAsync(GetPuzzleEndpointRequest req, CancellationToken ct)
- {
- // default to 404 if validation fails
- if (ValidationFailed)
- {
- _logger.LogError("Validation error. {path} {pathBase}", HttpContext.Request.Path, HttpContext.Request.PathBase);
- await SendNotFoundAsync(ct);
- return;
- }
+// public override async Task HandleAsync(GetPuzzleEndpointRequest req, CancellationToken ct)
+// {
+// // default to 404 if validation fails
+// if (ValidationFailed)
+// {
+// _logger.LogError("Validation error. {path} {pathBase}", HttpContext.Request.Path, HttpContext.Request.PathBase);
+// await SendNotFoundAsync(ct);
+// return;
+// }
- bool hideSolutions = Query("hideSolutions", isRequired: false);
+// bool hideSolutions = Query("hideSolutions", isRequired: false);
- // query for the puzzle
- var puzzle = await _puzzleRepo.GetPuzzleByDateAsync(req.PuzzleDate, includeSolutions: !hideSolutions);
+// // query for the puzzle
+// var puzzle = await _puzzleRepo.GetPuzzleByDateAsync(req.PuzzleDate, includeSolutions: !hideSolutions);
- // if not found, done here
- if (puzzle == null)
- {
- await SendNotFoundAsync(ct);
- return;
- }
+// // if not found, done here
+// if (puzzle == null)
+// {
+// await SendNotFoundAsync(ct);
+// return;
+// }
- // get response from cache
- var response = ConnectionsPuzzleDTO.FromEntity(puzzle);
+// // get response from cache
+// var response = ConnectionsPuzzleDTO.FromEntity(puzzle);
- // done
- await SendAsync(response, cancellation: ct);
- }
- }
-}
+// // done
+// await SendAsync(response, cancellation: ct);
+// }
+// }
+// }
diff --git a/Features/Puzzle/List/ListPuzzlesEndpoint.cs b/Features/Puzzle/List/ListPuzzlesEndpoint.cs
index de007c8..935c468 100644
--- a/Features/Puzzle/List/ListPuzzlesEndpoint.cs
+++ b/Features/Puzzle/List/ListPuzzlesEndpoint.cs
@@ -1,34 +1,34 @@
-using ConnectionsAPI.Database.Repository;
-using ConnectionsAPI.Models;
-using LazyCache;
-using Microsoft.EntityFrameworkCore;
+// using ConnectionsAPI.Database.Repository;
+// using ConnectionsAPI.Models;
+// using LazyCache;
+// using Microsoft.EntityFrameworkCore;
-namespace ConnectionsAPI.Features.Puzzle.List
-{
- public class ListPuzzlesEndpoint(PuzzleRepository puzzleRepo, IAppCache cache) : EndpointWithoutRequest>
- {
- private readonly PuzzleRepository _puzzleRepo = puzzleRepo;
- private readonly IAppCache _cache = cache;
+// namespace ConnectionsAPI.Features.Puzzle.List
+// {
+// public class ListPuzzlesEndpoint(PuzzleRepository puzzleRepo, IAppCache cache) : EndpointWithoutRequest>
+// {
+// private readonly PuzzleRepository _puzzleRepo = puzzleRepo;
+// private readonly IAppCache _cache = cache;
- public override void Configure()
- {
- Get("/all.json",
- "/puzzle/all");
- AllowAnonymous();
- }
+// public override void Configure()
+// {
+// Get("/all.json",
+// "/puzzle/all");
+// AllowAnonymous();
+// }
- public override async Task HandleAsync(CancellationToken ct)
- {
- bool hideSolutions = Query("hideSolutions", isRequired: false);
+// public override async Task HandleAsync(CancellationToken ct)
+// {
+// bool hideSolutions = Query("hideSolutions", isRequired: false);
- // query all, ordered by print date
- var puzzles = await _puzzleRepo.GetAllPuzzlesAsync(includeSolutions: !hideSolutions);
+// // query all, ordered by print date
+// var puzzles = await _puzzleRepo.GetAllPuzzlesAsync(includeSolutions: !hideSolutions);
- // map to response object
- var response = puzzles.Select(ConnectionsPuzzleDTO.FromEntity).ToList();
+// // map to response object
+// var response = puzzles.Select(ConnectionsPuzzleDTO.FromEntity).ToList();
- // done
- await SendAsync(response, cancellation: ct);
- }
- }
-}
+// // done
+// await SendAsync(response, cancellation: ct);
+// }
+// }
+// }
diff --git a/Models/PuzzleDTO.cs b/Models/PuzzleDTO.cs
deleted file mode 100644
index c8f465b..0000000
--- a/Models/PuzzleDTO.cs
+++ /dev/null
@@ -1,55 +0,0 @@
-namespace ConnectionsAPI.Models
-{
- public class ConnectionsPuzzleDTO
- {
- public static ConnectionsPuzzleDTO FromEntity(Database.Entities.ConnectionsPuzzle dbPuzzle) =>
- new()
- {
- PuzzleNumber = dbPuzzle.Index,
- PrintDate = dbPuzzle.PrintDate,
- Editor = dbPuzzle.EditorName,
-
- NextPuzzle = dbPuzzle.NextPrintDate ?? string.Empty,
- PreviousPuzzle = dbPuzzle.PrevPrintDate ?? string.Empty,
-
- Categories = dbPuzzle.Categories.OrderBy(x => (int)x.Color).Select(PuzzleCategoryDTO.FromEntity).ToList(),
- };
-
- public int PuzzleNumber { get; set; }
- public string PrintDate { get; set; } = string.Empty;
- public string PreviousPuzzle { get; set; } = string.Empty;
- public string NextPuzzle { get; set; } = string.Empty;
- public string Editor { get; set; } = string.Empty;
- public ICollection Categories { get; set; } = [];
- }
-
- public class PuzzleCategoryDTO
- {
- public static PuzzleCategoryDTO FromEntity(Database.Entities.ConnectionsCategory dbCategory) =>
- new()
- {
- Title = dbCategory.Name,
- Cards = dbCategory.Cards.OrderBy(x => x.Content).Select(PuzzleCardDTO.FromEntity).ToList(),
- Color = dbCategory.Color.ToString().ToLower(),
- OrderingKey = (int)dbCategory.Color
- };
-
- public string Title { get; set; } = string.Empty;
- public string Color { get; set; } = string.Empty;
- public int OrderingKey { get; set; }
- public ICollection Cards { get; set; } = [];
- }
-
- public class PuzzleCardDTO
- {
- public static PuzzleCardDTO FromEntity(Database.Entities.ConnectionsCard dbCard) =>
- new()
- {
- Content = dbCard.Content,
- Position = dbCard.Position,
- };
-
- public string Content { get; set; } = string.Empty;
- public int Position { get; set; }
- }
-}
diff --git a/Models/Request/GetPuzzleRequest.cs b/Models/Request/GetPuzzleRequest.cs
new file mode 100644
index 0000000..c0fa67e
--- /dev/null
+++ b/Models/Request/GetPuzzleRequest.cs
@@ -0,0 +1,3 @@
+namespace ConnectionsAPI.Models.Request;
+
+public record GetPuzzleRequest(string PrintDate);
diff --git a/Models/Response/ConnectionsPuzzleDTO.cs b/Models/Response/ConnectionsPuzzleDTO.cs
new file mode 100644
index 0000000..90e0057
--- /dev/null
+++ b/Models/Response/ConnectionsPuzzleDTO.cs
@@ -0,0 +1,53 @@
+namespace ConnectionsAPI.Models.Response;
+public class ConnectionsPuzzleDTO
+{
+ public static ConnectionsPuzzleDTO FromEntity(Database.Entities.ConnectionsPuzzle dbPuzzle) =>
+ new()
+ {
+ PuzzleNumber = dbPuzzle.Index,
+ PrintDate = dbPuzzle.PrintDate,
+ Editor = dbPuzzle.EditorName,
+
+ NextPuzzle = dbPuzzle.NextPrintDate ?? string.Empty,
+ PreviousPuzzle = dbPuzzle.PrevPrintDate ?? string.Empty,
+
+ Categories = dbPuzzle.Categories.OrderBy(x => (int)x.Color).Select(PuzzleCategoryDTO.FromEntity).ToList(),
+ };
+
+ public int PuzzleNumber { get; set; }
+ public string PrintDate { get; set; } = string.Empty;
+ public string PreviousPuzzle { get; set; } = string.Empty;
+ public string NextPuzzle { get; set; } = string.Empty;
+ public string Editor { get; set; } = string.Empty;
+ public ICollection Categories { get; set; } = [];
+}
+
+public class PuzzleCategoryDTO
+{
+ public static PuzzleCategoryDTO FromEntity(Database.Entities.ConnectionsCategory dbCategory) =>
+ new()
+ {
+ Title = dbCategory.Name,
+ Cards = dbCategory.Cards.OrderBy(x => x.Content).Select(PuzzleCardDTO.FromEntity).ToList(),
+ Color = dbCategory.Color.ToString().ToLower(),
+ OrderingKey = (int)dbCategory.Color
+ };
+
+ public string Title { get; set; } = string.Empty;
+ public string Color { get; set; } = string.Empty;
+ public int OrderingKey { get; set; }
+ public ICollection Cards { get; set; } = [];
+}
+
+public class PuzzleCardDTO
+{
+ public static PuzzleCardDTO FromEntity(Database.Entities.ConnectionsCard dbCard) =>
+ new()
+ {
+ Content = dbCard.Content,
+ Position = dbCard.Position,
+ };
+
+ public string Content { get; set; } = string.Empty;
+ public int Position { get; set; }
+}
diff --git a/Validators/GetPuzzleRequestValidator.cs b/Validators/GetPuzzleRequestValidator.cs
new file mode 100644
index 0000000..e530597
--- /dev/null
+++ b/Validators/GetPuzzleRequestValidator.cs
@@ -0,0 +1,23 @@
+using System.Globalization;
+using System.Text.RegularExpressions;
+using ConnectionsAPI.Models.Request;
+using FluentValidation;
+
+namespace ConnectionsAPI.Validators;
+
+public partial class GetPuzzleRequestValidator : Validator
+{
+ [GeneratedRegex("^\\d{4}-\\d{2}-\\d{2}$", RegexOptions.IgnoreCase)]
+ private static partial Regex PrintDateGeneratedRegex();
+
+ public GetPuzzleRequestValidator()
+ {
+ RuleFor(x => x.PrintDate)
+ .NotEmpty().WithMessage("Puzzle date is required")
+ .Must(x => PrintDateGeneratedRegex().IsMatch(x))
+ .WithMessage("Print date must be in the format yyyy-MM-dd")
+ .Must(x => DateTime.TryParseExact(x, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out _))
+ .WithMessage("Print date must be a valid datetime");
+
+ }
+}