mirror of
https://github.com/archtechx/tenancy.git
synced 2026-09-20 12:34:03 +00:00
Add symlink support for prefixed disks
Disk prefixes are no longer ignored by tenants:link. possibleTenantSymlinks() now appends them to both the public path and the disk root. CreateStorageSymlinksAction now creates parent directories for the symlinks in the public/ directory (e.g. for a disk with 'abc/def' prefix, the 'abc/def' subdirectory will be created inside public/<url_override>). RemoveStorageSymlinksAction removes the directories that CreateStorageSymlinksAction creates for the symlinks.
This commit is contained in:
parent
9673464128
commit
b2b8d50edb
4 changed files with 144 additions and 2 deletions
|
|
@ -47,6 +47,12 @@ class CreateStorageSymlinksAction
|
||||||
mkdir($storagePath, 0777, true);
|
mkdir($storagePath, 0777, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The public path of a prefixed disk includes the prefix,
|
||||||
|
// and its parent directories may not exist yet.
|
||||||
|
if (! is_dir($publicParent = dirname($publicPath))) {
|
||||||
|
mkdir($publicParent, 0777, true);
|
||||||
|
}
|
||||||
|
|
||||||
if ($relativeLink) {
|
if ($relativeLink) {
|
||||||
app()->make('files')->relativeLink($storagePath, $publicPath);
|
app()->make('files')->relativeLink($storagePath, $publicPath);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -32,12 +32,24 @@ class RemoveStorageSymlinksAction
|
||||||
|
|
||||||
protected function removeLink(string $publicPath, Tenant $tenant): void
|
protected function removeLink(string $publicPath, Tenant $tenant): void
|
||||||
{
|
{
|
||||||
|
$files = app()->make('files');
|
||||||
|
|
||||||
if ($this->symlinkExists($publicPath)) {
|
if ($this->symlinkExists($publicPath)) {
|
||||||
event(new RemovingStorageSymlink($tenant));
|
event(new RemovingStorageSymlink($tenant));
|
||||||
|
|
||||||
app()->make('files')->delete($publicPath);
|
$files->delete($publicPath);
|
||||||
|
|
||||||
event(new StorageSymlinkRemoved($tenant));
|
event(new StorageSymlinkRemoved($tenant));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove the directories CreateStorageSymlinksAction created for the symlink
|
||||||
|
// until a non-empty one is reached.
|
||||||
|
$directory = dirname($publicPath);
|
||||||
|
|
||||||
|
while ($directory !== public_path() && $files->isEmptyDirectory($directory)) {
|
||||||
|
$files->deleteDirectory($directory);
|
||||||
|
|
||||||
|
$directory = dirname($directory);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -58,8 +58,16 @@ trait DealsWithTenantSymlinks
|
||||||
}
|
}
|
||||||
|
|
||||||
$publicPath = str_replace('%tenant%', (string) $tenantKey, $publicPath);
|
$publicPath = str_replace('%tenant%', (string) $tenantKey, $publicPath);
|
||||||
|
$diskRoot = $tenantDisks[$disk]['root'];
|
||||||
|
|
||||||
$symlinks[public_path($publicPath)] = $tenantDisks[$disk]['root'];
|
if ($prefix = trim($disks[$disk]['prefix'] ?? '', '/\\')) {
|
||||||
|
$diskRoot = rtrim($diskRoot, '/\\') . DIRECTORY_SEPARATOR . $prefix;
|
||||||
|
|
||||||
|
// Storage::url() appends the disk's prefix to the url, so the prefix has to be in the public path as well
|
||||||
|
$publicPath .= DIRECTORY_SEPARATOR . $prefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
$symlinks[public_path($publicPath)] = $diskRoot;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $symlinks;
|
return $symlinks;
|
||||||
|
|
|
||||||
|
|
@ -149,3 +149,119 @@ test('removing tenant symlinks works even if the symlinks are invalid', function
|
||||||
|
|
||||||
expect(is_link($publicPath))->toBeFalse();
|
expect(is_link($publicPath))->toBeFalse();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('disks with a prefix are symlinked correctly', function (string $prefix) {
|
||||||
|
config([
|
||||||
|
'tenancy.bootstrappers' => [
|
||||||
|
FilesystemTenancyBootstrapper::class,
|
||||||
|
],
|
||||||
|
'tenancy.filesystem.suffix_base' => 'tenant-',
|
||||||
|
'tenancy.filesystem.root_override.public' => '%storage_path%/app/public/',
|
||||||
|
'tenancy.filesystem.url_override.public' => 'public-%tenant%',
|
||||||
|
'filesystems.disks.public.prefix' => $prefix,
|
||||||
|
]);
|
||||||
|
|
||||||
|
/** @var Tenant $tenant */
|
||||||
|
$tenant = Tenant::create();
|
||||||
|
$tenantKey = $tenant->getTenantKey();
|
||||||
|
|
||||||
|
// possibleTenantSymlinks() trims the prefix, do the same here for the assertions to be accurate
|
||||||
|
$prefix = trim($prefix, '/');
|
||||||
|
|
||||||
|
tenancy()->initialize($tenant);
|
||||||
|
|
||||||
|
Storage::disk('public')->put('foo.txt', 'tenant file');
|
||||||
|
|
||||||
|
(new CreateStorageSymlinksAction)($tenant);
|
||||||
|
|
||||||
|
expect(Storage::disk('public')->url('foo.txt'))->toBe("http://localhost/public-{$tenantKey}/{$prefix}/foo.txt");
|
||||||
|
expect(readlink(public_path("public-{$tenantKey}/{$prefix}")))->toBe(storage_path("app/public/{$prefix}"));
|
||||||
|
expect(file_get_contents(public_path("public-{$tenantKey}/{$prefix}/foo.txt")))->toBe('tenant file');
|
||||||
|
})->with(['abc', 'abc/def', '/abc/def/']);
|
||||||
|
|
||||||
|
test('symlinks of prefixed disks only expose the prefixed directory', function () {
|
||||||
|
config([
|
||||||
|
'tenancy.bootstrappers' => [
|
||||||
|
FilesystemTenancyBootstrapper::class,
|
||||||
|
],
|
||||||
|
'tenancy.filesystem.root_override.public' => '%storage_path%/app/public/',
|
||||||
|
'tenancy.filesystem.url_override.public' => 'public-%tenant%',
|
||||||
|
'filesystems.disks.public.prefix' => 'abc',
|
||||||
|
]);
|
||||||
|
|
||||||
|
/** @var Tenant $tenant */
|
||||||
|
$tenant = Tenant::create();
|
||||||
|
$tenantKey = $tenant->getTenantKey();
|
||||||
|
|
||||||
|
tenancy()->initialize($tenant);
|
||||||
|
|
||||||
|
Storage::disk('public')->put('foo.txt', 'tenant file');
|
||||||
|
|
||||||
|
// The disk cannot reach this file, neither should the symlink
|
||||||
|
File::put(storage_path('app/public/sibling.txt'), 'file next to the prefixed directory');
|
||||||
|
|
||||||
|
(new CreateStorageSymlinksAction)($tenant);
|
||||||
|
|
||||||
|
expect(file_exists(public_path("public-{$tenantKey}/sibling.txt")))->toBeFalse();
|
||||||
|
expect(file_get_contents(public_path("public-{$tenantKey}/abc/foo.txt")))->toBe('tenant file');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('removing a prefixed disk symlink removes the directories created for it', function () {
|
||||||
|
config([
|
||||||
|
'tenancy.bootstrappers' => [
|
||||||
|
FilesystemTenancyBootstrapper::class,
|
||||||
|
],
|
||||||
|
'tenancy.filesystem.root_override.public' => '%storage_path%/app/public/',
|
||||||
|
'tenancy.filesystem.url_override.public' => 'public-%tenant%',
|
||||||
|
'filesystems.disks.public.prefix' => 'abc/def',
|
||||||
|
]);
|
||||||
|
|
||||||
|
/** @var Tenant $tenant */
|
||||||
|
$tenant = Tenant::create();
|
||||||
|
$tenantKey = $tenant->getTenantKey();
|
||||||
|
|
||||||
|
tenancy()->initialize($tenant);
|
||||||
|
|
||||||
|
Storage::disk('public')->put('foo.txt', 'tenant file');
|
||||||
|
|
||||||
|
(new CreateStorageSymlinksAction)($tenant);
|
||||||
|
|
||||||
|
$symlink = public_path("public-{$tenantKey}/abc/def");
|
||||||
|
$diskRoot = readlink($symlink);
|
||||||
|
|
||||||
|
(new RemoveStorageSymlinksAction)($tenant);
|
||||||
|
|
||||||
|
// The symlink and every directory created for it are deleted
|
||||||
|
expect(is_link($symlink))->toBeFalse();
|
||||||
|
expect(file_exists(public_path("public-{$tenantKey}")))->toBeFalse();
|
||||||
|
// public_path() itself is not deleted
|
||||||
|
expect(is_dir(public_path()))->toBeTrue();
|
||||||
|
// The directory that the symlink points to is untouched
|
||||||
|
expect(file_get_contents($diskRoot . '/foo.txt'))->toBe('tenant file');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('non-empty directories are not removed with the symlink', function () {
|
||||||
|
config([
|
||||||
|
'tenancy.bootstrappers' => [
|
||||||
|
FilesystemTenancyBootstrapper::class,
|
||||||
|
],
|
||||||
|
'tenancy.filesystem.root_override.public' => '%storage_path%/app/public/',
|
||||||
|
'tenancy.filesystem.url_override.public' => 'public-%tenant%',
|
||||||
|
'filesystems.disks.public.prefix' => 'abc/def',
|
||||||
|
]);
|
||||||
|
|
||||||
|
/** @var Tenant $tenant */
|
||||||
|
$tenant = Tenant::create();
|
||||||
|
$tenantKey = $tenant->getTenantKey();
|
||||||
|
|
||||||
|
tenancy()->initialize($tenant);
|
||||||
|
|
||||||
|
(new CreateStorageSymlinksAction)($tenant);
|
||||||
|
|
||||||
|
File::put(public_path("public-{$tenantKey}/abc/actual-file.txt"), 'foo');
|
||||||
|
|
||||||
|
(new RemoveStorageSymlinksAction)($tenant);
|
||||||
|
|
||||||
|
expect(is_link(public_path("public-{$tenantKey}/abc/def")))->toBeFalse();
|
||||||
|
expect(file_get_contents(public_path("public-{$tenantKey}/abc/actual-file.txt")))->toBe('foo');
|
||||||
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue