1
0
Fork 0
mirror of https://github.com/archtechx/tenancy.git synced 2025-12-12 08:04:03 +00:00

[4.x] Cleanup (#1317)

* cleanup, resolve todos, add immediate todos

* Improve path_identification_middleware docblock

* rename leave() method in tests

* wip fix hardcoded values making assumptions about the parameters used in routing

* defaultParameterNames

* fix CreatesDatabaseUsers return values

* $tenant -> tenant()

* resolve more todos

* make comment block a complete block

* Correct useTenantRoutesInFortify(), delete unused import

* test fixes

* remove todos

* remove JobPipeline todo

* simplify comment example

* remove todo

* fix VERSION_PREFIX in queue.yml

---------

Co-authored-by: lukinovec <lukinovec@gmail.com>
This commit is contained in:
Samuel Štancl 2025-02-20 20:49:09 +01:00 committed by GitHub
parent eac88dcc2a
commit 657e165cc8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 148 additions and 182 deletions

View file

@ -11,7 +11,7 @@ jobs:
- name: Prepare composer version constraint prefix - name: Prepare composer version constraint prefix
run: | run: |
BRANCH=${GITHUB_REF#refs/heads/} BRANCH=${GITHUB_REF#refs/heads/}
if [[ $BRANCH =~ ^[0-9] ]]; then if [[ $BRANCH =~ ^[0-9]\.x$ ]]; then
echo "VERSION_PREFIX=${BRANCH}-dev" >> $GITHUB_ENV echo "VERSION_PREFIX=${BRANCH}-dev" >> $GITHUB_ENV
else else
echo "VERSION_PREFIX=dev-${BRANCH}" >> $GITHUB_ENV echo "VERSION_PREFIX=dev-${BRANCH}" >> $GITHUB_ENV

View file

@ -145,24 +145,19 @@ class TenancyServiceProvider extends ServiceProvider
*/ */
protected function overrideUrlInTenantContext(): void protected function overrideUrlInTenantContext(): void
{ {
/** // \Stancl\Tenancy\Bootstrappers\RootUrlBootstrapper::$rootUrlOverride = function (Tenant $tenant, string $originalRootUrl) {
* Import your tenant model! // $tenantDomain = $tenant instanceof \Stancl\Tenancy\Contracts\SingleDomainTenant
* // ? $tenant->domain
* \Stancl\Tenancy\Bootstrappers\RootUrlBootstrapper::$rootUrlOverride = function (Tenant $tenant, string $originalRootUrl) { // : $tenant->domains->first()->domain;
* $tenantDomain = $tenant instanceof \Stancl\Tenancy\Contracts\SingleDomainTenant // $scheme = str($originalRootUrl)->before('://');
* ? $tenant->domain //
* : $tenant->domains->first()->domain; // // If you're using domain identification:
* // return $scheme . '://' . $tenantDomain . '/';
* $scheme = str($originalRootUrl)->before('://'); //
* // // If you're using subdomain identification:
* // If you're using domain identification: // $originalDomain = str($originalRootUrl)->after($scheme . '://');
* return $scheme . '://' . $tenantDomain . '/'; // return $scheme . '://' . $tenantDomain . '.' . $originalDomain . '/';
* // };
* // If you're using subdomain identification:
* $originalDomain = str($originalRootUrl)->after($scheme . '://');
* return $scheme . '://' . $tenantDomain . '.' . $originalDomain . '/';
* };
*/
} }
public function register() public function register()
@ -178,32 +173,17 @@ class TenancyServiceProvider extends ServiceProvider
$this->makeTenancyMiddlewareHighestPriority(); $this->makeTenancyMiddlewareHighestPriority();
$this->overrideUrlInTenantContext(); $this->overrideUrlInTenantContext();
/** // // Include soft deleted resources in synced resource queries.
* Include soft deleted resources in synced resource queries. // ResourceSyncing\Listeners\UpdateOrCreateSyncedResource::$scopeGetModelQuery = function (Builder $query) {
* // if ($query->hasMacro('withTrashed')) {
* ResourceSyncing\Listeners\UpdateOrCreateSyncedResource::$scopeGetModelQuery = function (Builder $query) { // $query->withTrashed();
* if ($query->hasMacro('withTrashed')) { // }
* $query->withTrashed(); // };
* }
* };
*/
/** // // To make Livewire v3 work with Tenancy, make the update route universal.
* To make Livewire v3 work with Tenancy, make the update route universal. // Livewire::setUpdateRoute(function ($handle) {
* // return RouteFacade::post('/livewire/update', $handle)->middleware(['web', 'universal', \Stancl\Tenancy::defaultMiddleware()]);
* Livewire::setUpdateRoute(function ($handle) { // });
* return RouteFacade::post('/livewire/update', $handle)->middleware(['web', 'universal']);
* });
*/
// if (InitializeTenancyByRequestData::inGlobalStack()) {
// FortifyRouteBootstrapper::$fortifyHome = 'dashboard';
// TenancyUrlGenerator::$prefixRouteNames = false;
// }
if (tenancy()->globalStackHasMiddleware(config('tenancy.identification.path_identification_middleware'))) {
TenancyUrlGenerator::$prefixRouteNames = true;
}
} }
protected function bootEvents() protected function bootEvents()
@ -228,10 +208,7 @@ class TenancyServiceProvider extends ServiceProvider
->group(base_path('routes/tenant.php')); ->group(base_path('routes/tenant.php'));
} }
// Delete this condition when using route-level path identification // $this->cloneRoutes();
if (tenancy()->globalStackHasMiddleware(config('tenancy.identification.path_identification_middleware'))) {
$this->cloneRoutes();
}
}); });
} }
@ -245,16 +222,13 @@ class TenancyServiceProvider extends ServiceProvider
/** @var CloneRoutesAsTenant $cloneRoutes */ /** @var CloneRoutesAsTenant $cloneRoutes */
$cloneRoutes = $this->app->make(CloneRoutesAsTenant::class); $cloneRoutes = $this->app->make(CloneRoutesAsTenant::class);
/** // // You can provide a closure for cloning a specific route, e.g.:
* You can provide a closure for cloning a specific route, e.g.: // $cloneRoutes->cloneUsing('welcome', function () {
* $cloneRoutes->cloneUsing('welcome', function () { // RouteFacade::get('/tenant-welcome', fn () => 'Current tenant: ' . tenant()->getTenantKey())
* RouteFacade::get('/tenant-welcome', fn () => 'Current tenant: ' . tenant()->getTenantKey()) // ->middleware(['universal', InitializeTenancyByPath::class])
* ->middleware(['universal', InitializeTenancyByPath::class]) // ->name('tenant.welcome');
* ->name('tenant.welcome'); // });
* }); // // To see the default behavior of cloning the universal routes, check out the cloneRoute() method in CloneRoutesAsTenant.
*
* To see the default behavior of cloning the universal routes, check out the cloneRoute() method in CloneRoutesAsTenant.
*/
$cloneRoutes->handle(); $cloneRoutes->handle();
} }

