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\Http\Exceptions\HttpForbidden;
|
||||||
use Engelsystem\Models\AngelType;
|
use Engelsystem\Models\AngelType;
|
||||||
use Engelsystem\Models\Room;
|
use Engelsystem\Models\Room;
|
||||||
|
use Engelsystem\Models\Shifts\Schedule;
|
||||||
use Engelsystem\Models\Shifts\Shift;
|
use Engelsystem\Models\Shifts\Shift;
|
||||||
use Engelsystem\Models\Shifts\ShiftType;
|
use Engelsystem\Models\Shifts\ShiftType;
|
||||||
use Engelsystem\Models\User\User;
|
use Engelsystem\Models\User\User;
|
||||||
|
@ -208,7 +209,7 @@ function admin_shifts()
|
||||||
'description' => $description,
|
'description' => $description,
|
||||||
];
|
];
|
||||||
} elseif ($mode == 'multi') {
|
} elseif ($mode == 'multi') {
|
||||||
$shift_start = $start;
|
$shift_start = $start;
|
||||||
do {
|
do {
|
||||||
$shift_end = (clone $shift_start)->addSeconds((int) $length * 60);
|
$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'));
|
throw_redirect(page_link_to('admin_shifts_history'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$schedules = Schedule::all()->pluck('name', 'id')->toArray();
|
||||||
$shiftsData = Db::select('
|
$shiftsData = Db::select('
|
||||||
SELECT
|
SELECT
|
||||||
transaction_id,
|
s.transaction_id,
|
||||||
title,
|
s.title,
|
||||||
COUNT(id) AS count,
|
schedule_shift.schedule_id,
|
||||||
MIN(start) AS start,
|
COUNT(s.id) AS count,
|
||||||
MAX(end) AS end,
|
MIN(s.start) AS start,
|
||||||
created_by AS user_id,
|
MAX(s.end) AS end,
|
||||||
MAX(created_at) AS created_at
|
s.created_by AS user_id,
|
||||||
FROM shifts
|
MAX(s.created_at) AS created_at
|
||||||
WHERE transaction_id IS NOT NULL
|
FROM shifts AS s
|
||||||
GROUP BY transaction_id
|
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
|
ORDER BY created_at DESC
|
||||||
');
|
');
|
||||||
|
|
||||||
foreach ($shiftsData as &$shiftData) {
|
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['user'] = User_Nick_render(User::find($shiftData['user_id']));
|
||||||
$shiftData['start'] = Carbon::make($shiftData['start'])->format(__('Y-m-d H:i'));
|
$shiftData['start'] = Carbon::make($shiftData['start'])->format(__('Y-m-d H:i'));
|
||||||
$shiftData['end'] = Carbon::make($shiftData['end'])->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\Room;
|
||||||
use Engelsystem\Helpers\Schedule\Schedule;
|
use Engelsystem\Helpers\Schedule\Schedule;
|
||||||
use Engelsystem\Helpers\Schedule\XmlParser;
|
use Engelsystem\Helpers\Schedule\XmlParser;
|
||||||
|
use Engelsystem\Helpers\Uuid;
|
||||||
use Engelsystem\Http\Request;
|
use Engelsystem\Http\Request;
|
||||||
use Engelsystem\Http\Response;
|
use Engelsystem\Http\Response;
|
||||||
use Engelsystem\Models\Room as RoomModel;
|
use Engelsystem\Models\Room as RoomModel;
|
||||||
|
@ -304,6 +305,7 @@ class ImportSchedule extends BaseController
|
||||||
$shift->end = $event->getEndDate()->copy()->timezone($eventTimeZone);
|
$shift->end = $event->getEndDate()->copy()->timezone($eventTimeZone);
|
||||||
$shift->room()->associate($room);
|
$shift->room()->associate($room);
|
||||||
$shift->url = $event->getUrl() ?? '';
|
$shift->url = $event->getUrl() ?? '';
|
||||||
|
$shift->transaction_id = Uuid::uuidBy($scheduleUrl->id, '5c4ed01e');
|
||||||
$shift->createdBy()->associate($user);
|
$shift->createdBy()->associate($user);
|
||||||
$shift->save();
|
$shift->save();
|
||||||
|
|
||||||
|
|
|
@ -2868,6 +2868,9 @@ msgstr "Titel"
|
||||||
msgid "schedule.import.shift.room"
|
msgid "schedule.import.shift.room"
|
||||||
msgstr "Raum"
|
msgstr "Raum"
|
||||||
|
|
||||||
|
msgid "shifts_history.schedule"
|
||||||
|
msgstr "Programm: %s"
|
||||||
|
|
||||||
msgid "news.title"
|
msgid "news.title"
|
||||||
msgstr "News"
|
msgstr "News"
|
||||||
|
|
||||||
|
|
|
@ -145,6 +145,9 @@ msgstr "Title"
|
||||||
msgid "schedule.import.shift.room"
|
msgid "schedule.import.shift.room"
|
||||||
msgstr "Room"
|
msgstr "Room"
|
||||||
|
|
||||||
|
msgid "shifts_history.schedule"
|
||||||
|
msgstr "Schedule: %s"
|
||||||
|
|
||||||
msgid "news.title"
|
msgid "news.title"
|
||||||
msgstr "News"
|
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
|
public function register(): void
|
||||||
{
|
{
|
||||||
Str::createUuidsUsing([$this, 'uuid']);
|
Str::createUuidsUsing(Uuid::class . '::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)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,15 +3,16 @@
|
||||||
namespace Engelsystem\Test\Unit\Helpers;
|
namespace Engelsystem\Test\Unit\Helpers;
|
||||||
|
|
||||||
use Engelsystem\Application;
|
use Engelsystem\Application;
|
||||||
|
use Engelsystem\Helpers\Uuid;
|
||||||
use Engelsystem\Helpers\UuidServiceProvider;
|
use Engelsystem\Helpers\UuidServiceProvider;
|
||||||
use Engelsystem\Test\Unit\ServiceProviderTest;
|
use Engelsystem\Test\Unit\ServiceProviderTest;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
use ReflectionProperty;
|
||||||
|
|
||||||
class UuidServiceProviderTest extends ServiceProviderTest
|
class UuidServiceProviderTest extends ServiceProviderTest
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @covers \Engelsystem\Helpers\UuidServiceProvider::register
|
* @covers \Engelsystem\Helpers\UuidServiceProvider::register
|
||||||
* @covers \Engelsystem\Helpers\UuidServiceProvider::uuid
|
|
||||||
*/
|
*/
|
||||||
public function testRegister(): void
|
public function testRegister(): void
|
||||||
{
|
{
|
||||||
|
@ -20,9 +21,13 @@ class UuidServiceProviderTest extends ServiceProviderTest
|
||||||
$serviceProvider = new UuidServiceProvider($app);
|
$serviceProvider = new UuidServiceProvider($app);
|
||||||
$serviceProvider->register();
|
$serviceProvider->register();
|
||||||
|
|
||||||
$this->assertStringMatchesFormat(
|
$uuidFactoryReference = (new ReflectionProperty(Str::class, 'uuidFactory'))
|
||||||
'%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',
|
->getValue();
|
||||||
Str::uuid()
|
|
||||||
);
|
$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