diff --git a/src/Infra/Database/Models/ApiClient.php b/src/Infra/Database/Models/ApiClient.php index 90ab502..bcee313 100644 --- a/src/Infra/Database/Models/ApiClient.php +++ b/src/Infra/Database/Models/ApiClient.php @@ -14,6 +14,7 @@ use Infra\Casts\ClientNameCast; use Infra\Casts\ClientSecretCast; use Infra\Casts\UtcDateTimeCast; use Infra\Casts\UuidCast; +use Infra\Database\Concerns\HasUuidV7CreatedAt; /** * @property \Ramsey\Uuid\UuidInterface $id @@ -22,6 +23,7 @@ use Infra\Casts\UuidCast; * @property \Application\Security\Models\ValueObjects\ClientIdentifier $identifier ENCRYPTED, Unique identifier that should not change * @property \Application\Security\Models\ValueObjects\ClientSecret $secret ENCRYPTED, api secret that can be regenerated * @property bool $is_active Defines if the client can be used in order to gain access to the system + * @property \DateTimeImmutable $created_at * @property \DateTimeImmutable $updated_at * @property-read \Illuminate\Notifications\DatabaseNotificationCollection $notifications * @@ -35,6 +37,7 @@ final class ApiClient extends Authenticatable use HasFactory; use HasUuids; + use HasUuidV7CreatedAt; use Notifiable; protected static function newFactory(): ApiClientFactory @@ -72,6 +75,7 @@ final class ApiClient extends Authenticatable 'identifier' => ClientIdentifierCast::class, 'secret' => ClientSecretCast::class, 'is_active' => 'boolean', + 'created_at' => UtcDateTimeCast::class, 'updated_at' => UtcDateTimeCast::class, ]; } diff --git a/src/Infra/Database/Models/Brand.php b/src/Infra/Database/Models/Brand.php index b493d8c..a4a163e 100644 --- a/src/Infra/Database/Models/Brand.php +++ b/src/Infra/Database/Models/Brand.php @@ -10,6 +10,8 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Infra\Casts\ArcadiaIdCast; use Infra\Casts\BrandCodeCast; +use Infra\Casts\UtcDateTimeCast; +use Infra\Database\Concerns\HasUuidV7CreatedAt; /** * @phpcs:disable Generic.Files.LineLength.TooLong @@ -18,6 +20,7 @@ use Infra\Casts\BrandCodeCast; * @property string $name The human readable brand name * @property \Domain\Models\ValueObjects\Brand\BrandCode $uwil_public_id A unique public id that is generated spefically for the UWIL eco system * @property \Domain\Models\ValueObjects\Brand\ArcadiaId $arcadia_id An unique external autoincrement id for the brand, it may be exposed but should not be used + * @property \DateTimeImmutable $created_at * * @method static \Database\Factories\BrandFactory factory($count = null, $state = []) * @@ -29,6 +32,7 @@ final class Brand extends Model use HasFactory; use HasUuids; + use HasUuidV7CreatedAt; protected static function newFactory(): BrandFactory { @@ -54,6 +58,7 @@ final class Brand extends Model return [ 'arcadia_id' => ArcadiaIdCast::class, 'uwil_public_id' => BrandCodeCast::class, + 'created_at' => UtcDateTimeCast::class, ]; } } diff --git a/src/Infra/Database/Models/ClientAuditLog.php b/src/Infra/Database/Models/ClientAuditLog.php index 04ddf26..b5babb1 100644 --- a/src/Infra/Database/Models/ClientAuditLog.php +++ b/src/Infra/Database/Models/ClientAuditLog.php @@ -10,6 +10,7 @@ use Infra\Casts\ClientIdentifierCast; use Infra\Casts\SecurityAuditLogDetailsCast; use Infra\Casts\UtcDateTimeCast; use Infra\Casts\UuidCast; +use Infra\Database\Concerns\HasUuidV7CreatedAt; /** * @property \Ramsey\Uuid\UuidInterface $id @@ -24,6 +25,7 @@ use Infra\Casts\UuidCast; final class ClientAuditLog extends Model { use HasUuids; + use HasUuidV7CreatedAt; protected $table = 'client_audit_logs'; diff --git a/src/Infra/Listeners/RecordClientSecurityAudit.php b/src/Infra/Listeners/RecordClientSecurityAudit.php index 983037d..a57bc30 100644 --- a/src/Infra/Listeners/RecordClientSecurityAudit.php +++ b/src/Infra/Listeners/RecordClientSecurityAudit.php @@ -34,12 +34,11 @@ final readonly class RecordClientSecurityAudit }; ClientAuditLog::query()->create([ - 'id' => Uuid::uuid7()->toString(), + 'id' => Uuid::uuid7($occurredAt)->toString(), 'client_id' => $event->clientId->value, 'client_identifier' => $event->clientIdentifier->value, 'event_type' => $eventType, 'details' => $details, - 'created_at' => $occurredAt, ]); } } diff --git a/tests/Feature/Console/Commands/ExportSecurityAuditLogsCommandTest.php b/tests/Feature/Console/Commands/ExportSecurityAuditLogsCommandTest.php index 31358d8..371131f 100644 --- a/tests/Feature/Console/Commands/ExportSecurityAuditLogsCommandTest.php +++ b/tests/Feature/Console/Commands/ExportSecurityAuditLogsCommandTest.php @@ -29,7 +29,6 @@ describe('ExportSecurityAuditLogsCommand', function (): void { 'client_identifier' => $clientIdentifier, 'event_type' => 'client_secret_generated', 'details' => null, - 'created_at' => '2026-08-20 10:00:00', ]); // WHEN @@ -80,7 +79,6 @@ describe('ExportSecurityAuditLogsCommand', function (): void { 'client_identifier' => $clientIdentifier, 'event_type' => 'client_status_toggled', 'details' => ['is_active' => 'false'], - 'created_at' => '2026-08-18 15:30:00', ]); // WHEN diff --git a/tests/Integration/Infra/Adapters/Directories/EloquentClientAuditLogDirectoryTest.php b/tests/Integration/Infra/Adapters/Directories/EloquentClientAuditLogDirectoryTest.php index 46607f1..dd40d82 100644 --- a/tests/Integration/Infra/Adapters/Directories/EloquentClientAuditLogDirectoryTest.php +++ b/tests/Integration/Infra/Adapters/Directories/EloquentClientAuditLogDirectoryTest.php @@ -27,30 +27,27 @@ describe('EloquentClientAuditLogDirectory Integration Tests', function (): void $clientIdentifier2 = 'arcadia_id_' . str_repeat('2', 32); ClientAuditLog::query()->create([ - 'id' => Uuid::uuid7()->toString(), + 'id' => Uuid::uuid7(new \DateTimeImmutable('2026-08-01 10:00:00 UTC'))->toString(), 'client_id' => $clientId1, 'client_identifier' => $clientIdentifier1, 'event_type' => 'client_secret_generated', 'details' => null, - 'created_at' => '2026-08-01 10:00:00', ]); ClientAuditLog::query()->create([ - 'id' => Uuid::uuid7()->toString(), + 'id' => Uuid::uuid7(new \DateTimeImmutable('2026-08-10 14:00:00 UTC'))->toString(), 'client_id' => $clientId1, 'client_identifier' => $clientIdentifier1, 'event_type' => 'client_status_toggled', 'details' => ['is_active' => 'false'], - 'created_at' => '2026-08-10 14:00:00', ]); ClientAuditLog::query()->create([ - 'id' => Uuid::uuid7()->toString(), + 'id' => Uuid::uuid7(new \DateTimeImmutable('2026-08-15 12:00:00 UTC'))->toString(), 'client_id' => $clientId2, 'client_identifier' => $clientIdentifier2, 'event_type' => 'client_secret_generated', 'details' => null, - 'created_at' => '2026-08-15 12:00:00', ]); $directory = new EloquentClientAuditLogDirectory(); diff --git a/tests/Integration/Infra/Database/Models/ApiClientModelTest.php b/tests/Integration/Infra/Database/Models/ApiClientModelTest.php new file mode 100644 index 0000000..8eec70c --- /dev/null +++ b/tests/Integration/Infra/Database/Models/ApiClientModelTest.php @@ -0,0 +1,35 @@ +toString(); + + // WHEN + $client = ApiClient::factory()->create([ + 'id' => $uuid7, + ]); + + $retrieved = ApiClient::query()->find($client->id); + + // THEN + expect($retrieved)->not->toBeNull() + ->and($retrieved?->id->toString())->toBe($uuid7) + ->and($retrieved?->created_at)->toBeInstanceOf(\DateTimeImmutable::class) + ->and($retrieved?->created_at->format('Y-m-d H:i:s'))->toBe('2026-08-21 08:30:00'); + }); +}); diff --git a/tests/Integration/Infra/Database/Models/BrandModelTest.php b/tests/Integration/Infra/Database/Models/BrandModelTest.php new file mode 100644 index 0000000..8d3036c --- /dev/null +++ b/tests/Integration/Infra/Database/Models/BrandModelTest.php @@ -0,0 +1,35 @@ +toString(); + + // WHEN + $brand = Brand::factory()->create([ + 'id' => $uuid7, + ]); + + $retrieved = Brand::query()->find($brand->id); + + // THEN + expect($retrieved)->not->toBeNull() + ->and($retrieved?->id)->toBe($uuid7) + ->and($retrieved?->created_at)->toBeInstanceOf(\DateTimeImmutable::class) + ->and($retrieved?->created_at->format('Y-m-d H:i:s'))->toBe('2026-08-21 10:30:00'); + }); +}); diff --git a/tests/Integration/Infra/Database/Models/ClientAuditLogModelTest.php b/tests/Integration/Infra/Database/Models/ClientAuditLogModelTest.php new file mode 100644 index 0000000..a83af95 --- /dev/null +++ b/tests/Integration/Infra/Database/Models/ClientAuditLogModelTest.php @@ -0,0 +1,40 @@ +toString(); + $clientId = Uuid::uuid7()->toString(); + + // WHEN + $log = ClientAuditLog::query()->create([ + 'id' => $uuid7, + 'client_id' => $clientId, + 'client_identifier' => 'arcadia_id_' . str_repeat('a', 32), + 'event_type' => 'client_secret_generated', + 'details' => null, + ]); + + $retrieved = ClientAuditLog::query()->find($log->id); + + // THEN + expect($retrieved)->not->toBeNull() + ->and($retrieved?->id->toString())->toBe($uuid7) + ->and($retrieved?->created_at)->toBeInstanceOf(\DateTimeImmutable::class) + ->and($retrieved?->created_at?->format('Y-m-d H:i:s'))->toBe('2026-08-20 14:15:00'); + }); +});