View file

@ -91,7 +91,7 @@ return [
/** /**
* Identification middleware tenancy recognizes as path identification middleware. * Identification middleware tenancy recognizes as path identification middleware.
* *
* This is used during determining whether whether a path identification is used * This is used for determining if a path identification middleware is used
* during operations specific to path identification, e.g. forgetting the tenant parameter in ForgetTenantParameter. * during operations specific to path identification, e.g. forgetting the tenant parameter in ForgetTenantParameter.
* *
* If you're using a custom path identification middleware, add it here. * If you're using a custom path identification middleware, add it here.
@ -118,6 +118,7 @@ return [
Resolvers\PathTenantResolver::class => [ Resolvers\PathTenantResolver::class => [
'tenant_parameter_name' => 'tenant', 'tenant_parameter_name' => 'tenant',
'tenant_model_column' => null, // null = tenant key 'tenant_model_column' => null, // null = tenant key
'tenant_route_name_prefix' => null, // null = 'tenant.'
'allowed_extra_model_columns' => [], // used with binding route fields 'allowed_extra_model_columns' => [], // used with binding route fields
'cache' => false, 'cache' => false,
@ -130,8 +131,6 @@ return [
'cache_store' => null, // null = default 'cache_store' => null, // null = default
], ],
], ],
// todo@docs update integration guides to use Stancl\Tenancy::defaultMiddleware()
], ],
/** /**
@ -215,7 +214,14 @@ return [
// 'pgsql' => Stancl\Tenancy\Database\TenantDatabaseManagers\PermissionControlledPostgreSQLSchemaManager::class, // Also permission controlled // 'pgsql' => Stancl\Tenancy\Database\TenantDatabaseManagers\PermissionControlledPostgreSQLSchemaManager::class, // Also permission controlled
], ],
// todo@docblock /*
* Drop tenant databases when `php artisan migrate:fresh` is used.
* You may want to use this locally since deleting tenants only
* deletes their databases when they're deleted individually, not
* when the records are mass deleted from the database.
*
* Note: This overrides the default MigrateFresh command.
*/
'drop_tenant_databases_on_migrate_fresh' => false, 'drop_tenant_databases_on_migrate_fresh' => false,
], ],
@ -320,7 +326,6 @@ return [
*/ */
'url_override' => [ 'url_override' => [
// Note that the local disk you add must exist in the tenancy.filesystem.root_override config // Note that the local disk you add must exist in the tenancy.filesystem.root_override config
// todo@v4 Rename url_override to something that describes the config key better
'public' => 'public-%tenant%', 'public' => 'public-%tenant%',
], ],
@ -356,7 +361,7 @@ return [
* leave asset() helper tenancy disabled and explicitly use tenant_asset() calls in places * leave asset() helper tenancy disabled and explicitly use tenant_asset() calls in places
* where you want to use tenant-specific assets (product images, avatars, etc). * where you want to use tenant-specific assets (product images, avatars, etc).
*/ */
'asset_helper_tenancy' => false, // todo@rename asset_helper_override? 'asset_helper_override' => false,
], ],
/** /**

View file

@ -92,7 +92,7 @@ class FilesystemTenancyBootstrapper implements TenancyBootstrapper
protected function assetHelper(string|false $suffix): void protected function assetHelper(string|false $suffix): void
{ {
if (! $this->app['config']['tenancy.filesystem.asset_helper_tenancy']) { if (! $this->app['config']['tenancy.filesystem.asset_helper_override']) {
return; return;
} }

View file

@ -7,54 +7,52 @@ 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; use Stancl\Tenancy\Resolvers\PathTenantResolver;
/** /**
* Allows customizing Fortify action redirects * Allows customizing Fortify action redirects so that they can also redirect
* so that they can also redirect to tenant routes instead of just the central routes. * to tenant routes instead of just the central routes.
* *
* Works with path and query string identification. * This should be used with path/query string identification OR when using Fortify
* universally, including with domains.
*
* When using domain identification, there's no need to pass the tenant parameter,
* you only want to customize the routes being used, so you can set $passTenantParameter
* to false.
*/ */
class FortifyRouteBootstrapper implements TenancyBootstrapper class FortifyRouteBootstrapper implements TenancyBootstrapper
{ {
/** /**
* Make Fortify actions redirect to custom routes. * Fortify redirects that should be used in tenant context.
* *
* For each route redirect, specify the intended route context (central or tenant). * Syntax: ['redirect_name' => 'tenant_route_name']
* 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:
*
* FortifyRouteBootstrapper::$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 = []; public static array $fortifyRedirectMap = [];
/**
* Should the tenant parameter be passed to fortify routes in the tenant context.
*
* This should be enabled with path/query string identification and disabled with domain identification.
*
* You may also disable this when using path/query string identification if passing the tenant parameter
* is handled in another way (TenancyUrlGenerator::$passTenantParameter for both,
* UrlGeneratorBootstrapper:$addTenantParameterToDefaults for path identification).
*/
public static bool $passTenantParameter = true;
/** /**
* Tenant route that serves as Fortify's home (e.g. a tenant dashboard route). * Tenant route that serves as Fortify's home (e.g. a tenant dashboard route).
* This route will always receive the tenant parameter. * This route will always receive the tenant parameter.
*/ */
public static string $fortifyHome = 'tenant.dashboard'; public static string|null $fortifyHome = 'tenant.dashboard';
/**
* Use default parameter names ('tenant' name and tenant key value) instead of the parameter name
* and column name configured in the path resolver config.
*
* You want to enable this when using query string identification while having customized that config.
*/
public static bool $defaultParameterNames = false;
protected array $originalFortifyConfig = []; protected array $originalFortifyConfig = [];
@ -76,27 +74,22 @@ class FortifyRouteBootstrapper implements TenancyBootstrapper
protected function useTenantRoutesInFortify(Tenant $tenant): void protected function useTenantRoutesInFortify(Tenant $tenant): void
{ {
$tenantKey = $tenant->getTenantKey(); $tenantParameterName = static::$defaultParameterNames ? 'tenant' : PathTenantResolver::tenantParameterName();
$tenantParameterName = PathTenantResolver::tenantParameterName(); $tenantParameterValue = static::$defaultParameterNames ? $tenant->getTenantKey() : PathTenantResolver::tenantParameterValue($tenant);
$generateLink = function (array $redirect) use ($tenantKey, $tenantParameterName) { $generateLink = function (string $redirect) use ($tenantParameterValue, $tenantParameterName) {
// Specifying the context is only required with query string identification return route($redirect, static::$passTenantParameter ? [$tenantParameterName => $tenantParameterValue] : []);
// 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 // Get redirect URLs for the configured redirect routes
$redirects = array_merge( $redirects = array_merge(
$this->originalFortifyConfig['redirects'] ?? [], // Fortify config redirects $this->originalFortifyConfig['redirects'] ?? [], // Fortify config redirects
array_map(fn (array $redirect) => $generateLink($redirect), static::$fortifyRedirectMap), // Mapped redirects array_map(fn (string $redirect) => $generateLink($redirect), static::$fortifyRedirectMap), // Mapped redirects
); );
if (static::$fortifyHome) { if (static::$fortifyHome) {
// Generate the home route URL with the tenant parameter and make it the Fortify home route // 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.home', route(static::$fortifyHome, static::$passTenantParameter ? [$tenantParameterName => $tenantParameterValue] : []));
} }
$this->config->set('fortify.redirects', $redirects); $this->config->set('fortify.redirects', $redirects);

View file

@ -36,13 +36,8 @@ class RootUrlBootstrapper implements TenancyBootstrapper
protected string|null $originalRootUrl = null; protected string|null $originalRootUrl = null;
/** /**
* You may want to selectively enable or disable this bootstrapper in specific tests. * Overriding the root url may cause issues in *some* tests, so you can disable
* For instance, when using `Livewire::test()` this bootstrapper can cause problems, * the behavior by setting this property to false.
* due to an internal Livewire route, so you may want to disable it, while in tests
* that are generating URLs in things like mails, the bootstrapper should be used
* just like in any queued job.
*
* todo@revisit
*/ */
public static bool $rootUrlOverrideInTests = true; public static bool $rootUrlOverrideInTests = true;

View file

@ -69,7 +69,10 @@ class UrlGeneratorBootstrapper implements TenancyBootstrapper
if (static::$addTenantParameterToDefaults) { if (static::$addTenantParameterToDefaults) {
$defaultParameters = array_merge( $defaultParameters = array_merge(
$defaultParameters, $defaultParameters,
[PathTenantResolver::tenantParameterName() => $tenant->getTenantKey()] [
PathTenantResolver::tenantParameterName() => PathTenantResolver::tenantParameterValue($tenant), // path identification
'tenant' => $tenant->getTenantKey(), // query string identification
],
); );
} }

View file

@ -10,16 +10,11 @@ trait CreatesDatabaseUsers
{ {
public function createDatabase(TenantWithDatabase $tenant): bool public function createDatabase(TenantWithDatabase $tenant): bool
{ {
parent::createDatabase($tenant); return parent::createDatabase($tenant) && $this->createUser($tenant->database());
return $this->createUser($tenant->database());
} }
public function deleteDatabase(TenantWithDatabase $tenant): bool public function deleteDatabase(TenantWithDatabase $tenant): bool
{ {
// Some DB engines require the user to be deleted before the database (e.g. Postgres) return $this->deleteUser($tenant->database()) && parent::deleteDatabase($tenant);
$this->deleteUser($tenant->database());
return parent::deleteDatabase($tenant);
} }
} }

View file

@ -23,6 +23,8 @@ use Stancl\Tenancy\Events\PullingPendingTenant;
*/ */
trait HasPending trait HasPending
{ {
public static string $pendingSinceCast = 'timestamp';
/** Boot the trait. */ /** Boot the trait. */
public static function bootHasPending(): void public static function bootHasPending(): void
{ {
@ -32,7 +34,7 @@ trait HasPending
/** Initialize the trait. */ /** Initialize the trait. */
public function initializeHasPending(): void public function initializeHasPending(): void
{ {
$this->casts['pending_since'] = 'timestamp'; $this->casts['pending_since'] = static::$pendingSinceCast;
} }
/** Determine if the model instance is in a pending state. */ /** Determine if the model instance is in a pending state. */

View file

@ -11,8 +11,6 @@ class MicrosoftSQLDatabaseManager extends TenantDatabaseManager
public function createDatabase(TenantWithDatabase $tenant): bool public function createDatabase(TenantWithDatabase $tenant): bool
{ {
$database = $tenant->database()->getName(); $database = $tenant->database()->getName();
$charset = $this->connection()->getConfig('charset');
$collation = $this->connection()->getConfig('collation'); // todo check why these are not used
return $this->connection()->statement("CREATE DATABASE [{$database}]"); return $this->connection()->statement("CREATE DATABASE [{$database}]");
} }

View file

@ -32,6 +32,8 @@ class PermissionControlledPostgreSQLSchemaManager extends PostgreSQLSchemaManage
// Grant permissions to any existing tables. This is used with RLS // Grant permissions to any existing tables. This is used with RLS
// todo@samuel refactor this along with the todo in TenantDatabaseManager // todo@samuel refactor this along with the todo in TenantDatabaseManager
// and move the grantPermissions() call inside the condition in `ManagesPostgresUsers::createUser()` // and move the grantPermissions() call inside the condition in `ManagesPostgresUsers::createUser()`
// but maybe moving it inside $createUser is wrong because some central user may migrate new tables
// while the RLS user should STILL get access to those tables
foreach ($tables as $table) { foreach ($tables as $table) {
$tableName = $table->table_name; $tableName = $table->table_name;

View file

@ -7,15 +7,11 @@ namespace Stancl\Tenancy\Events\Contracts;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
use Stancl\Tenancy\Contracts\Tenant; use Stancl\Tenancy\Contracts\Tenant;
abstract class TenantEvent // todo we could add a feature to JobPipeline that automatically gets data for the send() from here abstract class TenantEvent
{ {
use SerializesModels; use SerializesModels;
/** @var Tenant */ public function __construct(
public $tenant; public Tenant $tenant,
) {}
public function __construct(Tenant $tenant)
{
$this->tenant = $tenant;
}
} }

View file

@ -2,8 +2,6 @@
declare(strict_types=1); declare(strict_types=1);
// todo perhaps create Identification namespace
namespace Stancl\Tenancy\Exceptions; namespace Stancl\Tenancy\Exceptions;
use Stancl\Tenancy\Contracts\TenantCouldNotBeIdentifiedException; use Stancl\Tenancy\Contracts\TenantCouldNotBeIdentifiedException;

View file

@ -62,7 +62,7 @@ class UserImpersonation implements Feature
/** /**
* Logout from the current domain and forget impersonation session. * Logout from the current domain and forget impersonation session.
*/ */
public static function leave(): void // todo@name possibly rename public static function stopImpersonating(): void
{ {
auth()->logout(); auth()->logout();

View file

@ -8,6 +8,8 @@ use Illuminate\Routing\Events\RouteMatched;
use Stancl\Tenancy\Enums\RouteMode; use Stancl\Tenancy\Enums\RouteMode;
use Stancl\Tenancy\Resolvers\PathTenantResolver; use Stancl\Tenancy\Resolvers\PathTenantResolver;
// todo@earlyIdReview
/** /**
* 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.
* *

View file

@ -68,7 +68,7 @@ class PreventAccessFromUnwantedDomains
return in_array($request->getHost(), config('tenancy.identification.central_domains'), true); return in_array($request->getHost(), config('tenancy.identification.central_domains'), true);
} }
// todo@samuel // todo@samuel technically not an identification middleware but probably ok to keep this here
public function requestHasTenant(Request $request): bool public function requestHasTenant(Request $request): bool
{ {
return false; return false;

View file

@ -6,8 +6,6 @@ namespace Stancl\Tenancy\Overrides;
use Illuminate\Cache\CacheManager as BaseCacheManager; use Illuminate\Cache\CacheManager as BaseCacheManager;
// todo@move move to Cache namespace?
class CacheManager extends BaseCacheManager class CacheManager extends BaseCacheManager
{ {
/** /**

View file

@ -85,6 +85,14 @@ class TenancyUrlGenerator extends UrlGenerator
*/ */
public static array $overrides = []; public static array $overrides = [];
/**
* Use default parameter names ('tenant' name and tenant key value) instead of the parameter name
* and column name configured in the path resolver config.
*
* You want to enable this when using query string identification while having customized that config.
*/
public static bool $defaultParameterNames = false;
/** /**
* Override the route() method so that the route name gets prefixed * Override the route() method so that the route name gets prefixed
* and the tenant parameter gets added when in tenant context. * and the tenant parameter gets added when in tenant context.
@ -166,7 +174,15 @@ class TenancyUrlGenerator extends UrlGenerator
*/ */
protected function addTenantParameter(array $parameters): array protected function addTenantParameter(array $parameters): array
{ {
return tenant() && static::$passTenantParameterToRoutes ? array_merge($parameters, [PathTenantResolver::tenantParameterName() => tenant()->getTenantKey()]) : $parameters; if (tenant() && static::$passTenantParameterToRoutes) {
if (static::$defaultParameterNames) {
return array_merge($parameters, ['tenant' => tenant()->getTenantKey()]);
} else {
return array_merge($parameters, [PathTenantResolver::tenantParameterName() => PathTenantResolver::tenantParameterValue(tenant())]);
}
} else {
return $parameters;
}
} }
protected function routeNameOverride(string $name): string|null protected function routeNameOverride(string $name): string|null

View file

@ -73,7 +73,7 @@ class PathTenantResolver extends Contracts\CachedTenantResolver
public static function tenantRouteNamePrefix(): string public static function tenantRouteNamePrefix(): string
{ {
return config('tenancy.identification.resolvers.' . static::class . '.tenant_route_name_prefix') ?? static::tenantParameterName() . '.'; return config('tenancy.identification.resolvers.' . static::class . '.tenant_route_name_prefix') ?? 'tenant.';
} }
public static function tenantModelColumn(): string public static function tenantModelColumn(): string
@ -81,6 +81,11 @@ class PathTenantResolver extends Contracts\CachedTenantResolver
return config('tenancy.identification.resolvers.' . static::class . '.tenant_model_column') ?? tenancy()->model()->getTenantKeyName(); return config('tenancy.identification.resolvers.' . static::class . '.tenant_model_column') ?? tenancy()->model()->getTenantKeyName();
} }
public static function tenantParameterValue(Tenant $tenant): string
{
return $tenant->getAttribute(static::tenantModelColumn());
}
/** @return string[] */ /** @return string[] */
public static function allowedExtraModelColumns(): array public static function allowedExtraModelColumns(): array
{ {

View file

@ -9,8 +9,6 @@ use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Stancl\Tenancy\Database\Contracts\TenantWithDatabase; use Stancl\Tenancy\Database\Contracts\TenantWithDatabase;
// todo@move move all resource syncing-related things to a separate namespace?
/** /**
* @property-read TenantWithDatabase[]|Collection<int, TenantWithDatabase&Model> $tenants * @property-read TenantWithDatabase[]|Collection<int, TenantWithDatabase&Model> $tenants
*/ */

View file

@ -13,6 +13,14 @@ use Stancl\Tenancy\Bootstrappers\Integrations\FortifyRouteBootstrapper;
beforeEach(function () { 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);
FortifyRouteBootstrapper::$passTenantParameter = true;
});
afterEach(function () {
FortifyRouteBootstrapper::$passTenantParameter = true;
FortifyRouteBootstrapper::$fortifyRedirectMap = [];
FortifyRouteBootstrapper::$fortifyHome = 'tenant.dashboard';
FortifyRouteBootstrapper::$defaultParameterNames = false;
}); });
test('fortify route tenancy bootstrapper updates fortify config correctly', function() { test('fortify route tenancy bootstrapper updates fortify config correctly', function() {
@ -25,53 +33,31 @@ test('fortify route tenancy bootstrapper updates fortify config correctly', func
return true; return true;
})->name($homeRouteName = 'home'); })->name($homeRouteName = 'home');
Route::get('/{tenant}/home', function () {
return true;
})->name($pathIdHomeRouteName = 'tenant.home');
Route::get('/welcome', function () { Route::get('/welcome', function () {
return true; return true;
})->name($welcomeRouteName = 'welcome'); })->name($welcomeRouteName = 'welcome');
Route::get('/{tenant}/welcome', function () {
return true;
})->name($pathIdWelcomeRouteName = 'path.welcome');
FortifyRouteBootstrapper::$fortifyHome = $homeRouteName; FortifyRouteBootstrapper::$fortifyHome = $homeRouteName;
FortifyRouteBootstrapper::$fortifyRedirectMap['login'] = $welcomeRouteName;
// Make login redirect to the central welcome route expect(config('fortify.home'))->toBe($originalFortifyHome);
FortifyRouteBootstrapper::$fortifyRedirectMap['login'] = [ expect(config('fortify.redirects'))->toBe($originalFortifyRedirects);
'route_name' => $welcomeRouteName,
'context' => Context::CENTRAL,
];
FortifyRouteBootstrapper::$passTenantParameter = true;
tenancy()->initialize($tenant = Tenant::create()); 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()); expect(config('fortify.home'))->toBe('http://localhost/home?tenant=' . $tenant->getTenantKey());
expect(config('fortify.redirects'))->toEqual(['login' => 'http://localhost/welcome?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 FortifyRouteBootstrapper::$passTenantParameter = false;
FortifyRouteBootstrapper::$fortifyRedirectMap['login']['context'] = Context::TENANT;
tenancy()->initialize($tenant); tenancy()->initialize($tenant);
expect(config('fortify.home'))->toBe('http://localhost/home');
expect(config('fortify.redirects'))->toEqual(['login' => 'http://localhost/welcome?tenant=' . $tenant->getTenantKey()]); expect(config('fortify.redirects'))->toEqual(['login' => 'http://localhost/welcome']);
// 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)
FortifyRouteBootstrapper::$fortifyHome = $pathIdHomeRouteName;
FortifyRouteBootstrapper::$fortifyRedirectMap['login']['route_name'] = $pathIdWelcomeRouteName;
tenancy()->end(); tenancy()->end();
expect(config('fortify.home'))->toBe($originalFortifyHome);
tenancy()->initialize($tenant); expect(config('fortify.redirects'))->toBe($originalFortifyRedirects);
expect(config('fortify.home'))->toBe("http://localhost/{$tenant->getTenantKey()}/home");
expect(config('fortify.redirects'))->toEqual(['login' => "http://localhost/{$tenant->getTenantKey()}/welcome"]);
}); });

View file

@ -65,7 +65,7 @@ test('asset can be accessed using the url returned by the tenant asset helper',
test('asset helper returns a link to tenant asset controller when asset url is null', function () { test('asset helper returns a link to tenant asset controller when asset url is null', function () {
config(['app.asset_url' => null]); config(['app.asset_url' => null]);
config(['tenancy.filesystem.asset_helper_tenancy' => true]); config(['tenancy.filesystem.asset_helper_override' => true]);
$tenant = Tenant::create(); $tenant = Tenant::create();
tenancy()->initialize($tenant); tenancy()->initialize($tenant);
@ -78,7 +78,7 @@ test('asset helper returns a link to tenant asset controller when asset url is n
test('asset helper returns a link to an external url when asset url is not null', function () { test('asset helper returns a link to an external url when asset url is not null', function () {
config(['app.asset_url' => 'https://an-s3-bucket']); config(['app.asset_url' => 'https://an-s3-bucket']);
config(['tenancy.filesystem.asset_helper_tenancy' => true]); config(['tenancy.filesystem.asset_helper_override' => true]);
$tenant = Tenant::create(); $tenant = Tenant::create();
tenancy()->initialize($tenant); tenancy()->initialize($tenant);
@ -93,7 +93,7 @@ test('asset helper works correctly with path identification', function (bool $ke
TenancyUrlGenerator::$prefixRouteNames = true; TenancyUrlGenerator::$prefixRouteNames = true;
TenancyUrlGenerator::$passTenantParameterToRoutes = true; TenancyUrlGenerator::$passTenantParameterToRoutes = true;
config(['tenancy.filesystem.asset_helper_tenancy' => true]); config(['tenancy.filesystem.asset_helper_override' => true]);
config(['tenancy.identification.default_middleware' => InitializeTenancyByPath::class]); config(['tenancy.identification.default_middleware' => InitializeTenancyByPath::class]);
config(['tenancy.bootstrappers' => array_merge([UrlGeneratorBootstrapper::class], config('tenancy.bootstrappers'))]); config(['tenancy.bootstrappers' => array_merge([UrlGeneratorBootstrapper::class], config('tenancy.bootstrappers'))]);
@ -165,7 +165,7 @@ test('asset helper tenancy can be disabled', function () {
config([ config([
'app.asset_url' => null, 'app.asset_url' => null,
'tenancy.filesystem.asset_helper_tenancy' => false, 'tenancy.filesystem.asset_helper_override' => false,
]); ]);
$tenant = Tenant::create(); $tenant = Tenant::create();

View file

@ -88,7 +88,7 @@ test('tenant user can be impersonated on a tenant domain', function () {
expect(session('tenancy_impersonating'))->toBeTrue(); expect(session('tenancy_impersonating'))->toBeTrue();
// Leave impersonation // Leave impersonation
UserImpersonation::leave(); UserImpersonation::stopImpersonating();
expect(UserImpersonation::isImpersonating())->toBeFalse(); expect(UserImpersonation::isImpersonating())->toBeFalse();
expect(session('tenancy_impersonating'))->toBeNull(); expect(session('tenancy_impersonating'))->toBeNull();
@ -134,7 +134,7 @@ test('tenant user can be impersonated on a tenant path', function () {
expect(session('tenancy_impersonating'))->toBeTrue(); expect(session('tenancy_impersonating'))->toBeTrue();
// Leave impersonation // Leave impersonation
UserImpersonation::leave(); UserImpersonation::stopImpersonating();
expect(UserImpersonation::isImpersonating())->toBeFalse(); expect(UserImpersonation::isImpersonating())->toBeFalse();
expect(session('tenancy_impersonating'))->toBeNull(); expect(session('tenancy_impersonating'))->toBeNull();