mirror of
https://github.com/archtechx/tenancy.git
synced 2025-12-12 17:04:04 +00:00
Update path identification and Fortify integration-related logic (#13)
* Add commented UrlBinding + FortifyRouteTenancy bootstrappers to the config * Improve FortifyRoute bootstrapper docblock * Rename bootstrappers * Complete renaming * Pass defaults of the original URL generator to the new one * Fix URL generator-related test (query string id test WIP) * Fix code style (php-cs-fixer) * Make Fortify bootstrapper not depend on the UrlGenerator bootstrapper, update comments * Fix testing UrlGenerator bootstrapper * Update TenancyUrlGenerator annotations * Pass tenant parameter manually in Fortify bootstrapper * Properly test TenancyUrlGenerator functionality * Get rid of query string in Fortify bootstrapper * Fix code style (php-cs-fixer) * Delete outdated comment * Improve comment * Improve before/afterEach * Encourage passing parameters using TenancyUrlGenerator instead of URL::defaults() * Delete rest of defaulting logic * Fix code style (php-cs-fixer) * Delete test group * Update ForgetTenantParameter docblock * Update passTenantParameterToRoutes annotation * Complete todo in test * Improve test * Update comment * Improve comment * Add keepQueryParameters bool to Fortify bootstrapper * Test keepQueryParameters * minor docblock update * minor docblock changes * Delete extra import * Update src/Overrides/TenancyUrlGenerator.php Co-authored-by: Samuel Štancl <samuel.stancl@gmail.com> * Improve comment * Rename test * Update bypass parameter-related test comments * Fix merge * Rename $keepQueryParameters * Add docblock * Add comment * Refactor Fortify bootstrapper * Fix code style (php-cs-fixer) * Fix comment * Skip Fortify bootstrapper test * minor code improvements * Improve fortify bootstrapper test * Add Fortify bootstrapper annotation, improve code * Fix code style (php-cs-fixer) * Add commenet * Complete resource syncing todo (cleanup not needed) * Delete incorrect namespace * Complete route context trait name todo * Fix code style (php-cs-fixer) --------- Co-authored-by: PHP CS Fixer <phpcsfixer@example.com> Co-authored-by: Samuel Štancl <samuel.stancl@gmail.com>
This commit is contained in:
parent
c043661318
commit
4953c69fd8
16 changed files with 255 additions and 141 deletions
|
|
@ -128,7 +128,7 @@ class TenancyServiceProvider extends ServiceProvider
|
||||||
/**
|
/**
|
||||||
* Example of CLI tenant URL root override:
|
* Example of CLI tenant URL root override:
|
||||||
*
|
*
|
||||||
* UrlTenancyBootstrapper::$rootUrlOverride = function (Tenant $tenant) {
|
* RootUrlBootstrapper::$rootUrlOverride = function (Tenant $tenant) {
|
||||||
* $baseUrl = env('APP_URL');
|
* $baseUrl = env('APP_URL');
|
||||||
* $scheme = str($baseUrl)->before('://');
|
* $scheme = str($baseUrl)->before('://');
|
||||||
* $hostname = str($baseUrl)->after($scheme . '://');
|
* $hostname = str($baseUrl)->after($scheme . '://');
|
||||||
|
|
|
||||||
|
|
@ -125,7 +125,9 @@ return [
|
||||||
Stancl\Tenancy\Bootstrappers\QueueTenancyBootstrapper::class,
|
Stancl\Tenancy\Bootstrappers\QueueTenancyBootstrapper::class,
|
||||||
Stancl\Tenancy\Bootstrappers\BatchTenancyBootstrapper::class,
|
Stancl\Tenancy\Bootstrappers\BatchTenancyBootstrapper::class,
|
||||||
// Stancl\Tenancy\Bootstrappers\PrefixCacheTenancyBootstrapper::class,
|
// Stancl\Tenancy\Bootstrappers\PrefixCacheTenancyBootstrapper::class,
|
||||||
// Stancl\Tenancy\Bootstrappers\UrlTenancyBootstrapper::class,
|
// Stancl\Tenancy\Bootstrappers\RootUrlBootstrapper::class,
|
||||||
|
// Stancl\Tenancy\Bootstrappers\UrlGeneratorBootstrapper::class,
|
||||||
|
// Stancl\Tenancy\Bootstrappers\Integrations\FortifyRouteTenancyBootstrapper,
|
||||||
// Stancl\Tenancy\Bootstrappers\SessionTenancyBootstrapper::class,
|
// Stancl\Tenancy\Bootstrappers\SessionTenancyBootstrapper::class,
|
||||||
// Stancl\Tenancy\Bootstrappers\MailTenancyBootstrapper::class, // Queueing mail requires using QueueTenancyBootstrapper with $forceRefresh set to true
|
// Stancl\Tenancy\Bootstrappers\MailTenancyBootstrapper::class, // Queueing mail requires using QueueTenancyBootstrapper with $forceRefresh set to true
|
||||||
// Stancl\Tenancy\Bootstrappers\RedisTenancyBootstrapper::class, // Note: phpredis is needed
|
// Stancl\Tenancy\Bootstrappers\RedisTenancyBootstrapper::class, // Note: phpredis is needed
|
||||||
|
|
|
||||||
|
|
@ -7,23 +7,56 @@ namespace Stancl\Tenancy\Bootstrappers\Integrations;
|
||||||
use Illuminate\Config\Repository;
|
use Illuminate\Config\Repository;
|
||||||
use Stancl\Tenancy\Contracts\TenancyBootstrapper;
|
use Stancl\Tenancy\Contracts\TenancyBootstrapper;
|
||||||
use Stancl\Tenancy\Contracts\Tenant;
|
use Stancl\Tenancy\Contracts\Tenant;
|
||||||
|
use Stancl\Tenancy\Enums\Context;
|
||||||
|
use Stancl\Tenancy\Resolvers\PathTenantResolver;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows customizing Fortify redirect URLs.
|
* Allows customizing Fortify action redirects
|
||||||
* Intended to be used with UrlBindingBootstrapper.
|
* so that they can also redirect to tenant routes instead of just the central routes.
|
||||||
*
|
*
|
||||||
* @see \Stancl\Tenancy\Bootstrappers\UrlBindingBootstrapper
|
* Works with path and query string identification.
|
||||||
*/
|
*/
|
||||||
class FortifyRouteTenancyBootstrapper implements TenancyBootstrapper
|
class FortifyRouteTenancyBootstrapper implements TenancyBootstrapper
|
||||||
{
|
{
|
||||||
// 'fortify_action' => 'tenant_route_name'
|
/**
|
||||||
public static array $fortifyRedirectTenantMap = [
|
* Make Fortify actions redirect to custom routes.
|
||||||
// 'logout' => 'welcome',
|
*
|
||||||
];
|
* For each route redirect, specify the intended route context (central or tenant).
|
||||||
|
* Based on the provided context, we pass the tenant parameter to the route (or not).
|
||||||
|
* The tenant parameter is only passed to the route when you specify its context as tenant.
|
||||||
|
*
|
||||||
|
* The route redirects should be in the following format:
|
||||||
|
*
|
||||||
|
* 'fortify_action' => [
|
||||||
|
* 'route_name' => 'tenant.route',
|
||||||
|
* 'context' => Context::TENANT,
|
||||||
|
* ]
|
||||||
|
*
|
||||||
|
* For example:
|
||||||
|
*
|
||||||
|
* FortifyRouteTenancyBootstrapper::$fortifyRedirectMap = [
|
||||||
|
* // On logout, redirect the user to the "bye" route in the central app
|
||||||
|
* 'logout' => [
|
||||||
|
* 'route_name' => 'bye',
|
||||||
|
* 'context' => Context::CENTRAL,
|
||||||
|
* ],
|
||||||
|
*
|
||||||
|
* // On login, redirect the user to the "welcome" route in the tenant app
|
||||||
|
* 'login' => [
|
||||||
|
* 'route_name' => 'welcome',
|
||||||
|
* 'context' => Context::TENANT,
|
||||||
|
* ],
|
||||||
|
*];
|
||||||
|
*/
|
||||||
|
public static array $fortifyRedirectMap = [];
|
||||||
|
|
||||||
// Fortify home route name
|
/**
|
||||||
public static string|null $fortifyHome = 'dashboard';
|
* Tenant route that serves as Fortify's home (e.g. a tenant dashboard route).
|
||||||
protected array|null $originalFortifyConfig = null;
|
* This route will always receive the tenant parameter.
|
||||||
|
*/
|
||||||
|
public static string $fortifyHome = 'tenant.dashboard';
|
||||||
|
|
||||||
|
protected array $originalFortifyConfig = [];
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
protected Repository $config,
|
protected Repository $config,
|
||||||
|
|
@ -32,9 +65,9 @@ class FortifyRouteTenancyBootstrapper implements TenancyBootstrapper
|
||||||
|
|
||||||
public function bootstrap(Tenant $tenant): void
|
public function bootstrap(Tenant $tenant): void
|
||||||
{
|
{
|
||||||
$this->originalFortifyConfig = $this->config->get('fortify');
|
$this->originalFortifyConfig = $this->config->get('fortify') ?? [];
|
||||||
|
|
||||||
$this->useTenantRoutesInFortify();
|
$this->useTenantRoutesInFortify($tenant);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function revert(): void
|
public function revert(): void
|
||||||
|
|
@ -42,16 +75,31 @@ class FortifyRouteTenancyBootstrapper implements TenancyBootstrapper
|
||||||
$this->config->set('fortify', $this->originalFortifyConfig);
|
$this->config->set('fortify', $this->originalFortifyConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function useTenantRoutesInFortify(): void
|
protected function useTenantRoutesInFortify(Tenant $tenant): void
|
||||||
{
|
{
|
||||||
// Regenerate the URLs after the behavior of the route() helper has been modified
|
$tenantKey = $tenant->getTenantKey();
|
||||||
// in UrlBindingBootstrapper to generate URLs specific to the current tenant
|
$tenantParameterName = PathTenantResolver::tenantParameterName();
|
||||||
$tenantRoutes = array_map(fn (string $routeName) => route($routeName), static::$fortifyRedirectTenantMap);
|
|
||||||
|
$generateLink = function (array $redirect) use ($tenantKey, $tenantParameterName) {
|
||||||
|
// Specifying the context is only required with query string identification
|
||||||
|
// because with path identification, the tenant parameter should always present
|
||||||
|
$passTenantParameter = $redirect['context'] === Context::TENANT;
|
||||||
|
|
||||||
|
// Only pass the tenant parameter when the user should be redirected to a tenant route
|
||||||
|
return route($redirect['route_name'], $passTenantParameter ? [$tenantParameterName => $tenantKey] : []);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get redirect URLs for the configured redirect routes
|
||||||
|
$redirects = array_merge(
|
||||||
|
$this->originalFortifyConfig['redirects'] ?? [], // Fortify config redirects
|
||||||
|
array_map(fn (array $redirect) => $generateLink($redirect), static::$fortifyRedirectMap), // Mapped redirects
|
||||||
|
);
|
||||||
|
|
||||||
if (static::$fortifyHome) {
|
if (static::$fortifyHome) {
|
||||||
$this->config->set('fortify.home', route(static::$fortifyHome));
|
// Generate the home route URL with the tenant parameter and make it the Fortify home route
|
||||||
|
$this->config->set('fortify.home', route(static::$fortifyHome, [$tenantParameterName => $tenantKey]));
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->config->set('fortify.redirects', array_merge($this->config->get('fortify.redirects') ?? [], $tenantRoutes));
|
$this->config->set('fortify.redirects', $redirects);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ use Illuminate\Contracts\Routing\UrlGenerator;
|
||||||
use Stancl\Tenancy\Contracts\TenancyBootstrapper;
|
use Stancl\Tenancy\Contracts\TenancyBootstrapper;
|
||||||
use Stancl\Tenancy\Contracts\Tenant;
|
use Stancl\Tenancy\Contracts\Tenant;
|
||||||
|
|
||||||
class UrlTenancyBootstrapper implements TenancyBootstrapper
|
class RootUrlBootstrapper implements TenancyBootstrapper
|
||||||
{
|
{
|
||||||
public static Closure|null $rootUrlOverride = null;
|
public static Closure|null $rootUrlOverride = null;
|
||||||
protected string|null $originalRootUrl = null;
|
protected string|null $originalRootUrl = null;
|
||||||
|
|
@ -21,7 +21,7 @@ use Stancl\Tenancy\Overrides\TenancyUrlGenerator;
|
||||||
* @see TenancyUrlGenerator
|
* @see TenancyUrlGenerator
|
||||||
* @see \Stancl\Tenancy\Resolvers\PathTenantResolver
|
* @see \Stancl\Tenancy\Resolvers\PathTenantResolver
|
||||||
*/
|
*/
|
||||||
class UrlBindingBootstrapper implements TenancyBootstrapper
|
class UrlGeneratorBootstrapper implements TenancyBootstrapper
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
protected Application $app,
|
protected Application $app,
|
||||||
|
|
@ -55,6 +55,8 @@ class UrlBindingBootstrapper implements TenancyBootstrapper
|
||||||
$app['config']->get('app.asset_url'),
|
$app['config']->get('app.asset_url'),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$newGenerator->defaults($urlGenerator->getDefaultParameters());
|
||||||
|
|
||||||
$newGenerator->setSessionResolver(function () {
|
$newGenerator->setSessionResolver(function () {
|
||||||
return $this->app['session'] ?? null;
|
return $this->app['session'] ?? null;
|
||||||
});
|
});
|
||||||
|
|
@ -13,8 +13,7 @@ use Illuminate\Support\Arr;
|
||||||
use Illuminate\Support\Facades\Route as RouteFacade;
|
use Illuminate\Support\Facades\Route as RouteFacade;
|
||||||
use Stancl\Tenancy\Enums\RouteMode;
|
use Stancl\Tenancy\Enums\RouteMode;
|
||||||
|
|
||||||
// todo1 Name – maybe DealsWithMiddlewareContexts?
|
trait DealsWithRouteContexts
|
||||||
trait DealsWithEarlyIdentification
|
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Get route's middleware context (tenant, central or universal).
|
* Get route's middleware context (tenant, central or universal).
|
||||||
|
|
@ -11,12 +11,13 @@ use Stancl\Tenancy\PathIdentificationManager;
|
||||||
/**
|
/**
|
||||||
* Remove the tenant parameter from the matched route when path identification is used globally.
|
* Remove the tenant parameter from the matched route when path identification is used globally.
|
||||||
*
|
*
|
||||||
* The tenant parameter gets forgotten using PathTenantResolver so that the route actions don't have to accept it.
|
* While initializing tenancy, we forget the tenant parameter (in PathTenantResolver),
|
||||||
* Then, tenancy gets initialized, and URL::defaults() is used to give the tenant parameter to the next matched route.
|
* so that the route actions don't have to accept it.
|
||||||
* But with kernel identification, the route gets matched AFTER the point when URL::defaults() is used,
|
|
||||||
* and because of that, the matched route gets the tenant parameter again, so we forget the parameter again on RouteMatched.
|
|
||||||
*
|
*
|
||||||
* We remove the {tenant} parameter from the hydrated route when
|
* With kernel identification, tenancy gets initialized before the route gets matched.
|
||||||
|
* The matched route gets the tenant parameter again, so we have to forget the parameter again on RouteMatched.
|
||||||
|
*
|
||||||
|
* We remove the {tenant} parameter from the matched route when
|
||||||
* 1) the InitializeTenancyByPath middleware is in the global stack, AND
|
* 1) the InitializeTenancyByPath middleware is in the global stack, AND
|
||||||
* 2) the matched route does not have identification middleware (so that {tenant} isn't forgotten when using route-level identification), AND
|
* 2) the matched route does not have identification middleware (so that {tenant} isn't forgotten when using route-level identification), AND
|
||||||
* 3) the route isn't in the central context (so that {tenant} doesn't get accidentally removed from central routes).
|
* 3) the route isn't in the central context (so that {tenant} doesn't get accidentally removed from central routes).
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,9 @@ namespace Stancl\Tenancy\Middleware;
|
||||||
|
|
||||||
use Closure;
|
use Closure;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Event;
|
|
||||||
use Illuminate\Support\Facades\URL;
|
|
||||||
use Stancl\Tenancy\Concerns\UsableWithEarlyIdentification;
|
use Stancl\Tenancy\Concerns\UsableWithEarlyIdentification;
|
||||||
use Stancl\Tenancy\Concerns\UsableWithUniversalRoutes;
|
use Stancl\Tenancy\Concerns\UsableWithUniversalRoutes;
|
||||||
use Stancl\Tenancy\Contracts\Tenant;
|
|
||||||
use Stancl\Tenancy\Enums\RouteMode;
|
use Stancl\Tenancy\Enums\RouteMode;
|
||||||
use Stancl\Tenancy\Events\InitializingTenancy;
|
|
||||||
use Stancl\Tenancy\Exceptions\RouteIsMissingTenantParameterException;
|
use Stancl\Tenancy\Exceptions\RouteIsMissingTenantParameterException;
|
||||||
use Stancl\Tenancy\Overrides\TenancyUrlGenerator;
|
use Stancl\Tenancy\Overrides\TenancyUrlGenerator;
|
||||||
use Stancl\Tenancy\Resolvers\PathTenantResolver;
|
use Stancl\Tenancy\Resolvers\PathTenantResolver;
|
||||||
|
|
@ -49,8 +45,6 @@ class InitializeTenancyByPath extends IdentificationMiddleware implements Usable
|
||||||
// We don't want to initialize tenancy if the tenant is
|
// We don't want to initialize tenancy if the tenant is
|
||||||
// simply injected into some route controller action.
|
// simply injected into some route controller action.
|
||||||
if (in_array(PathTenantResolver::tenantParameterName(), $route->parameterNames())) {
|
if (in_array(PathTenantResolver::tenantParameterName(), $route->parameterNames())) {
|
||||||
$this->setDefaultTenantForRouteParametersWhenInitializingTenancy();
|
|
||||||
|
|
||||||
return $this->initializeTenancy(
|
return $this->initializeTenancy(
|
||||||
$request,
|
$request,
|
||||||
$next,
|
$next,
|
||||||
|
|
@ -61,18 +55,6 @@ class InitializeTenancyByPath extends IdentificationMiddleware implements Usable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function setDefaultTenantForRouteParametersWhenInitializingTenancy(): void
|
|
||||||
{
|
|
||||||
Event::listen(InitializingTenancy::class, function (InitializingTenancy $event) {
|
|
||||||
/** @var Tenant $tenant */
|
|
||||||
$tenant = $event->tenancy->tenant;
|
|
||||||
|
|
||||||
URL::defaults([
|
|
||||||
PathTenantResolver::tenantParameterName() => $tenant->getTenantKey(),
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Path identification request has a tenant if the middleware context is tenant.
|
* Path identification request has a tenant if the middleware context is tenant.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ use Illuminate\Support\Arr;
|
||||||
use Stancl\Tenancy\PathIdentificationManager;
|
use Stancl\Tenancy\PathIdentificationManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class is used in place of the default UrlGenerator when UrlBindingBootstrapper is enabled.
|
* This class is used in place of the default UrlGenerator when UrlGeneratorBootstrapper is enabled.
|
||||||
*
|
*
|
||||||
* TenancyUrlGenerator does two extra things:
|
* TenancyUrlGenerator does two extra things:
|
||||||
* 1. Autofill the {tenant} parameter in the tenant context with the current tenant if $passTenantParameterToRoutes is enabled (enabled by default)
|
* 1. Autofill the {tenant} parameter in the tenant context with the current tenant if $passTenantParameterToRoutes is enabled (enabled by default)
|
||||||
|
|
@ -28,16 +28,20 @@ class TenancyUrlGenerator extends UrlGenerator
|
||||||
public static string $bypassParameter = 'central';
|
public static string $bypassParameter = 'central';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if the route names of routes generated using
|
* Determine if the route names passed to `route()` or `temporarySignedRoute()`
|
||||||
* `route()` or `temporarySignedRoute()` should get prefixed with the tenant route name prefix.
|
* should get prefixed with the tenant route name prefix.
|
||||||
*
|
*
|
||||||
* Set this to true when using path identification.
|
* This is useful when using path identification with packages that generate URLs,
|
||||||
|
* like Jetstream, so that you don't have to manually prefix route names passed to each route() call.
|
||||||
*/
|
*/
|
||||||
public static bool $prefixRouteNames = false;
|
public static bool $prefixRouteNames = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if the tenant parameter should get passed
|
* Determine if the tenant parameter should get passed
|
||||||
* to the links generated by `route()` or `temporarySignedRoute()`.
|
* to the links generated by `route()` or `temporarySignedRoute()` whenever available
|
||||||
|
* (enabled by default – works with both path and query string identification).
|
||||||
|
*
|
||||||
|
* With path identification, you can disable this and use URL::defaults() instead (as an alternative solution).
|
||||||
*/
|
*/
|
||||||
public static bool $passTenantParameterToRoutes = true;
|
public static bool $passTenantParameterToRoutes = true;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,14 +8,14 @@ use Closure;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Support\Traits\Macroable;
|
use Illuminate\Support\Traits\Macroable;
|
||||||
use Stancl\Tenancy\Concerns\DealsWithEarlyIdentification;
|
use Stancl\Tenancy\Concerns\DealsWithRouteContexts;
|
||||||
use Stancl\Tenancy\Contracts\TenancyBootstrapper;
|
use Stancl\Tenancy\Contracts\TenancyBootstrapper;
|
||||||
use Stancl\Tenancy\Contracts\Tenant;
|
use Stancl\Tenancy\Contracts\Tenant;
|
||||||
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedByIdException;
|
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedByIdException;
|
||||||
|
|
||||||
class Tenancy
|
class Tenancy
|
||||||
{
|
{
|
||||||
use Macroable, DealsWithEarlyIdentification;
|
use Macroable, DealsWithRouteContexts;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current tenant.
|
* The current tenant.
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,10 @@
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
use Stancl\Tenancy\Enums\Context;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Support\Facades\URL;
|
use Illuminate\Support\Facades\URL;
|
||||||
use Stancl\JobPipeline\JobPipeline;
|
use Stancl\JobPipeline\JobPipeline;
|
||||||
use Illuminate\Broadcasting\Channel;
|
|
||||||
use Illuminate\Support\Facades\File;
|
use Illuminate\Support\Facades\File;
|
||||||
use Stancl\Tenancy\Tests\Etc\Tenant;
|
use Stancl\Tenancy\Tests\Etc\Tenant;
|
||||||
use Illuminate\Support\Facades\Cache;
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
|
@ -20,9 +20,7 @@ use Stancl\Tenancy\Jobs\CreateDatabase;
|
||||||
use Stancl\Tenancy\Events\TenantCreated;
|
use Stancl\Tenancy\Events\TenantCreated;
|
||||||
use Stancl\Tenancy\Events\TenantDeleted;
|
use Stancl\Tenancy\Events\TenantDeleted;
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
use Illuminate\Support\Facades\Broadcast;
|
|
||||||
use Stancl\Tenancy\Events\DeletingTenant;
|
use Stancl\Tenancy\Events\DeletingTenant;
|
||||||
use Illuminate\Broadcasting\PrivateChannel;
|
|
||||||
use Illuminate\Filesystem\FilesystemAdapter;
|
use Illuminate\Filesystem\FilesystemAdapter;
|
||||||
use Illuminate\Broadcasting\BroadcastManager;
|
use Illuminate\Broadcasting\BroadcastManager;
|
||||||
use Stancl\Tenancy\Events\TenancyInitialized;
|
use Stancl\Tenancy\Events\TenancyInitialized;
|
||||||
|
|
@ -30,25 +28,26 @@ use Illuminate\Contracts\Routing\UrlGenerator;
|
||||||
use Stancl\Tenancy\Jobs\CreateStorageSymlinks;
|
use Stancl\Tenancy\Jobs\CreateStorageSymlinks;
|
||||||
use Stancl\Tenancy\Jobs\RemoveStorageSymlinks;
|
use Stancl\Tenancy\Jobs\RemoveStorageSymlinks;
|
||||||
use Stancl\Tenancy\Listeners\BootstrapTenancy;
|
use Stancl\Tenancy\Listeners\BootstrapTenancy;
|
||||||
|
use Stancl\Tenancy\Resolvers\PathTenantResolver;
|
||||||
use Stancl\Tenancy\Tests\Etc\TestingBroadcaster;
|
use Stancl\Tenancy\Tests\Etc\TestingBroadcaster;
|
||||||
use Stancl\Tenancy\Listeners\DeleteTenantStorage;
|
use Stancl\Tenancy\Listeners\DeleteTenantStorage;
|
||||||
use Stancl\Tenancy\Overrides\TenancyUrlGenerator;
|
use Stancl\Tenancy\Overrides\TenancyUrlGenerator;
|
||||||
use Stancl\Tenancy\Listeners\RevertToCentralContext;
|
use Stancl\Tenancy\Listeners\RevertToCentralContext;
|
||||||
|
use Stancl\Tenancy\Bootstrappers\RootUrlBootstrapper;
|
||||||
use Stancl\Tenancy\Overrides\TenancyBroadcastManager;
|
use Stancl\Tenancy\Overrides\TenancyBroadcastManager;
|
||||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
|
||||||
use Stancl\Tenancy\Middleware\InitializeTenancyByPath;
|
use Stancl\Tenancy\Middleware\InitializeTenancyByPath;
|
||||||
use Stancl\Tenancy\Bootstrappers\CacheTagsBootstrapper;
|
use Stancl\Tenancy\Bootstrappers\CacheTagsBootstrapper;
|
||||||
use Stancl\Tenancy\Bootstrappers\UrlBindingBootstrapper;
|
use Illuminate\Routing\Exceptions\UrlGenerationException;
|
||||||
use Stancl\Tenancy\Bootstrappers\UrlTenancyBootstrapper;
|
|
||||||
use Stancl\Tenancy\Bootstrappers\MailTenancyBootstrapper;
|
use Stancl\Tenancy\Bootstrappers\MailTenancyBootstrapper;
|
||||||
use Stancl\Tenancy\Bootstrappers\RedisTenancyBootstrapper;
|
use Stancl\Tenancy\Bootstrappers\RedisTenancyBootstrapper;
|
||||||
|
use Stancl\Tenancy\Bootstrappers\UrlGeneratorBootstrapper;
|
||||||
use Stancl\Tenancy\Middleware\InitializeTenancyBySubdomain;
|
use Stancl\Tenancy\Middleware\InitializeTenancyBySubdomain;
|
||||||
use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper;
|
use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper;
|
||||||
use Stancl\Tenancy\Middleware\InitializeTenancyByRequestData;
|
use Stancl\Tenancy\Middleware\InitializeTenancyByRequestData;
|
||||||
use Stancl\Tenancy\Bootstrappers\BroadcastChannelPrefixBootstrapper;
|
|
||||||
use Stancl\Tenancy\Bootstrappers\FilesystemTenancyBootstrapper;
|
use Stancl\Tenancy\Bootstrappers\FilesystemTenancyBootstrapper;
|
||||||
use Stancl\Tenancy\Bootstrappers\BroadcastingConfigBootstrapper;
|
use Stancl\Tenancy\Bootstrappers\BroadcastingConfigBootstrapper;
|
||||||
use Stancl\Tenancy\Bootstrappers\PrefixCacheTenancyBootstrapper;
|
use Stancl\Tenancy\Bootstrappers\PrefixCacheTenancyBootstrapper;
|
||||||
|
use Stancl\Tenancy\Bootstrappers\BroadcastChannelPrefixBootstrapper;
|
||||||
use Stancl\Tenancy\Bootstrappers\Integrations\FortifyRouteTenancyBootstrapper;
|
use Stancl\Tenancy\Bootstrappers\Integrations\FortifyRouteTenancyBootstrapper;
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
|
|
@ -59,7 +58,7 @@ beforeEach(function () {
|
||||||
// Reset static properties of classes used in this test file to their default values
|
// Reset static properties of classes used in this test file to their default values
|
||||||
BroadcastingConfigBootstrapper::$credentialsMap = [];
|
BroadcastingConfigBootstrapper::$credentialsMap = [];
|
||||||
TenancyBroadcastManager::$tenantBroadcasters = ['pusher', 'ably'];
|
TenancyBroadcastManager::$tenantBroadcasters = ['pusher', 'ably'];
|
||||||
UrlTenancyBootstrapper::$rootUrlOverride = null;
|
RootUrlBootstrapper::$rootUrlOverride = null;
|
||||||
|
|
||||||
Event::listen(
|
Event::listen(
|
||||||
TenantCreated::class,
|
TenantCreated::class,
|
||||||
|
|
@ -74,11 +73,12 @@ beforeEach(function () {
|
||||||
|
|
||||||
afterEach(function () {
|
afterEach(function () {
|
||||||
// Reset static properties of classes used in this test file to their default values
|
// Reset static properties of classes used in this test file to their default values
|
||||||
UrlTenancyBootstrapper::$rootUrlOverride = null;
|
RootUrlBootstrapper::$rootUrlOverride = null;
|
||||||
PrefixCacheTenancyBootstrapper::$tenantCacheStores = [];
|
PrefixCacheTenancyBootstrapper::$tenantCacheStores = [];
|
||||||
TenancyBroadcastManager::$tenantBroadcasters = ['pusher', 'ably'];
|
TenancyBroadcastManager::$tenantBroadcasters = ['pusher', 'ably'];
|
||||||
BroadcastingConfigBootstrapper::$credentialsMap = [];
|
BroadcastingConfigBootstrapper::$credentialsMap = [];
|
||||||
TenancyUrlGenerator::$prefixRouteNames = false;
|
TenancyUrlGenerator::$prefixRouteNames = false;
|
||||||
|
TenancyUrlGenerator::$passTenantParameterToRoutes = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
test('database data is separated', function () {
|
test('database data is separated', function () {
|
||||||
|
|
@ -501,7 +501,7 @@ test('MailTenancyBootstrapper reverts the config and mailer credentials to defau
|
||||||
});
|
});
|
||||||
|
|
||||||
test('url bootstrapper overrides the root url when tenancy gets initialized and reverts the url to the central one after tenancy ends', function() {
|
test('url bootstrapper overrides the root url when tenancy gets initialized and reverts the url to the central one after tenancy ends', function() {
|
||||||
config(['tenancy.bootstrappers' => [UrlTenancyBootstrapper::class]]);
|
config(['tenancy.bootstrappers' => [RootUrlBootstrapper::class]]);
|
||||||
|
|
||||||
Route::group([
|
Route::group([
|
||||||
'middleware' => InitializeTenancyBySubdomain::class,
|
'middleware' => InitializeTenancyBySubdomain::class,
|
||||||
|
|
@ -521,7 +521,7 @@ test('url bootstrapper overrides the root url when tenancy gets initialized and
|
||||||
return $scheme . '://' . $tenant->getTenantKey() . '.' . $hostname;
|
return $scheme . '://' . $tenant->getTenantKey() . '.' . $hostname;
|
||||||
};
|
};
|
||||||
|
|
||||||
UrlTenancyBootstrapper::$rootUrlOverride = $rootUrlOverride;
|
RootUrlBootstrapper::$rootUrlOverride = $rootUrlOverride;
|
||||||
|
|
||||||
$tenant = Tenant::create();
|
$tenant = Tenant::create();
|
||||||
$tenantUrl = $rootUrlOverride($tenant);
|
$tenantUrl = $rootUrlOverride($tenant);
|
||||||
|
|
@ -546,36 +546,43 @@ test('url bootstrapper overrides the root url when tenancy gets initialized and
|
||||||
});
|
});
|
||||||
|
|
||||||
test('url binding tenancy bootstrapper swaps the url generator instance correctly', function() {
|
test('url binding tenancy bootstrapper swaps the url generator instance correctly', function() {
|
||||||
config(['tenancy.bootstrappers' => [UrlBindingBootstrapper::class]]);
|
config(['tenancy.bootstrappers' => [UrlGeneratorBootstrapper::class]]);
|
||||||
|
|
||||||
tenancy()->initialize(Tenant::create());
|
tenancy()->initialize(Tenant::create());
|
||||||
expect(app('url'))->toBeInstanceOf(TenancyUrlGenerator::class);
|
expect(app('url'))->toBeInstanceOf(TenancyUrlGenerator::class);
|
||||||
expect(url())->toBeInstanceOf(TenancyUrlGenerator::class);
|
expect(url())->toBeInstanceOf(TenancyUrlGenerator::class);
|
||||||
|
|
||||||
tenancy()->end();
|
tenancy()->end();
|
||||||
expect(app('url'))->toBeInstanceOf(UrlGenerator::class);
|
expect(app('url'))->toBeInstanceOf(UrlGenerator::class)
|
||||||
expect(url())->toBeInstanceOf(UrlGenerator::class);
|
->not()->toBeInstanceOf(TenancyUrlGenerator::class);
|
||||||
|
expect(url())->toBeInstanceOf(UrlGenerator::class)
|
||||||
|
->not()->toBeInstanceOf(TenancyUrlGenerator::class);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('url binding tenancy bootstrapper changes route helper behavior correctly', function() {
|
test('url generator bootstrapper can prefix route names passed to the route helper', function() {
|
||||||
Route::get('/central/home', fn () => route('home'))->name('home');
|
Route::get('/central/home', fn () => route('home'))->name('home');
|
||||||
// Tenant route name prefix is 'tenant.' by default
|
// Tenant route name prefix is 'tenant.' by default
|
||||||
Route::get('/{tenant}/home', fn () => route('tenant.home'))->name('tenant.home')->middleware(['tenant', InitializeTenancyByPath::class]);
|
Route::get('/{tenant}/home', fn () => route('tenant.home'))->name('tenant.home')->middleware(['tenant', InitializeTenancyByPath::class]);
|
||||||
Route::get('/query-string', fn () => route('query-string'))->name('query-string')->middleware(['tenant', InitializeTenancyByRequestData::class]);
|
|
||||||
|
|
||||||
$tenant = Tenant::create();
|
$tenant = Tenant::create();
|
||||||
$tenantKey = $tenant->getTenantKey();
|
$tenantKey = $tenant->getTenantKey();
|
||||||
$centralRouteUrl = route('home');
|
$centralRouteUrl = route('home');
|
||||||
$tenantRouteUrl = route('tenant.home', ['tenant' => $tenantKey]);
|
$tenantRouteUrl = route('tenant.home', ['tenant' => $tenantKey]);
|
||||||
$queryStringCentralUrl = route('query-string');
|
|
||||||
$queryStringTenantUrl = route('query-string', ['tenant' => $tenantKey]);
|
|
||||||
TenancyUrlGenerator::$bypassParameter = 'bypassParameter';
|
TenancyUrlGenerator::$bypassParameter = 'bypassParameter';
|
||||||
$bypassParameter = TenancyUrlGenerator::$bypassParameter;
|
|
||||||
|
|
||||||
config(['tenancy.bootstrappers' => [UrlBindingBootstrapper::class]]);
|
config(['tenancy.bootstrappers' => [UrlGeneratorBootstrapper::class]]);
|
||||||
TenancyUrlGenerator::$prefixRouteNames = true;
|
|
||||||
|
|
||||||
tenancy()->initialize($tenant);
|
tenancy()->initialize($tenant);
|
||||||
|
|
||||||
|
// Route names don't get prefixed when TenancyUrlGenerator::$prefixRouteNames is false
|
||||||
|
expect(route('home'))->not()->toBe($centralRouteUrl);
|
||||||
|
// When TenancyUrlGenerator::$passTenantParameterToRoutes is true (default)
|
||||||
|
// The route helper receives the tenant parameter
|
||||||
|
// So in order to generate central URL, we have to pass the bypass parameter
|
||||||
|
expect(route('home', ['bypassParameter' => true]))->toBe($centralRouteUrl);
|
||||||
|
|
||||||
|
|
||||||
|
TenancyUrlGenerator::$prefixRouteNames = true;
|
||||||
// The $prefixRouteNames property is true
|
// The $prefixRouteNames property is true
|
||||||
// The route name passed to the route() helper ('home') gets prefixed prefixed with 'tenant.' automatically
|
// The route name passed to the route() helper ('home') gets prefixed prefixed with 'tenant.' automatically
|
||||||
expect(route('home'))->toBe($tenantRouteUrl);
|
expect(route('home'))->toBe($tenantRouteUrl);
|
||||||
|
|
@ -584,58 +591,149 @@ test('url binding tenancy bootstrapper changes route helper behavior correctly',
|
||||||
// Also, the route receives the tenant parameter automatically
|
// Also, the route receives the tenant parameter automatically
|
||||||
expect(route('tenant.home'))->toBe($tenantRouteUrl);
|
expect(route('tenant.home'))->toBe($tenantRouteUrl);
|
||||||
|
|
||||||
// The $bypassParameter parameter ('central' by default) can bypass the route name prefixing
|
|
||||||
// When the bypass parameter is true, the generated route URL points to the route named 'home'
|
|
||||||
// Also, check if the bypass parameter gets removed from the generated URL query string
|
|
||||||
expect(route('home', [$bypassParameter => true]))->toBe($centralRouteUrl)
|
|
||||||
->not()->toContain($bypassParameter);
|
|
||||||
// When the bypass parameter is false, the generated route URL points to the prefixed route ('tenant.home')
|
|
||||||
expect(route('home', [$bypassParameter => false]))->toBe($tenantRouteUrl)
|
|
||||||
->not()->toContain($bypassParameter);
|
|
||||||
|
|
||||||
TenancyUrlGenerator::$prefixRouteNames = false;
|
|
||||||
// Route names don't get prefixed – TenancyUrlGenerator::$prefixRouteNames is false
|
|
||||||
expect(route('home', [$bypassParameter => true]))->toBe($centralRouteUrl);
|
|
||||||
expect(route('query-string'))->toBe($queryStringTenantUrl);
|
|
||||||
|
|
||||||
TenancyUrlGenerator::$passTenantParameterToRoutes = false;
|
|
||||||
expect(route('query-string'))->toBe($queryStringCentralUrl);
|
|
||||||
|
|
||||||
TenancyUrlGenerator::$passTenantParameterToRoutes = true;
|
|
||||||
expect(route('query-string'))->toBe($queryStringTenantUrl);
|
|
||||||
|
|
||||||
// Ending tenancy reverts route() behavior changes
|
// Ending tenancy reverts route() behavior changes
|
||||||
tenancy()->end();
|
tenancy()->end();
|
||||||
|
|
||||||
expect(route('home'))->toBe($centralRouteUrl);
|
expect(route('home'))->toBe($centralRouteUrl);
|
||||||
expect(route('query-string'))->toBe($queryStringCentralUrl);
|
});
|
||||||
expect(route('tenant.home', ['tenant' => $tenantKey]))->toBe($tenantRouteUrl);
|
|
||||||
|
test('both the name prefixing and the tenant parameter logic gets skipped when bypass parameter is used', function () {
|
||||||
|
$tenantParameterName = PathTenantResolver::tenantParameterName();
|
||||||
|
|
||||||
|
Route::get('/central/home', fn () => route('home'))->name('home');
|
||||||
|
// Tenant route name prefix is 'tenant.' by default
|
||||||
|
Route::get('/{tenant}/home', fn () => route('tenant.home'))->name('tenant.home')->middleware(['tenant', InitializeTenancyByPath::class]);
|
||||||
|
|
||||||
|
$tenant = Tenant::create();
|
||||||
|
$centralRouteUrl = route('home');
|
||||||
|
$tenantRouteUrl = route('tenant.home', ['tenant' => $tenant->getTenantKey()]);
|
||||||
|
config(['tenancy.bootstrappers' => [UrlGeneratorBootstrapper::class]]);
|
||||||
|
|
||||||
|
TenancyUrlGenerator::$prefixRouteNames = true;
|
||||||
|
TenancyUrlGenerator::$bypassParameter = 'bypassParameter';
|
||||||
|
|
||||||
|
tenancy()->initialize($tenant);
|
||||||
|
|
||||||
|
// The $bypassParameter parameter ('central' by default) can bypass the route name prefixing
|
||||||
|
// When the bypass parameter is true, the generated route URL points to the route named 'home'
|
||||||
|
expect(route('home', ['bypassParameter' => true]))->toBe($centralRouteUrl)
|
||||||
|
// Bypass parameter prevents passing the tenant parameter directly
|
||||||
|
->not()->toContain($tenantParameterName . '=')
|
||||||
|
// Bypass parameter gets removed from the generated URL automatically
|
||||||
|
->not()->toContain('bypassParameter');
|
||||||
|
|
||||||
|
// When the bypass parameter is false, the generated route URL points to the prefixed route ('tenant.home')
|
||||||
|
expect(route('home', ['bypassParameter' => false]))->toBe($tenantRouteUrl)
|
||||||
|
->not()->toContain('bypassParameter');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('url generator bootstrapper can make route helper generate links with the tenant parameter', function() {
|
||||||
|
Route::get('/query_string', fn () => route('query_string'))->name('query_string')->middleware(['universal', InitializeTenancyByRequestData::class]);
|
||||||
|
Route::get('/path', fn () => route('path'))->name('path');
|
||||||
|
Route::get('/{tenant}/path', fn () => route('tenant.path'))->name('tenant.path')->middleware([InitializeTenancyByPath::class]);
|
||||||
|
|
||||||
|
$tenant = Tenant::create();
|
||||||
|
$tenantKey = $tenant->getTenantKey();
|
||||||
|
$queryStringCentralUrl = route('query_string');
|
||||||
|
$queryStringTenantUrl = route('query_string', ['tenant' => $tenantKey]);
|
||||||
|
$pathCentralUrl = route('path');
|
||||||
|
$pathTenantUrl = route('tenant.path', ['tenant' => $tenantKey]);
|
||||||
|
|
||||||
|
// Makes the route helper receive the tenant parameter whenever available
|
||||||
|
// Unless the bypass parameter is true
|
||||||
|
TenancyUrlGenerator::$passTenantParameterToRoutes = true;
|
||||||
|
|
||||||
|
TenancyUrlGenerator::$bypassParameter = 'bypassParameter';
|
||||||
|
|
||||||
|
config(['tenancy.bootstrappers' => [UrlGeneratorBootstrapper::class]]);
|
||||||
|
|
||||||
|
expect(route('path'))->toBe($pathCentralUrl);
|
||||||
|
// Tenant parameter required, but not passed since tenancy wasn't initialized
|
||||||
|
expect(fn () => route('tenant.path'))->toThrow(UrlGenerationException::class);
|
||||||
|
|
||||||
|
tenancy()->initialize($tenant);
|
||||||
|
|
||||||
|
// Tenant parameter is passed automatically
|
||||||
|
expect(route('path'))->not()->toBe($pathCentralUrl); // Parameter added as query string – bypassParameter needed
|
||||||
|
expect(route('path', ['bypassParameter' => true]))->toBe($pathCentralUrl);
|
||||||
|
expect(route('tenant.path'))->toBe($pathTenantUrl);
|
||||||
|
|
||||||
|
expect(route('query_string'))->toBe($queryStringTenantUrl)->toContain('tenant=');
|
||||||
|
expect(route('query_string', ['bypassParameter' => 'true']))->toBe($queryStringCentralUrl)->not()->toContain('tenant=');
|
||||||
|
|
||||||
|
tenancy()->end();
|
||||||
|
|
||||||
|
expect(route('query_string'))->toBe($queryStringCentralUrl);
|
||||||
|
|
||||||
|
// Tenant parameter required, but shouldn't be passed since tenancy isn't initialized
|
||||||
|
expect(fn () => route('tenant.path'))->toThrow(UrlGenerationException::class);
|
||||||
|
|
||||||
// Route-level identification
|
// Route-level identification
|
||||||
pest()->get("http://localhost/central/home")->assertSee($centralRouteUrl);
|
pest()->get("http://localhost/query_string")->assertSee($queryStringCentralUrl);
|
||||||
pest()->get("http://localhost/$tenantKey/home")->assertSee($tenantRouteUrl);
|
pest()->get("http://localhost/query_string?tenant=$tenantKey")->assertSee($queryStringTenantUrl);
|
||||||
pest()->get("http://localhost/query-string?tenant=$tenantKey")->assertSee($queryStringTenantUrl);
|
pest()->get("http://localhost/path")->assertSee($pathCentralUrl);
|
||||||
|
pest()->get("http://localhost/$tenantKey/path")->assertSee($pathTenantUrl);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('fortify route tenancy bootstrapper updates fortify config correctly', function() {
|
test('fortify route tenancy bootstrapper updates fortify config correctly', function() {
|
||||||
config(['tenancy.bootstrappers' => [FortifyRouteTenancyBootstrapper::class]]);
|
config(['tenancy.bootstrappers' => [FortifyRouteTenancyBootstrapper::class]]);
|
||||||
|
|
||||||
Route::get('/', function () {
|
|
||||||
return true;
|
|
||||||
})->name($tenantHomeRouteName = 'tenant.home');
|
|
||||||
|
|
||||||
FortifyRouteTenancyBootstrapper::$fortifyHome = $tenantHomeRouteName;
|
|
||||||
FortifyRouteTenancyBootstrapper::$fortifyRedirectTenantMap = ['logout' => FortifyRouteTenancyBootstrapper::$fortifyHome];
|
|
||||||
$originalFortifyHome = config('fortify.home');
|
$originalFortifyHome = config('fortify.home');
|
||||||
$originalFortifyRedirects = config('fortify.redirects');
|
$originalFortifyRedirects = config('fortify.redirects');
|
||||||
|
|
||||||
tenancy()->initialize(Tenant::create());
|
Route::get('/home', function () {
|
||||||
expect(config('fortify.home'))->toBe($homeUrl = route($tenantHomeRouteName));
|
return true;
|
||||||
expect(config('fortify.redirects'))->toBe(['logout' => $homeUrl]);
|
})->name($homeRouteName = 'home');
|
||||||
|
|
||||||
|
Route::get('/{tenant}/home', function () {
|
||||||
|
return true;
|
||||||
|
})->name($pathIdHomeRouteName = 'tenant.home');
|
||||||
|
|
||||||
|
Route::get('/welcome', function () {
|
||||||
|
return true;
|
||||||
|
})->name($welcomeRouteName = 'welcome');
|
||||||
|
|
||||||
|
Route::get('/{tenant}/welcome', function () {
|
||||||
|
return true;
|
||||||
|
})->name($pathIdWelcomeRouteName = 'path.welcome');
|
||||||
|
|
||||||
|
FortifyRouteTenancyBootstrapper::$fortifyHome = $homeRouteName;
|
||||||
|
|
||||||
|
// Make login redirect to the central welcome route
|
||||||
|
FortifyRouteTenancyBootstrapper::$fortifyRedirectMap['login'] = [
|
||||||
|
'route_name' => $welcomeRouteName,
|
||||||
|
'context' => Context::CENTRAL,
|
||||||
|
];
|
||||||
|
|
||||||
|
tenancy()->initialize($tenant = Tenant::create());
|
||||||
|
// The bootstraper makes fortify.home always receive the tenant parameter
|
||||||
|
expect(config('fortify.home'))->toBe('http://localhost/home?tenant=' . $tenant->getTenantKey());
|
||||||
|
|
||||||
|
// The login redirect route has the central context specified, so it doesn't receive the tenant parameter
|
||||||
|
expect(config('fortify.redirects'))->toEqual(['login' => 'http://localhost/welcome']);
|
||||||
|
|
||||||
tenancy()->end();
|
tenancy()->end();
|
||||||
expect(config('fortify.home'))->toBe($originalFortifyHome);
|
expect(config('fortify.home'))->toBe($originalFortifyHome);
|
||||||
expect(config('fortify.redirects'))->toBe($originalFortifyRedirects);
|
expect(config('fortify.redirects'))->toBe($originalFortifyRedirects);
|
||||||
|
|
||||||
|
// Making a route's context will pass the tenant parameter to the route
|
||||||
|
FortifyRouteTenancyBootstrapper::$fortifyRedirectMap['login']['context'] = Context::TENANT;
|
||||||
|
|
||||||
|
tenancy()->initialize($tenant);
|
||||||
|
|
||||||
|
expect(config('fortify.redirects'))->toEqual(['login' => 'http://localhost/welcome?tenant=' . $tenant->getTenantKey()]);
|
||||||
|
|
||||||
|
// Make the home and login route accept the tenant as a route parameter
|
||||||
|
// To confirm that tenant route parameter gets filled automatically too (path identification works as well as query string)
|
||||||
|
FortifyRouteTenancyBootstrapper::$fortifyHome = $pathIdHomeRouteName;
|
||||||
|
FortifyRouteTenancyBootstrapper::$fortifyRedirectMap['login']['route_name'] = $pathIdWelcomeRouteName;
|
||||||
|
|
||||||
|
tenancy()->end();
|
||||||
|
|
||||||
|
tenancy()->initialize($tenant);
|
||||||
|
|
||||||
|
expect(config('fortify.home'))->toBe("http://localhost/{$tenant->getTenantKey()}/home");
|
||||||
|
expect(config('fortify.redirects'))->toEqual(['login' => "http://localhost/{$tenant->getTenantKey()}/welcome"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('database tenancy bootstrapper throws an exception if DATABASE_URL is set', function (string|null $databaseUrl) {
|
test('database tenancy bootstrapper throws an exception if DATABASE_URL is set', function (string|null $databaseUrl) {
|
||||||
|
|
|
||||||
|
|
@ -116,10 +116,6 @@ test('early identification works with path identification', function (bool $useK
|
||||||
->assertOk()
|
->assertOk()
|
||||||
->assertContent($tenantPost->title . '-' . $tenantComment->comment);
|
->assertContent($tenantPost->title . '-' . $tenantComment->comment);
|
||||||
assertTenancyInitializedInEarlyIdentificationRequest();
|
assertTenancyInitializedInEarlyIdentificationRequest();
|
||||||
|
|
||||||
// Tenant routes that use path identification receive the tenant parameter automatically
|
|
||||||
// (setDefaultTenantForRouteParametersWhenInitializingTenancy() in Stancl\Tenancy\Middleware\InitializeTenancyByPath)
|
|
||||||
expect(route('tenant-route'))->toBe(route('tenant-route', ['tenant' => $tenant->getTenantKey()]));
|
|
||||||
})->with([
|
})->with([
|
||||||
'route-level identification' => false,
|
'route-level identification' => false,
|
||||||
'kernel identification' => true,
|
'kernel identification' => true,
|
||||||
|
|
|
||||||
|
|
@ -129,26 +129,6 @@ test('tenant parameter name can be customized', function () {
|
||||||
->get('/acme/foo/abc/xyz');
|
->get('/acme/foo/abc/xyz');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('tenant parameter is set for all routes as the default parameter once the tenancy initialized', function () {
|
|
||||||
Tenant::create([
|
|
||||||
'id' => 'acme',
|
|
||||||
]);
|
|
||||||
|
|
||||||
expect(tenancy()->initialized)->toBeFalse();
|
|
||||||
|
|
||||||
// make a request that will initialize tenancy
|
|
||||||
pest()->get(route('foo', ['tenant' => 'acme', 'a' => 1, 'b' => 2]));
|
|
||||||
|
|
||||||
expect(tenancy()->initialized)->toBeTrue();
|
|
||||||
expect(tenant('id'))->toBe('acme');
|
|
||||||
|
|
||||||
// assert that the route WITHOUT the tenant parameter matches the route WITH the tenant parameter
|
|
||||||
expect(route('baz', ['a' => 1, 'b' => 2]))->toBe(route('baz', ['tenant' => 'acme', 'a' => 1, 'b' => 2]));
|
|
||||||
|
|
||||||
expect(route('baz', ['a' => 1, 'b' => 2]))->toBe('http://localhost/acme/baz/1/2'); // assert the full route string
|
|
||||||
pest()->get(route('baz', ['a' => 1, 'b' => 2]))->assertOk(); // Assert route don't need tenant parameter
|
|
||||||
});
|
|
||||||
|
|
||||||
test('tenant parameter does not have to be the first in order to initialize tenancy', function() {
|
test('tenant parameter does not have to be the first in order to initialize tenancy', function() {
|
||||||
Tenant::create([
|
Tenant::create([
|
||||||
'id' => $tenantId = 'another-tenant',
|
'id' => $tenantId = 'another-tenant',
|
||||||
|
|
|
||||||
|
|
@ -43,9 +43,6 @@ beforeEach(function () {
|
||||||
|
|
||||||
Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
|
Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
|
||||||
Event::listen(TenancyEnded::class, RevertToCentralContext::class);
|
Event::listen(TenancyEnded::class, RevertToCentralContext::class);
|
||||||
|
|
||||||
// todo1 Is this cleanup needed?
|
|
||||||
UpdateSyncedResource::$shouldQueue = false; // Global state cleanup
|
|
||||||
Event::listen(SyncedResourceSaved::class, UpdateSyncedResource::class);
|
Event::listen(SyncedResourceSaved::class, UpdateSyncedResource::class);
|
||||||
|
|
||||||
// Run migrations on central connection
|
// Run migrations on central connection
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ use Stancl\Tenancy\Listeners\BootstrapTenancy;
|
||||||
use Stancl\Tenancy\Middleware\InitializeTenancyByPath;
|
use Stancl\Tenancy\Middleware\InitializeTenancyByPath;
|
||||||
use Stancl\Tenancy\Middleware\InitializeTenancyByRequestData;
|
use Stancl\Tenancy\Middleware\InitializeTenancyByRequestData;
|
||||||
use Stancl\Tenancy\Bootstrappers\FilesystemTenancyBootstrapper;
|
use Stancl\Tenancy\Bootstrappers\FilesystemTenancyBootstrapper;
|
||||||
use Stancl\Tenancy\Bootstrappers\UrlBindingBootstrapper;
|
use Stancl\Tenancy\Bootstrappers\UrlGeneratorBootstrapper;
|
||||||
use Stancl\Tenancy\Overrides\TenancyUrlGenerator;
|
use Stancl\Tenancy\Overrides\TenancyUrlGenerator;
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
|
|
@ -22,6 +22,7 @@ beforeEach(function () {
|
||||||
]]);
|
]]);
|
||||||
|
|
||||||
TenancyUrlGenerator::$prefixRouteNames = false;
|
TenancyUrlGenerator::$prefixRouteNames = false;
|
||||||
|
TenancyUrlGenerator::$passTenantParameterToRoutes = true;
|
||||||
|
|
||||||
/** @var CloneRoutesAsTenant $cloneAction */
|
/** @var CloneRoutesAsTenant $cloneAction */
|
||||||
$cloneAction = app(CloneRoutesAsTenant::class);
|
$cloneAction = app(CloneRoutesAsTenant::class);
|
||||||
|
|
@ -78,9 +79,11 @@ test('asset helper returns a link to an external url when asset url is not null'
|
||||||
|
|
||||||
test('asset helper works correctly with path identification', function (bool $kernelIdentification) {
|
test('asset helper works correctly with path identification', function (bool $kernelIdentification) {
|
||||||
TenancyUrlGenerator::$prefixRouteNames = true;
|
TenancyUrlGenerator::$prefixRouteNames = true;
|
||||||
|
TenancyUrlGenerator::$passTenantParameterToRoutes = true;
|
||||||
|
|
||||||
config(['tenancy.filesystem.asset_helper_tenancy' => true]);
|
config(['tenancy.filesystem.asset_helper_tenancy' => true]);
|
||||||
config(['tenancy.identification.default_middleware' => InitializeTenancyByPath::class]);
|
config(['tenancy.identification.default_middleware' => InitializeTenancyByPath::class]);
|
||||||
config(['tenancy.bootstrappers' => array_merge([UrlBindingBootstrapper::class], config('tenancy.bootstrappers'))]);
|
config(['tenancy.bootstrappers' => array_merge([UrlGeneratorBootstrapper::class], config('tenancy.bootstrappers'))]);
|
||||||
|
|
||||||
$tenantAssetRoute = Route::prefix('{tenant}')->get('/tenant_helper', function () {
|
$tenantAssetRoute = Route::prefix('{tenant}')->get('/tenant_helper', function () {
|
||||||
return tenant_asset('foo');
|
return tenant_asset('foo');
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,10 @@ use Stancl\Tenancy\Bootstrappers\BroadcastChannelPrefixBootstrapper;
|
||||||
use Stancl\Tenancy\Facades\GlobalCache;
|
use Stancl\Tenancy\Facades\GlobalCache;
|
||||||
use Stancl\Tenancy\TenancyServiceProvider;
|
use Stancl\Tenancy\TenancyServiceProvider;
|
||||||
use Stancl\Tenancy\Facades\Tenancy as TenancyFacade;
|
use Stancl\Tenancy\Facades\Tenancy as TenancyFacade;
|
||||||
use Stancl\Tenancy\Bootstrappers\UrlTenancyBootstrapper;
|
use Stancl\Tenancy\Bootstrappers\RootUrlBootstrapper;
|
||||||
use Stancl\Tenancy\Bootstrappers\MailTenancyBootstrapper;
|
use Stancl\Tenancy\Bootstrappers\MailTenancyBootstrapper;
|
||||||
use Stancl\Tenancy\Bootstrappers\RedisTenancyBootstrapper;
|
use Stancl\Tenancy\Bootstrappers\RedisTenancyBootstrapper;
|
||||||
|
use Stancl\Tenancy\Bootstrappers\UrlGeneratorBootstrapper;
|
||||||
use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper;
|
use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper;
|
||||||
use Stancl\Tenancy\Bootstrappers\BroadcastingConfigBootstrapper;
|
use Stancl\Tenancy\Bootstrappers\BroadcastingConfigBootstrapper;
|
||||||
use Stancl\Tenancy\Bootstrappers\FilesystemTenancyBootstrapper;
|
use Stancl\Tenancy\Bootstrappers\FilesystemTenancyBootstrapper;
|
||||||
|
|
@ -113,7 +114,7 @@ abstract class TestCase extends \Orchestra\Testbench\TestCase
|
||||||
'tenancy.bootstrappers' => [
|
'tenancy.bootstrappers' => [
|
||||||
DatabaseTenancyBootstrapper::class,
|
DatabaseTenancyBootstrapper::class,
|
||||||
FilesystemTenancyBootstrapper::class,
|
FilesystemTenancyBootstrapper::class,
|
||||||
UrlTenancyBootstrapper::class,
|
RootUrlBootstrapper::class,
|
||||||
],
|
],
|
||||||
'queue.connections.central' => [
|
'queue.connections.central' => [
|
||||||
'driver' => 'sync',
|
'driver' => 'sync',
|
||||||
|
|
@ -128,7 +129,8 @@ abstract class TestCase extends \Orchestra\Testbench\TestCase
|
||||||
$app->singleton(BroadcastingConfigBootstrapper::class);
|
$app->singleton(BroadcastingConfigBootstrapper::class);
|
||||||
$app->singleton(BroadcastChannelPrefixBootstrapper::class);
|
$app->singleton(BroadcastChannelPrefixBootstrapper::class);
|
||||||
$app->singleton(MailTenancyBootstrapper::class);
|
$app->singleton(MailTenancyBootstrapper::class);
|
||||||
$app->singleton(UrlTenancyBootstrapper::class);
|
$app->singleton(RootUrlBootstrapper::class);
|
||||||
|
$app->singleton(UrlGeneratorBootstrapper::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getPackageProviders($app)
|
protected function getPackageProviders($app)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue