Refactored UUID generation: use pseudo unique named UUID for schedules
This commit is contained in:
parent
23424830e7
commit
2be8e565bf
|
@ -5,6 +5,7 @@ use Engelsystem\Helpers\Carbon;
|
|||
use Engelsystem\Http\Exceptions\HttpForbidden;
|
||||
use Engelsystem\Models\AngelType;
|
||||
use Engelsystem\Models\Room;
|
||||
use Engelsystem\Models\Shifts\Schedule;
|
||||
use Engelsystem\Models\Shifts\Shift;
|
||||
use Engelsystem\Models\Shifts\ShiftType;
|
||||
use Engelsystem\Models\User\User;
|
||||
|
@ -208,7 +209,7 @@ function admin_shifts()
|
|||
'description' => $description,
|
||||
];
|
||||
} elseif ($mode == 'multi') {
|
||||
$shift_start = $start;
|
||||
$shift_start = $start;
|
||||
do {
|
||||
$shift_end = (clone $shift_start)->addSeconds((int) $length * 60);
|
||||
|
||||
|
@ -589,22 +590,26 @@ function admin_shifts_history(): string
|
|||
throw_redirect(page_link_to('admin_shifts_history'));
|
||||
}
|
||||
|
||||
$schedules = Schedule::all()->pluck('name', 'id')->toArray();
|
||||
$shiftsData = Db::select('
|
||||
SELECT
|
||||
transaction_id,
|
||||
title,
|
||||
COUNT(id) AS count,
|
||||
MIN(start) AS start,
|
||||
MAX(end) AS end,
|
||||
created_by AS user_id,
|
||||
MAX(created_at) AS created_at
|
||||
FROM shifts
|
||||
WHERE transaction_id IS NOT NULL
|
||||
GROUP BY transaction_id
|
||||
s.transaction_id,
|
||||
s.title,
|
||||
schedule_shift.schedule_id,
|
||||
COUNT(s.id) AS count,
|
||||
MIN(s.start) AS start,
|
||||
MAX(s.end) AS end,
|
||||
s.created_by AS user_id,
|
||||
MAX(s.created_at) AS created_at
|
||||
FROM shifts AS s
|
||||
LEFT JOIN schedule_shift on schedule_shift.shift_id = s.id
|
||||
WHERE s.transaction_id IS NOT NULL
|
||||
GROUP BY s.transaction_id
|
||||
ORDER BY created_at DESC
|
||||
');
|
||||
|
||||
foreach ($shiftsData as &$shiftData) {
|
||||
$shiftData['title'] = $shiftData['schedule_id'] ? __('shifts_history.schedule', [$schedules[$shiftData['schedule_id']]]) : $shiftData['title'];
|
||||
$shiftData['user'] = User_Nick_render(User::find($shiftData['user_id']));
|
||||
$shiftData['start'] = Carbon::make($shiftData['start'])->format(__('Y-m-d H:i'));
|
||||
$shiftData['end'] = Carbon::make($shiftData['end'])->format(__('Y-m-d H:i'));
|
||||
|
|
|
@ -12,6 +12,7 @@ use Engelsystem\Helpers\Schedule\Event;
|
|||
use Engelsystem\Helpers\Schedule\Room;
|
||||
use Engelsystem\Helpers\Schedule\Schedule;
|
||||
use Engelsystem\Helpers\Schedule\XmlParser;
|
||||
use Engelsystem\Helpers\Uuid;
|
||||
use Engelsystem\Http\Request;
|
||||
use Engelsystem\Http\Response;
|
||||
use Engelsystem\Models\Room as RoomModel;
|
||||
|
@ -304,6 +305,7 @@ class ImportSchedule extends BaseController
|
|||
$shift->end = $event->getEndDate()->copy()->timezone($eventTimeZone);
|
||||
$shift->room()->associate($room);
|
||||
$shift->url = $event->getUrl() ?? '';
|
||||
$shift->transaction_id = Uuid::uuidBy($scheduleUrl->id, '5c4ed01e');
|
||||
$shift->createdBy()->associate($user);
|
||||
$shift->save();
|
||||
|
||||
|
|
|
@ -2868,6 +2868,9 @@ msgstr "Titel"
|
|||
msgid "schedule.import.shift.room"
|
||||
msgstr "Raum"
|
||||
|
||||
msgid "shifts_history.schedule"
|
||||
msgstr "Programm: %s"
|
||||
|
||||
msgid "news.title"
|
||||
msgstr "News"
|
||||
|
||||
|
|
|
@ -145,6 +145,9 @@ msgstr "Title"
|
|||
msgid "schedule.import.shift.room"
|
||||
msgstr "Room"
|
||||
|
||||
msgid "shifts_history.schedule"
|
||||
msgstr "Schedule: %s"
|
||||
|
||||
msgid "news.title"
|
||||
msgstr "News"
|
||||
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Engelsystem\Helpers;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use Illuminate\Support\Str;
|
||||
use Stringable;
|
||||
|
||||
class Uuid
|
||||
{
|
||||
/**
|
||||
* Generate a v4 UUID
|
||||
*/
|
||||
public static function uuid(): string
|
||||
{
|
||||
return sprintf(
|
||||
'%08x-%04x-%04x-%04x-%012x',
|
||||
mt_rand(0, 0xffffffff),
|
||||
mt_rand(0, 0xffff),
|
||||
// first bit is the uuid version, here 4
|
||||
mt_rand(0, 0x0fff) | 0x4000,
|
||||
// variant
|
||||
mt_rand(0, 0x3fff) | 0x8000,
|
||||
mt_rand(0, 0xffffffffffff)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a dependent v4 UUID
|
||||
* @var string|int|float|Stringable $value any value that can be converted to string
|
||||
*/
|
||||
public static function uuidBy(mixed $value, string $name = null): string
|
||||
{
|
||||
if (!is_null($name)) {
|
||||
if (!preg_match('/^[\da-f]+$/i', $name)) {
|
||||
throw new InvalidArgumentException('$name must be a hex string');
|
||||
}
|
||||
|
||||
if (Str::length($name) > 20) {
|
||||
throw new InvalidArgumentException('$name is longer than 20 characters');
|
||||
}
|
||||
|
||||
$name = Str::lower($name);
|
||||
}
|
||||
|
||||
$value = $name . md5((string) $value);
|
||||
|
||||
return sprintf(
|
||||
'%08s-%04s-%04s-%04s-%012s',
|
||||
Str::substr($value, 0, 8),
|
||||
Str::substr($value, 8, 4),
|
||||
// first bit is the uuid version, here 4
|
||||
'4' . Str::substr($value, 13, 3),
|
||||
// first bit is the variant (0x8-0xb)
|
||||
dechex(8 + (hexdec(Str::substr($value, 16, 1)) % 4))
|
||||
. Str::substr($value, 17, 3),
|
||||
Str::substr($value, 20, 12)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -12,23 +12,6 @@ class UuidServiceProvider extends ServiceProvider
|
|||
*/
|
||||
public function register(): void
|
||||
{
|
||||
Str::createUuidsUsing([$this, 'uuid']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a v4 UUID
|
||||
*/
|
||||
public function uuid(): string
|
||||
{
|
||||
return sprintf(
|
||||
'%08x-%04x-%04x-%04x-%012x',
|
||||
mt_rand(0, 0xffffffff),
|
||||
mt_rand(0, 0xffff),
|
||||
// first bit is the uuid version, here 4
|
||||
mt_rand(0, 0x0fff) | 0x4000,
|
||||
// variant
|
||||
mt_rand(0, 0x3fff) | 0x8000,
|
||||
mt_rand(0, 0xffffffffffff)
|
||||
);
|
||||
Str::createUuidsUsing(Uuid::class . '::uuid');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,15 +3,16 @@
|
|||
namespace Engelsystem\Test\Unit\Helpers;
|
||||
|
||||
use Engelsystem\Application;
|
||||
use Engelsystem\Helpers\Uuid;
|
||||
use Engelsystem\Helpers\UuidServiceProvider;
|
||||
use Engelsystem\Test\Unit\ServiceProviderTest;
|
||||
use Illuminate\Support\Str;
|
||||
use ReflectionProperty;
|
||||
|
||||
class UuidServiceProviderTest extends ServiceProviderTest
|
||||
{
|
||||
/**
|
||||
* @covers \Engelsystem\Helpers\UuidServiceProvider::register
|
||||
* @covers \Engelsystem\Helpers\UuidServiceProvider::uuid
|
||||
*/
|
||||
public function testRegister(): void
|
||||
{
|
||||
|
@ -20,9 +21,13 @@ class UuidServiceProviderTest extends ServiceProviderTest
|
|||
$serviceProvider = new UuidServiceProvider($app);
|
||||
$serviceProvider->register();
|
||||
|
||||
$this->assertStringMatchesFormat(
|
||||
'%x%x%x%x%x%x%x%x-%x%x%x%x-4%x%x%x-%x%x%x%x-%x%x%x%x%x%x%x%x%x%x%x%x',
|
||||
Str::uuid()
|
||||
);
|
||||
$uuidFactoryReference = (new ReflectionProperty(Str::class, 'uuidFactory'))
|
||||
->getValue();
|
||||
|
||||
$this->assertIsCallable($uuidFactoryReference);
|
||||
$this->assertIsString($uuidFactoryReference);
|
||||
$this->assertEquals(Uuid::class . '::uuid', $uuidFactoryReference);
|
||||
|
||||
$this->assertTrue(Str::isUuid(Str::uuid()), 'Is a UUID');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,132 @@
|
|||
<?php
|
||||
|
||||
namespace Engelsystem\Test\Unit\Helpers;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use Engelsystem\Helpers\Uuid;
|
||||
use Engelsystem\Test\Unit\TestCase;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class UuidTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @covers \Engelsystem\Helpers\Uuid::uuid
|
||||
*/
|
||||
public function testUuid(): void
|
||||
{
|
||||
$uuid = new Uuid();
|
||||
$result = $uuid->uuid();
|
||||
|
||||
$this->checkUuid4Format($result);
|
||||
}
|
||||
|
||||
public function generateUuidBy(): array
|
||||
{
|
||||
return [
|
||||
[42, 'a1d0c6e8-3f02-4327-9846-1063f4ac58a6'],
|
||||
['42', 'a1d0c6e8-3f02-4327-9846-1063f4ac58a6'],
|
||||
[1.23, '579c4c7f-58e4-45e8-8cfc-73e08903a08c'],
|
||||
['1.23', '579c4c7f-58e4-45e8-8cfc-73e08903a08c'],
|
||||
['test', '098f6bcd-4621-4373-8ade-4e832627b4f6']
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Engelsystem\Helpers\Uuid::uuidBy
|
||||
* @dataProvider generateUuidBy
|
||||
*/
|
||||
public function testUuidBy(mixed $value, string $expected): void
|
||||
{
|
||||
$uuid = new Uuid();
|
||||
$result = $uuid->uuidBy($value);
|
||||
|
||||
$this->checkUuid4Format($result);
|
||||
$this->assertEquals($expected, $result);
|
||||
}
|
||||
|
||||
public function generateUuidByNumbers(): array
|
||||
{
|
||||
$numbers = [];
|
||||
foreach (range(0, 10) as $number) {
|
||||
$numbers[] = [$number];
|
||||
}
|
||||
|
||||
return $numbers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Engelsystem\Helpers\Uuid::uuidBy
|
||||
* @dataProvider generateUuidByNumbers
|
||||
*/
|
||||
public function testUuidByNumbers(mixed $value): void
|
||||
{
|
||||
$uuid = new Uuid();
|
||||
$result = $uuid->uuidBy($value);
|
||||
|
||||
$this->checkUuid4Format($result);
|
||||
}
|
||||
|
||||
public function generateUuidByNamed(): array
|
||||
{
|
||||
return [
|
||||
['42', 42, '42a1d0c6-e83f-4273-a7d8-461063f4ac58'],
|
||||
['123', 1.23, '123579c4-c7f5-4e48-9e8c-cfc73e08903a'],
|
||||
['7e57', 'test', '7e57098f-6bcd-4621-9373-cade4e832627'],
|
||||
['5c4ed01eda7a1490751', 'schedule data', '5c4ed01e-da7a-4490-b514-33ffb998ffe8'],
|
||||
['00001000010000100001', '20 characters', '00001000-0100-4010-8001-3e8c8aa31133'],
|
||||
['ABC0DEF', 'lowercase', 'abc0deff-8241-4ecc-87fb-74bf40ccfe96'],
|
||||
['0123456789000abc0def', 'change nothing', '01234567-8900-4abc-8def-4a28d7089c93'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Engelsystem\Helpers\Uuid::uuidBy
|
||||
* @dataProvider generateUuidByNamed
|
||||
*/
|
||||
public function testUuidByNamed(string $name, mixed $value, string $expected): void
|
||||
{
|
||||
$uuid = new Uuid();
|
||||
$result = $uuid->uuidBy($value, $name);
|
||||
|
||||
$this->checkUuid4Format($result);
|
||||
$this->assertEquals($expected, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Engelsystem\Helpers\Uuid::uuidBy
|
||||
*/
|
||||
public function testUuidByNamedTooLong(): void
|
||||
{
|
||||
$uuid = new Uuid();
|
||||
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
$this->expectExceptionMessageMatches('/\$name.+20.+/');
|
||||
$uuid->uuidBy('', '111111111111111111111');
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Engelsystem\Helpers\Uuid::uuidBy
|
||||
*/
|
||||
public function testUuidByNamedNotHex(): void
|
||||
{
|
||||
$uuid = new Uuid();
|
||||
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
$this->expectExceptionMessageMatches('/\$name.+hex.+/');
|
||||
$uuid->uuidBy('', 'not a hex name');
|
||||
}
|
||||
|
||||
protected function checkUuid4Format(string $uuid): void
|
||||
{
|
||||
$version = Str::substr($uuid, 14, 1);
|
||||
$variant = Str::substr($uuid, 19, 1);
|
||||
|
||||
$this->assertTrue(Str::isUuid($uuid), 'Is a UUID');
|
||||
$this->assertEquals(4, $version, 'Version');
|
||||
$this->assertStringStartsWith(
|
||||
'10',
|
||||
sprintf('%04b', hexdec($variant)),
|
||||
'Variant is 0x8-0xb (RFC 4122, DCE 1.1, ISO/IEC 11578:1996)'
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue