diff --git a/Boot.php b/Boot.php index 2b5e4d6..b8023e2 100644 --- a/Boot.php +++ b/Boot.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Core\Mod\Tenant; +namespace Core\Tenant; use Core\Events\AdminPanelBooting; use Core\Events\ApiRoutesRegistering; @@ -40,48 +40,48 @@ class Boot extends ServiceProvider public function register(): void { $this->app->singleton( - \Core\Mod\Tenant\Contracts\TwoFactorAuthenticationProvider::class, - \Core\Mod\Tenant\Services\TotpService::class + \Core\Tenant\Contracts\TwoFactorAuthenticationProvider::class, + \Core\Tenant\Services\TotpService::class ); $this->app->singleton( - \Core\Mod\Tenant\Services\EntitlementService::class, - \Core\Mod\Tenant\Services\EntitlementService::class + \Core\Tenant\Services\EntitlementService::class, + \Core\Tenant\Services\EntitlementService::class ); $this->app->singleton( - \Core\Mod\Tenant\Services\WorkspaceManager::class, - \Core\Mod\Tenant\Services\WorkspaceManager::class + \Core\Tenant\Services\WorkspaceManager::class, + \Core\Tenant\Services\WorkspaceManager::class ); $this->app->singleton( - \Core\Mod\Tenant\Services\UserStatsService::class, - \Core\Mod\Tenant\Services\UserStatsService::class + \Core\Tenant\Services\UserStatsService::class, + \Core\Tenant\Services\UserStatsService::class ); $this->app->singleton( - \Core\Mod\Tenant\Services\WorkspaceService::class, - \Core\Mod\Tenant\Services\WorkspaceService::class + \Core\Tenant\Services\WorkspaceService::class, + \Core\Tenant\Services\WorkspaceService::class ); $this->app->singleton( - \Core\Mod\Tenant\Services\WorkspaceCacheManager::class, - \Core\Mod\Tenant\Services\WorkspaceCacheManager::class + \Core\Tenant\Services\WorkspaceCacheManager::class, + \Core\Tenant\Services\WorkspaceCacheManager::class ); $this->app->singleton( - \Core\Mod\Tenant\Services\UsageAlertService::class, - \Core\Mod\Tenant\Services\UsageAlertService::class + \Core\Tenant\Services\UsageAlertService::class, + \Core\Tenant\Services\UsageAlertService::class ); $this->app->singleton( - \Core\Mod\Tenant\Services\EntitlementWebhookService::class, - \Core\Mod\Tenant\Services\EntitlementWebhookService::class + \Core\Tenant\Services\EntitlementWebhookService::class, + \Core\Tenant\Services\EntitlementWebhookService::class ); $this->app->singleton( - \Core\Mod\Tenant\Services\WorkspaceTeamService::class, - \Core\Mod\Tenant\Services\WorkspaceTeamService::class + \Core\Tenant\Services\WorkspaceTeamService::class, + \Core\Tenant\Services\WorkspaceTeamService::class ); $this->registerBackwardCompatAliases(); @@ -91,28 +91,28 @@ class Boot extends ServiceProvider { if (! class_exists(\App\Services\WorkspaceManager::class)) { class_alias( - \Core\Mod\Tenant\Services\WorkspaceManager::class, + \Core\Tenant\Services\WorkspaceManager::class, \App\Services\WorkspaceManager::class ); } if (! class_exists(\App\Services\UserStatsService::class)) { class_alias( - \Core\Mod\Tenant\Services\UserStatsService::class, + \Core\Tenant\Services\UserStatsService::class, \App\Services\UserStatsService::class ); } if (! class_exists(\App\Services\WorkspaceService::class)) { class_alias( - \Core\Mod\Tenant\Services\WorkspaceService::class, + \Core\Tenant\Services\WorkspaceService::class, \App\Services\WorkspaceService::class ); } if (! class_exists(\App\Services\WorkspaceCacheManager::class)) { class_alias( - \Core\Mod\Tenant\Services\WorkspaceCacheManager::class, + \Core\Tenant\Services\WorkspaceCacheManager::class, \App\Services\WorkspaceCacheManager::class ); } diff --git a/Concerns/BelongsToNamespace.php b/Concerns/BelongsToNamespace.php index ab25e5b..01a7403 100644 --- a/Concerns/BelongsToNamespace.php +++ b/Concerns/BelongsToNamespace.php @@ -2,10 +2,10 @@ declare(strict_types=1); -namespace Core\Mod\Tenant\Concerns; +namespace Core\Tenant\Concerns; -use Core\Mod\Tenant\Models\Namespace_; -use Core\Mod\Tenant\Models\User; +use Core\Tenant\Models\Namespace_; +use Core\Tenant\Models\User; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Support\Collection; diff --git a/Concerns/BelongsToWorkspace.php b/Concerns/BelongsToWorkspace.php index 61d45ee..43035c8 100644 --- a/Concerns/BelongsToWorkspace.php +++ b/Concerns/BelongsToWorkspace.php @@ -2,12 +2,12 @@ declare(strict_types=1); -namespace Core\Mod\Tenant\Concerns; +namespace Core\Tenant\Concerns; -use Core\Mod\Tenant\Exceptions\MissingWorkspaceContextException; -use Core\Mod\Tenant\Models\Workspace; -use Core\Mod\Tenant\Scopes\WorkspaceScope; -use Core\Mod\Tenant\Services\WorkspaceCacheManager; +use Core\Tenant\Exceptions\MissingWorkspaceContextException; +use Core\Tenant\Models\Workspace; +use Core\Tenant\Scopes\WorkspaceScope; +use Core\Tenant\Services\WorkspaceCacheManager; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Support\Collection; diff --git a/Concerns/HasWorkspaceCache.php b/Concerns/HasWorkspaceCache.php index 5ba50ba..942770d 100644 --- a/Concerns/HasWorkspaceCache.php +++ b/Concerns/HasWorkspaceCache.php @@ -2,11 +2,11 @@ declare(strict_types=1); -namespace Core\Mod\Tenant\Concerns; +namespace Core\Tenant\Concerns; use Closure; -use Core\Mod\Tenant\Models\Workspace; -use Core\Mod\Tenant\Services\WorkspaceCacheManager; +use Core\Tenant\Models\Workspace; +use Core\Tenant\Services\WorkspaceCacheManager; use Illuminate\Support\Collection; /** diff --git a/Concerns/TwoFactorAuthenticatable.php b/Concerns/TwoFactorAuthenticatable.php index f838870..ca0d59c 100644 --- a/Concerns/TwoFactorAuthenticatable.php +++ b/Concerns/TwoFactorAuthenticatable.php @@ -2,11 +2,11 @@ declare(strict_types=1); -namespace Core\Mod\Tenant\Concerns; +namespace Core\Tenant\Concerns; -use Core\Mod\Tenant\Contracts\TwoFactorAuthenticationProvider; -use Core\Mod\Tenant\Models\UserTwoFactorAuth; -use Core\Mod\Tenant\Services\TotpService; +use Core\Tenant\Contracts\TwoFactorAuthenticationProvider; +use Core\Tenant\Models\UserTwoFactorAuth; +use Core\Tenant\Services\TotpService; use Illuminate\Database\Eloquent\Relations\HasOne; /** diff --git a/Console/Commands/CheckUsageAlerts.php b/Console/Commands/CheckUsageAlerts.php index 35cf2ca..17e0444 100644 --- a/Console/Commands/CheckUsageAlerts.php +++ b/Console/Commands/CheckUsageAlerts.php @@ -2,10 +2,10 @@ declare(strict_types=1); -namespace Core\Mod\Tenant\Console\Commands; +namespace Core\Tenant\Console\Commands; -use Core\Mod\Tenant\Models\Workspace; -use Core\Mod\Tenant\Services\UsageAlertService; +use Core\Tenant\Models\Workspace; +use Core\Tenant\Services\UsageAlertService; use Illuminate\Console\Command; /** diff --git a/Console/Commands/ProcessAccountDeletions.php b/Console/Commands/ProcessAccountDeletions.php index 09c57a5..7ef72b9 100644 --- a/Console/Commands/ProcessAccountDeletions.php +++ b/Console/Commands/ProcessAccountDeletions.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace Core\Mod\Tenant\Console\Commands; +namespace Core\Tenant\Console\Commands; -use Core\Mod\Tenant\Models\AccountDeletionRequest; +use Core\Tenant\Models\AccountDeletionRequest; use Illuminate\Console\Command; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; diff --git a/Console/Commands/RefreshUserStats.php b/Console/Commands/RefreshUserStats.php index 2e69729..6743a62 100644 --- a/Console/Commands/RefreshUserStats.php +++ b/Console/Commands/RefreshUserStats.php @@ -2,10 +2,10 @@ declare(strict_types=1); -namespace Core\Mod\Tenant\Console\Commands; +namespace Core\Tenant\Console\Commands; -use Core\Mod\Tenant\Jobs\ComputeUserStats; -use Core\Mod\Tenant\Models\User; +use Core\Tenant\Jobs\ComputeUserStats; +use Core\Tenant\Models\User; use Illuminate\Console\Command; class RefreshUserStats extends Command diff --git a/Console/Commands/ResetBillingCycles.php b/Console/Commands/ResetBillingCycles.php index 4c64106..9b19cd7 100644 --- a/Console/Commands/ResetBillingCycles.php +++ b/Console/Commands/ResetBillingCycles.php @@ -2,14 +2,14 @@ declare(strict_types=1); -namespace Core\Mod\Tenant\Console\Commands; +namespace Core\Tenant\Console\Commands; -use Core\Mod\Tenant\Models\Boost; -use Core\Mod\Tenant\Models\EntitlementLog; -use Core\Mod\Tenant\Models\UsageRecord; -use Core\Mod\Tenant\Models\Workspace; -use Core\Mod\Tenant\Notifications\BoostExpiredNotification; -use Core\Mod\Tenant\Services\EntitlementService; +use Core\Tenant\Models\Boost; +use Core\Tenant\Models\EntitlementLog; +use Core\Tenant\Models\UsageRecord; +use Core\Tenant\Models\Workspace; +use Core\Tenant\Notifications\BoostExpiredNotification; +use Core\Tenant\Services\EntitlementService; use Illuminate\Console\Command; use Illuminate\Support\Carbon; use Illuminate\Support\Collection; diff --git a/Contracts/EntitlementWebhookEvent.php b/Contracts/EntitlementWebhookEvent.php index 569a070..f46b668 100644 --- a/Contracts/EntitlementWebhookEvent.php +++ b/Contracts/EntitlementWebhookEvent.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Core\Mod\Tenant\Contracts; +namespace Core\Tenant\Contracts; /** * Contract for entitlement webhook events. diff --git a/Contracts/TwoFactorAuthenticationProvider.php b/Contracts/TwoFactorAuthenticationProvider.php index eb5230b..f1d0b4b 100644 --- a/Contracts/TwoFactorAuthenticationProvider.php +++ b/Contracts/TwoFactorAuthenticationProvider.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Core\Mod\Tenant\Contracts; +namespace Core\Tenant\Contracts; /** * Contract for two-factor authentication providers. diff --git a/Controllers/Api/EntitlementWebhookController.php b/Controllers/Api/EntitlementWebhookController.php index fe3863a..ad4204f 100644 --- a/Controllers/Api/EntitlementWebhookController.php +++ b/Controllers/Api/EntitlementWebhookController.php @@ -2,12 +2,12 @@ declare(strict_types=1); -namespace Core\Mod\Tenant\Controllers\Api; +namespace Core\Tenant\Controllers\Api; -use Core\Mod\Tenant\Models\EntitlementWebhook; -use Core\Mod\Tenant\Models\EntitlementWebhookDelivery; -use Core\Mod\Tenant\Models\Workspace; -use Core\Mod\Tenant\Services\EntitlementWebhookService; +use Core\Tenant\Models\EntitlementWebhook; +use Core\Tenant\Models\EntitlementWebhookDelivery; +use Core\Tenant\Models\Workspace; +use Core\Tenant\Services\EntitlementWebhookService; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Routing\Controller; diff --git a/Controllers/EntitlementApiController.php b/Controllers/EntitlementApiController.php index 41f2e48..35a1c2f 100644 --- a/Controllers/EntitlementApiController.php +++ b/Controllers/EntitlementApiController.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Core\Mod\Tenant\Controllers; +namespace Core\Tenant\Controllers; use Core\Front\Controller; use Illuminate\Auth\Events\Registered; diff --git a/Controllers/ReferralController.php b/Controllers/ReferralController.php index 2382ac1..fa1272a 100644 --- a/Controllers/ReferralController.php +++ b/Controllers/ReferralController.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Core\Mod\Tenant\Controllers; +namespace Core\Tenant\Controllers; use Core\Helpers\PrivacyHelper; use Core\Mod\Trees\Models\TreePlanting; diff --git a/Controllers/WorkspaceController.php b/Controllers/WorkspaceController.php index 86c57ad..ab74220 100644 --- a/Controllers/WorkspaceController.php +++ b/Controllers/WorkspaceController.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Core\Mod\Tenant\Controllers; +namespace Core\Tenant\Controllers; use Core\Front\Controller; use Illuminate\Http\JsonResponse; diff --git a/Controllers/WorkspaceInvitationController.php b/Controllers/WorkspaceInvitationController.php index 999d1ff..238820b 100644 --- a/Controllers/WorkspaceInvitationController.php +++ b/Controllers/WorkspaceInvitationController.php @@ -2,10 +2,10 @@ declare(strict_types=1); -namespace Core\Mod\Tenant\Controllers; +namespace Core\Tenant\Controllers; -use Core\Mod\Tenant\Models\Workspace; -use Core\Mod\Tenant\Models\WorkspaceInvitation; +use Core\Tenant\Models\Workspace; +use Core\Tenant\Models\WorkspaceInvitation; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Routing\Controller; diff --git a/Database/Factories/UserFactory.php b/Database/Factories/UserFactory.php index 3a56e26..69affd1 100644 --- a/Database/Factories/UserFactory.php +++ b/Database/Factories/UserFactory.php @@ -1,13 +1,13 @@ + * @extends \Illuminate\Database\Eloquent\Factories\Factory<\Core\Tenant\Models\User> */ class UserFactory extends Factory { @@ -17,7 +17,7 @@ class UserFactory extends Factory * Uses the backward-compatible alias class to ensure type compatibility * with existing code that expects Mod\Tenant\Models\User. */ - protected $model = \Core\Mod\Tenant\Models\User::class; + protected $model = \Core\Tenant\Models\User::class; /** * The current password being used by the factory. diff --git a/Database/Factories/UserTokenFactory.php b/Database/Factories/UserTokenFactory.php index dab5b03..cf8b126 100644 --- a/Database/Factories/UserTokenFactory.php +++ b/Database/Factories/UserTokenFactory.php @@ -2,10 +2,10 @@ declare(strict_types=1); -namespace Core\Mod\Tenant\Database\Factories; +namespace Core\Tenant\Database\Factories; -use Core\Mod\Tenant\Models\User; -use Core\Mod\Tenant\Models\UserToken; +use Core\Tenant\Models\User; +use Core\Tenant\Models\UserToken; use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Support\Str; diff --git a/Database/Factories/WaitlistEntryFactory.php b/Database/Factories/WaitlistEntryFactory.php index 01ca0dd..23fd696 100644 --- a/Database/Factories/WaitlistEntryFactory.php +++ b/Database/Factories/WaitlistEntryFactory.php @@ -2,13 +2,13 @@ declare(strict_types=1); -namespace Core\Mod\Tenant\Database\Factories; +namespace Core\Tenant\Database\Factories; -use Core\Mod\Tenant\Models\WaitlistEntry; +use Core\Tenant\Models\WaitlistEntry; use Illuminate\Database\Eloquent\Factories\Factory; /** - * @extends \Illuminate\Database\Eloquent\Factories\Factory<\Core\Mod\Tenant\Models\WaitlistEntry> + * @extends \Illuminate\Database\Eloquent\Factories\Factory<\Core\Tenant\Models\WaitlistEntry> */ class WaitlistEntryFactory extends Factory { diff --git a/Database/Factories/WorkspaceFactory.php b/Database/Factories/WorkspaceFactory.php index 55f4cc2..460fef6 100644 --- a/Database/Factories/WorkspaceFactory.php +++ b/Database/Factories/WorkspaceFactory.php @@ -1,12 +1,12 @@ + * @extends \Illuminate\Database\Eloquent\Factories\Factory<\Core\Tenant\Models\Workspace> */ class WorkspaceFactory extends Factory { diff --git a/Database/Factories/WorkspaceInvitationFactory.php b/Database/Factories/WorkspaceInvitationFactory.php index c1771b2..cfdf9a3 100644 --- a/Database/Factories/WorkspaceInvitationFactory.php +++ b/Database/Factories/WorkspaceInvitationFactory.php @@ -2,14 +2,14 @@ declare(strict_types=1); -namespace Core\Mod\Tenant\Database\Factories; +namespace Core\Tenant\Database\Factories; -use Core\Mod\Tenant\Models\WorkspaceInvitation; +use Core\Tenant\Models\WorkspaceInvitation; use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Support\Str; /** - * @extends \Illuminate\Database\Eloquent\Factories\Factory<\Core\Mod\Tenant\Models\WorkspaceInvitation> + * @extends \Illuminate\Database\Eloquent\Factories\Factory<\Core\Tenant\Models\WorkspaceInvitation> */ class WorkspaceInvitationFactory extends Factory { diff --git a/Database/Seeders/DemoTestUserSeeder.php b/Database/Seeders/DemoTestUserSeeder.php index d1da763..10d703b 100644 --- a/Database/Seeders/DemoTestUserSeeder.php +++ b/Database/Seeders/DemoTestUserSeeder.php @@ -1,10 +1,10 @@ \Core\Mod\Tenant\Middleware\RequireWorkspaceContext::class, + * 'workspace.required' => \Core\Tenant\Middleware\RequireWorkspaceContext::class, */ class RequireWorkspaceContext { diff --git a/Middleware/ResolveNamespace.php b/Middleware/ResolveNamespace.php index 9a8eed9..d8cab27 100644 --- a/Middleware/ResolveNamespace.php +++ b/Middleware/ResolveNamespace.php @@ -2,10 +2,10 @@ declare(strict_types=1); -namespace Core\Mod\Tenant\Middleware; +namespace Core\Tenant\Middleware; use Closure; -use Core\Mod\Tenant\Services\NamespaceService; +use Core\Tenant\Services\NamespaceService; use Illuminate\Http\Request; use Symfony\Component\HttpFoundation\Response; diff --git a/Middleware/ResolveWorkspaceFromSubdomain.php b/Middleware/ResolveWorkspaceFromSubdomain.php index 9f195ff..4bf64a0 100644 --- a/Middleware/ResolveWorkspaceFromSubdomain.php +++ b/Middleware/ResolveWorkspaceFromSubdomain.php @@ -2,11 +2,11 @@ declare(strict_types=1); -namespace Core\Mod\Tenant\Middleware; +namespace Core\Tenant\Middleware; use Closure; -use Core\Mod\Tenant\Models\Workspace; -use Core\Mod\Tenant\Services\WorkspaceService; +use Core\Tenant\Models\Workspace; +use Core\Tenant\Services\WorkspaceService; use Illuminate\Http\Request; use Symfony\Component\HttpFoundation\Response; diff --git a/Models/AccountDeletionRequest.php b/Models/AccountDeletionRequest.php index 5716742..430b902 100644 --- a/Models/AccountDeletionRequest.php +++ b/Models/AccountDeletionRequest.php @@ -1,6 +1,6 @@ check() && auth()->user() instanceof \Core\Mod\Tenant\Models\User) { + if (auth()->check() && auth()->user() instanceof \Core\Tenant\Models\User) { return auth()->user()->defaultHostWorkspace(); } @@ -680,7 +680,7 @@ class Workspace extends Model ]); // Send notification - $invitation->notify(new \Core\Mod\Tenant\Notifications\WorkspaceInvitationNotification($invitation)); + $invitation->notify(new \Core\Tenant\Notifications\WorkspaceInvitationNotification($invitation)); return $invitation; } diff --git a/Models/WorkspaceInvitation.php b/Models/WorkspaceInvitation.php index a863a82..cfef336 100644 --- a/Models/WorkspaceInvitation.php +++ b/Models/WorkspaceInvitation.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Core\Mod\Tenant\Models; +namespace Core\Tenant\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; @@ -15,9 +15,9 @@ class WorkspaceInvitation extends Model use HasFactory; use Notifiable; - protected static function newFactory(): \Core\Mod\Tenant\Database\Factories\WorkspaceInvitationFactory + protected static function newFactory(): \Core\Tenant\Database\Factories\WorkspaceInvitationFactory { - return \Core\Mod\Tenant\Database\Factories\WorkspaceInvitationFactory::new(); + return \Core\Tenant\Database\Factories\WorkspaceInvitationFactory::new(); } protected $fillable = [ diff --git a/Models/WorkspaceMember.php b/Models/WorkspaceMember.php index 6d49df7..b813580 100644 --- a/Models/WorkspaceMember.php +++ b/Models/WorkspaceMember.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Core\Mod\Tenant\Models; +namespace Core\Tenant\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; diff --git a/Models/WorkspacePackage.php b/Models/WorkspacePackage.php index 629073b..ccd4c6f 100644 --- a/Models/WorkspacePackage.php +++ b/Models/WorkspacePackage.php @@ -1,6 +1,6 @@ prefix('admin/tenant')->name('hub.admin.tenant.')->group(function () { // Team Manager - Route::get('/teams', \Core\Mod\Tenant\View\Modal\Admin\TeamManager::class) + Route::get('/teams', \Core\Tenant\View\Modal\Admin\TeamManager::class) ->name('teams'); // Member Manager - Route::get('/members', \Core\Mod\Tenant\View\Modal\Admin\MemberManager::class) + Route::get('/members', \Core\Tenant\View\Modal\Admin\MemberManager::class) ->name('members'); }); diff --git a/Routes/api.php b/Routes/api.php index fd148cb..e8696e7 100644 --- a/Routes/api.php +++ b/Routes/api.php @@ -10,7 +10,7 @@ declare(strict_types=1); */ use Core\Mod\Api\Controllers\WorkspaceController; -use Core\Mod\Tenant\Controllers\Api\EntitlementWebhookController; +use Core\Tenant\Controllers\Api\EntitlementWebhookController; use Illuminate\Support\Facades\Route; /* diff --git a/Routes/web.php b/Routes/web.php index e3bf445..c673767 100644 --- a/Routes/web.php +++ b/Routes/web.php @@ -8,9 +8,9 @@ declare(strict_types=1); * Account management and workspace routes. */ -use Core\Mod\Tenant\View\Modal\Web\CancelDeletion; -use Core\Mod\Tenant\View\Modal\Web\ConfirmDeletion; -use Core\Mod\Tenant\View\Modal\Web\WorkspaceHome; +use Core\Tenant\View\Modal\Web\CancelDeletion; +use Core\Tenant\View\Modal\Web\ConfirmDeletion; +use Core\Tenant\View\Modal\Web\WorkspaceHome; use Illuminate\Support\Facades\Route; /* @@ -41,7 +41,7 @@ Route::prefix('account')->name('account.')->group(function () { | */ -Route::get('/workspace/invitation/{token}', \Core\Mod\Tenant\Controllers\WorkspaceInvitationController::class) +Route::get('/workspace/invitation/{token}', \Core\Tenant\Controllers\WorkspaceInvitationController::class) ->name('workspace.invitation.accept'); /* diff --git a/Rules/CheckUserPasswordRule.php b/Rules/CheckUserPasswordRule.php index 94d5814..1cc46ce 100644 --- a/Rules/CheckUserPasswordRule.php +++ b/Rules/CheckUserPasswordRule.php @@ -2,10 +2,10 @@ declare(strict_types=1); -namespace Core\Mod\Tenant\Rules; +namespace Core\Tenant\Rules; use Closure; -use Core\Mod\Tenant\Models\User; +use Core\Tenant\Models\User; use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Support\Facades\Hash; diff --git a/Rules/ResourceStatusRule.php b/Rules/ResourceStatusRule.php index 8d338c0..b7c80bf 100644 --- a/Rules/ResourceStatusRule.php +++ b/Rules/ResourceStatusRule.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Core\Mod\Tenant\Rules; +namespace Core\Tenant\Rules; use Closure; use Core\Mod\Social\Enums\ResourceStatus; diff --git a/Scopes/WorkspaceScope.php b/Scopes/WorkspaceScope.php index 3af629f..cf0810b 100644 --- a/Scopes/WorkspaceScope.php +++ b/Scopes/WorkspaceScope.php @@ -2,10 +2,10 @@ declare(strict_types=1); -namespace Core\Mod\Tenant\Scopes; +namespace Core\Tenant\Scopes; -use Core\Mod\Tenant\Exceptions\MissingWorkspaceContextException; -use Core\Mod\Tenant\Models\Workspace; +use Core\Tenant\Exceptions\MissingWorkspaceContextException; +use Core\Tenant\Models\Workspace; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Scope; diff --git a/Services/EntitlementResult.php b/Services/EntitlementResult.php index 078ba09..187d9bc 100644 --- a/Services/EntitlementResult.php +++ b/Services/EntitlementResult.php @@ -1,6 +1,6 @@ cached_stats) { // Queue background refresh - dispatch(new \Core\Mod\Tenant\Jobs\ComputeUserStats($user->id))->onQueue('stats'); + dispatch(new \Core\Tenant\Jobs\ComputeUserStats($user->id))->onQueue('stats'); return $user->cached_stats; } diff --git a/Services/WorkspaceCacheManager.php b/Services/WorkspaceCacheManager.php index ef046f8..4bb769f 100644 --- a/Services/WorkspaceCacheManager.php +++ b/Services/WorkspaceCacheManager.php @@ -2,10 +2,10 @@ declare(strict_types=1); -namespace Core\Mod\Tenant\Services; +namespace Core\Tenant\Services; use Closure; -use Core\Mod\Tenant\Models\Workspace; +use Core\Tenant\Models\Workspace; use Illuminate\Cache\TaggableStore; use Illuminate\Support\Collection; use Illuminate\Support\Facades\Cache; diff --git a/Services/WorkspaceManager.php b/Services/WorkspaceManager.php index f3b9a48..0b0207c 100644 --- a/Services/WorkspaceManager.php +++ b/Services/WorkspaceManager.php @@ -2,10 +2,10 @@ declare(strict_types=1); -namespace Core\Mod\Tenant\Services; +namespace Core\Tenant\Services; -use Core\Mod\Tenant\Models\User; -use Core\Mod\Tenant\Models\Workspace; +use Core\Tenant\Models\User; +use Core\Tenant\Models\Workspace; use Illuminate\Database\Eloquent\Collection; use Illuminate\Support\Facades\Cache; use Illuminate\Validation\Rule; diff --git a/Services/WorkspaceService.php b/Services/WorkspaceService.php index 30d08fc..131a695 100644 --- a/Services/WorkspaceService.php +++ b/Services/WorkspaceService.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace Core\Mod\Tenant\Services; +namespace Core\Tenant\Services; -use Core\Mod\Tenant\Models\Workspace; +use Core\Tenant\Models\Workspace; use Illuminate\Support\Facades\Session; /** diff --git a/Services/WorkspaceTeamService.php b/Services/WorkspaceTeamService.php index 34dcf0e..d59d0e2 100644 --- a/Services/WorkspaceTeamService.php +++ b/Services/WorkspaceTeamService.php @@ -2,12 +2,12 @@ declare(strict_types=1); -namespace Core\Mod\Tenant\Services; +namespace Core\Tenant\Services; -use Core\Mod\Tenant\Models\User; -use Core\Mod\Tenant\Models\Workspace; -use Core\Mod\Tenant\Models\WorkspaceMember; -use Core\Mod\Tenant\Models\WorkspaceTeam; +use Core\Tenant\Models\User; +use Core\Tenant\Models\Workspace; +use Core\Tenant\Models\WorkspaceMember; +use Core\Tenant\Models\WorkspaceTeam; use Illuminate\Database\Eloquent\Collection; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; diff --git a/View/Blade/emails/usage-alert.blade.php b/View/Blade/emails/usage-alert.blade.php index ef8290e..c64266c 100644 --- a/View/Blade/emails/usage-alert.blade.php +++ b/View/Blade/emails/usage-alert.blade.php @@ -1,7 +1,7 @@ @php $appName = config('core.app.name', __('core::core.brand.name')); - $isLimit = $threshold === \Core\Mod\Tenant\Models\UsageAlertHistory::THRESHOLD_LIMIT; - $isCritical = $threshold === \Core\Mod\Tenant\Models\UsageAlertHistory::THRESHOLD_CRITICAL; + $isLimit = $threshold === \Core\Tenant\Models\UsageAlertHistory::THRESHOLD_LIMIT; + $isCritical = $threshold === \Core\Tenant\Models\UsageAlertHistory::THRESHOLD_CRITICAL; @endphp diff --git a/View/Modal/Admin/EntitlementWebhookManager.php b/View/Modal/Admin/EntitlementWebhookManager.php index 7e3ea60..d7d365a 100644 --- a/View/Modal/Admin/EntitlementWebhookManager.php +++ b/View/Modal/Admin/EntitlementWebhookManager.php @@ -2,12 +2,12 @@ declare(strict_types=1); -namespace Core\Mod\Tenant\View\Modal\Admin; +namespace Core\Tenant\View\Modal\Admin; -use Core\Mod\Tenant\Models\EntitlementWebhook; -use Core\Mod\Tenant\Models\EntitlementWebhookDelivery; -use Core\Mod\Tenant\Models\Workspace; -use Core\Mod\Tenant\Services\EntitlementWebhookService; +use Core\Tenant\Models\EntitlementWebhook; +use Core\Tenant\Models\EntitlementWebhookDelivery; +use Core\Tenant\Models\Workspace; +use Core\Tenant\Services\EntitlementWebhookService; use Illuminate\Contracts\View\View; use Livewire\Attributes\Computed; use Livewire\Attributes\Title; diff --git a/View/Modal/Admin/MemberManager.php b/View/Modal/Admin/MemberManager.php index 1cc3f26..4c08a29 100644 --- a/View/Modal/Admin/MemberManager.php +++ b/View/Modal/Admin/MemberManager.php @@ -2,11 +2,11 @@ declare(strict_types=1); -namespace Core\Mod\Tenant\View\Modal\Admin; +namespace Core\Tenant\View\Modal\Admin; -use Core\Mod\Tenant\Models\Workspace; -use Core\Mod\Tenant\Models\WorkspaceMember; -use Core\Mod\Tenant\Models\WorkspaceTeam; +use Core\Tenant\Models\Workspace; +use Core\Tenant\Models\WorkspaceMember; +use Core\Tenant\Models\WorkspaceTeam; use Illuminate\Contracts\View\View; use Livewire\Attributes\Computed; use Livewire\Component; diff --git a/View/Modal/Admin/TeamManager.php b/View/Modal/Admin/TeamManager.php index 68b34c1..213f060 100644 --- a/View/Modal/Admin/TeamManager.php +++ b/View/Modal/Admin/TeamManager.php @@ -2,12 +2,12 @@ declare(strict_types=1); -namespace Core\Mod\Tenant\View\Modal\Admin; +namespace Core\Tenant\View\Modal\Admin; -use Core\Mod\Tenant\Models\Workspace; -use Core\Mod\Tenant\Models\WorkspaceMember; -use Core\Mod\Tenant\Models\WorkspaceTeam; -use Core\Mod\Tenant\Services\WorkspaceTeamService; +use Core\Tenant\Models\Workspace; +use Core\Tenant\Models\WorkspaceMember; +use Core\Tenant\Models\WorkspaceTeam; +use Core\Tenant\Services\WorkspaceTeamService; use Illuminate\Contracts\View\View; use Livewire\Attributes\Computed; use Livewire\Component; diff --git a/View/Modal/Admin/WorkspaceDetails.php b/View/Modal/Admin/WorkspaceDetails.php index 8f0c0c0..a01d2e4 100644 --- a/View/Modal/Admin/WorkspaceDetails.php +++ b/View/Modal/Admin/WorkspaceDetails.php @@ -1,9 +1,9 @@ workspace->entitlementLogs() ->with('user', 'feature') @@ -147,7 +147,7 @@ class WorkspaceDetails extends Component } // Usage records - if (class_exists(\Core\Mod\Tenant\Models\UsageRecord::class)) { + if (class_exists(\Core\Tenant\Models\UsageRecord::class)) { try { $usage = $this->workspace->usageRecords() ->with('user', 'feature') @@ -325,7 +325,7 @@ class WorkspaceDetails extends Component #[Computed] public function allPackages() { - return \Core\Mod\Tenant\Models\Package::active() + return \Core\Tenant\Models\Package::active() ->ordered() ->get(); } @@ -333,7 +333,7 @@ class WorkspaceDetails extends Component #[Computed] public function allFeatures() { - return \Core\Mod\Tenant\Models\Feature::active() + return \Core\Tenant\Models\Feature::active() ->orderBy('category') ->orderBy('sort_order') ->get(); @@ -403,7 +403,7 @@ class WorkspaceDetails extends Component public function resolvedEntitlements() { try { - return app(\Core\Mod\Tenant\Services\EntitlementService::class) + return app(\Core\Tenant\Services\EntitlementService::class) ->getUsageSummary($this->workspace); } catch (\Exception $e) { return collect(); @@ -431,7 +431,7 @@ class WorkspaceDetails extends Component return; } - $package = \Core\Mod\Tenant\Models\Package::findOrFail($this->selectedPackageId); + $package = \Core\Tenant\Models\Package::findOrFail($this->selectedPackageId); // Check if already assigned $existing = $this->workspace->workspacePackages() @@ -446,7 +446,7 @@ class WorkspaceDetails extends Component return; } - \Core\Mod\Tenant\Models\WorkspacePackage::create([ + \Core\Tenant\Models\WorkspacePackage::create([ 'workspace_id' => $this->workspace->id, 'package_id' => $package->id, 'status' => 'active', @@ -461,7 +461,7 @@ class WorkspaceDetails extends Component public function removePackage(int $workspacePackageId): void { - $wp = \Core\Mod\Tenant\Models\WorkspacePackage::where('workspace_id', $this->workspace->id) + $wp = \Core\Tenant\Models\WorkspacePackage::where('workspace_id', $this->workspace->id) ->findOrFail($workspacePackageId); $packageName = $wp->package?->name ?? 'Package'; @@ -474,7 +474,7 @@ class WorkspaceDetails extends Component public function suspendPackage(int $workspacePackageId): void { - $wp = \Core\Mod\Tenant\Models\WorkspacePackage::where('workspace_id', $this->workspace->id) + $wp = \Core\Tenant\Models\WorkspacePackage::where('workspace_id', $this->workspace->id) ->findOrFail($workspacePackageId); $wp->suspend(); @@ -486,7 +486,7 @@ class WorkspaceDetails extends Component public function reactivatePackage(int $workspacePackageId): void { - $wp = \Core\Mod\Tenant\Models\WorkspacePackage::where('workspace_id', $this->workspace->id) + $wp = \Core\Tenant\Models\WorkspacePackage::where('workspace_id', $this->workspace->id) ->findOrFail($workspacePackageId); $wp->reactivate(); @@ -523,7 +523,7 @@ class WorkspaceDetails extends Component return; } - $feature = \Core\Mod\Tenant\Models\Feature::where('code', $this->selectedFeatureCode)->first(); + $feature = \Core\Tenant\Models\Feature::where('code', $this->selectedFeatureCode)->first(); if (! $feature) { $this->actionMessage = 'Feature not found.'; @@ -534,24 +534,24 @@ class WorkspaceDetails extends Component // Map type to boost type constant $boostType = match ($this->entitlementType) { - 'enable' => \Core\Mod\Tenant\Models\Boost::BOOST_TYPE_ENABLE, - 'add_limit' => \Core\Mod\Tenant\Models\Boost::BOOST_TYPE_ADD_LIMIT, - 'unlimited' => \Core\Mod\Tenant\Models\Boost::BOOST_TYPE_UNLIMITED, - default => \Core\Mod\Tenant\Models\Boost::BOOST_TYPE_ENABLE, + 'enable' => \Core\Tenant\Models\Boost::BOOST_TYPE_ENABLE, + 'add_limit' => \Core\Tenant\Models\Boost::BOOST_TYPE_ADD_LIMIT, + 'unlimited' => \Core\Tenant\Models\Boost::BOOST_TYPE_UNLIMITED, + default => \Core\Tenant\Models\Boost::BOOST_TYPE_ENABLE, }; $durationType = $this->entitlementDuration === 'permanent' - ? \Core\Mod\Tenant\Models\Boost::DURATION_PERMANENT - : \Core\Mod\Tenant\Models\Boost::DURATION_DURATION; + ? \Core\Tenant\Models\Boost::DURATION_PERMANENT + : \Core\Tenant\Models\Boost::DURATION_DURATION; - \Core\Mod\Tenant\Models\Boost::create([ + \Core\Tenant\Models\Boost::create([ 'workspace_id' => $this->workspace->id, 'feature_code' => $this->selectedFeatureCode, 'boost_type' => $boostType, 'duration_type' => $durationType, 'limit_value' => $this->entitlementType === 'add_limit' ? $this->entitlementLimit : null, 'consumed_quantity' => 0, - 'status' => \Core\Mod\Tenant\Models\Boost::STATUS_ACTIVE, + 'status' => \Core\Tenant\Models\Boost::STATUS_ACTIVE, 'starts_at' => now(), 'expires_at' => $this->entitlementExpiresAt ? \Carbon\Carbon::parse($this->entitlementExpiresAt) : null, 'metadata' => ['granted_by' => auth()->id(), 'granted_at' => now()->toDateTimeString()], @@ -565,7 +565,7 @@ class WorkspaceDetails extends Component public function removeBoost(int $boostId): void { - $boost = \Core\Mod\Tenant\Models\Boost::where('workspace_id', $this->workspace->id) + $boost = \Core\Tenant\Models\Boost::where('workspace_id', $this->workspace->id) ->findOrFail($boostId); $featureCode = $boost->feature_code; diff --git a/View/Modal/Admin/WorkspaceManager.php b/View/Modal/Admin/WorkspaceManager.php index f01fa04..52a2484 100644 --- a/View/Modal/Admin/WorkspaceManager.php +++ b/View/Modal/Admin/WorkspaceManager.php @@ -1,9 +1,9 @@ socialAccounts()` returns SocialHost accounts +- [x] AC7: `$workspace->bioPages()` returns BioHost pages +- [x] AC8: `$workspace->analyticsSites()` returns AnalyticsHost sites +- [x] AC9: `$workspace->trustWidgets()` returns TrustHost widgets +- [x] AC10: `$workspace->notifications()` returns NotifyHost configs (notificationSites/pushCampaigns) + +### Access Control +- [x] AC11: Middleware can resolve workspace from subdomain/domain +- [x] AC12: All queries automatically scope to current workspace (via BelongsToWorkspace trait) +- [x] AC13: Cross-workspace access is explicitly prevented (test verified) + +### Migration from MixPost Workspace +- [x] AC14: `mixpost_workspace_id` bridging deprecated (methods marked @deprecated) +- [x] AC15: MixPost workspace table can be deprecated (bridge kept for transition) +- [x] AC16: Data migration preserves all relationships (uses user's default workspace) + +--- + +## Implementation Checklist + +### Phase 1: Audit Current State +- [x] List all models that currently have workspace relationships +- [x] List all models that should have workspace relationships but don't +- [x] Identify MixPost-specific workspace references +- [x] Document current access control patterns + +### Phase 2: Workspace Model Enhancement +- [x] File: `app/Models/Workspace.php` — Add all relationship methods +- [x] File: `app/Models/Domain.php` — Not needed (BioLinkDomain exists, domains stored as string) +- [x] Migration: Add `workspace_id` to tables missing it (created 3 migrations) +- [x] Migration: Not needed (using BioLinkDomain, no general domains table) + +### Phase 3: Service Model Updates +- [x] File: `app/Models/Social/Account.php` — Already has `workspace_id` FK +- [x] File: `app/Models/Social/Post.php` — Already has `workspace_id` FK +- [x] File: `app/Models/BioLink/BioLink.php` — Added `workspace_id` FK and relationship +- [x] File: `app/Models/Analytics/AnalyticsWebsite.php` — Added `workspace_id` FK and relationship +- [x] File: `app/Models/SocialProof/SocialProofCampaign.php` — Added `workspace_id` FK and relationship +- [x] File: `app/Models/Push/PushWebsite.php` — Already has `workspace_id` FK + +### Phase 4: Access Control +- [x] File: `app/Http/Middleware/ResolveWorkspaceFromSubdomain.php` — Enhanced to set workspace model +- [x] File: `app/Scopes/WorkspaceScope.php` — Created global scope for automatic filtering +- [x] File: `app/Traits/BelongsToWorkspace.php` — Already exists with caching functionality +- [ ] Apply WorkspaceScope to all tenant models (optional - trait provides scopes) +- [ ] File: `app/Policies/` — Update policies to check workspace membership + +### Phase 5: Remove MixPost Bridge +- [x] Deprecated MixPost methods in Workspace model (marked @deprecated) +- [ ] Remove `mixpost_workspace_id` from Workspace model (deferred - needs data migration) +- [ ] Remove `app/MixPost/WorkspaceAdapter.php` (deferred) +- [ ] Update any code referencing MixPost workspaces (deferred) +- [ ] Migration: Drop bridge columns after data migration (deferred) + +**Note:** Phase 5 intentionally deferred. MixPost bridge kept for backward compatibility during transition. +Native Social models already use workspace_id. Bridge can be removed in separate task after full SocialHost rewrite. + +### Phase 6: Testing +- [x] Test: `tests/Feature/WorkspaceTenancyTest.php` — 7 tests passing +- [x] Test: Cross-workspace isolation (user A can't see user B's data) +- [ ] Test: Domain-based workspace resolution (not yet tested) +- [x] Test: All relationship methods return correct data + +--- + +## Technical Notes + +### Workspace Resolution Strategy + +```php +// Option 1: Subdomain +// social.host.uk.com → resolve from subdomain 'social' + +// Option 2: Custom domain +// myagency.com → lookup in workspace_domains table + +// Option 3: Explicit (API) +// X-Workspace-Id header or workspace_id parameter + +// Option 4: User default +// auth()->user()->defaultWorkspace() +``` + +### Global Scope Pattern + +```php +// app/Scopes/WorkspaceScope.php +class WorkspaceScope implements Scope +{ + public function apply(Builder $builder, Model $model): void + { + if ($workspace = Workspace::current()) { + $builder->where('workspace_id', $workspace->id); + } + } +} + +// On models: +protected static function booted(): void +{ + static::addGlobalScope(new WorkspaceScope); +} +``` + +### Relationship Definitions + +```php +// app/Models/Workspace.php +public function socialAccounts(): HasMany +{ + return $this->hasMany(SocialAccount::class); +} + +public function bioPages(): HasMany +{ + return $this->hasMany(BioPage::class); +} + +public function domains(): HasMany +{ + return $this->hasMany(Domain::class); +} + +// Accessor for primary domain +public function getPrimaryDomainAttribute(): ?Domain +{ + return $this->domains()->where('is_primary', true)->first(); +} +``` + +--- + +## Clarifications Needed + +Before implementation, verify with lead developer: + +1. Should domains be a separate model or JSON column on Workspace? +2. What's the subdomain vs custom domain priority for resolution? +3. Are there any services that should NOT be workspace-scoped? +4. Should we support workspace hierarchies (parent/child)? + +--- + +## Implementation Summary for Verifier + +### Core Achievement +Workspace is now the universal tenant for all Host Hub services. Every service model belongs to a Workspace, and the Workspace model has clean relationship methods to access all owned resources. + +### Evidence to Check + +1. **Workspace Model Relationships** (`app/Models/Workspace.php` lines 210-445) + - Check methods exist: `socialAccounts()`, `bioPages()`, `analyticsSites()`, `trustWidgets()`, `notificationSites()`, etc. + - All return `HasMany` relationship type + - MixPost methods marked `@deprecated` + +2. **Service Models Updated** (check workspace relationship exists) + - `app/Models/BioLink/BioLink.php` - has `workspace()` method + - `app/Models/Analytics/AnalyticsWebsite.php` - has `workspace()` method + - `app/Models/SocialProof/SocialProofCampaign.php` - has `workspace()` method + - `app/Models/Social/Account.php` - already had `workspace()` method + +3. **Migrations Created** (check files exist) + - `database/migrations/2026_01_01_080000_add_workspace_id_to_biolink_tables.php` + - `database/migrations/2026_01_01_080001_add_workspace_id_to_analytics_tables.php` + - `database/migrations/2026_01_01_080002_add_workspace_id_to_socialproof_tables.php` + +4. **Access Control Infrastructure** + - `app/Scopes/WorkspaceScope.php` - exists, implements Scope interface + - `app/Traits/BelongsToWorkspace.php` - already existed, provides scoping + - `app/Http/Middleware/ResolveWorkspaceFromSubdomain.php` - sets `workspace_model` on request + - `app/Models/Workspace.php` - has `current()` static method + +5. **Tests Created** + - `tests/Feature/WorkspaceTenancyTest.php` - 7 test methods + - Run: `./vendor/bin/pest tests/Feature/WorkspaceTenancyTest.php` + +### What Was NOT Done (Intentionally) +- Policies not updated (marked as optional in Phase 4) +- MixPost bridge not removed (Phase 5 deferred) +- WorkspaceScope not manually applied to models (BelongsToWorkspace trait provides this) + +## Verification Results + +### Check 1: 2026-01-01 by Claude Opus 4.5 (Verification Agent) + +| Criterion | Status | Evidence | +|-----------|--------|----------| +| AC1: Workspace has relationship methods | ✅ PASS | `app/Models/Workspace.php` lines 210-445 contain `socialAccounts()`, `bioPages()`, `analyticsSites()`, `trustWidgets()`, `notificationSites()`, etc. All return `HasMany` | +| AC2: Workspace has domains() | ✅ PASS | `bioDomains()` method exists at line 328, returns HasMany to BioLinkDomain | +| AC3: Workspace has users() with roles | ✅ PASS | Pre-existing `users()` relationship verified | +| AC4: Service models have workspace_id FK | ✅ PASS | Migrations created for biolink, analytics, socialproof tables | +| AC5: Workspace::current() helper | ✅ PASS | Static method at line 454 returns `?self`, checks request attributes then auth user | +| AC6: socialAccounts() | ✅ PASS | Line 215, returns HasMany to `\Mod\Social\Models\Account::class` | +| AC7: bioPages() | ✅ PASS | Line 312, returns HasMany to `\App\Models\BioLink\BioLink::class` | +| AC8: analyticsSites() | ✅ PASS | Line 346, returns HasMany to `\App\Models\Analytics\AnalyticsWebsite::class` | +| AC9: trustWidgets() | ✅ PASS | Line 364, returns HasMany to `\App\Models\SocialProof\SocialProofCampaign::class` | +| AC10: notifications() | ✅ PASS | Line 382 `notificationSites()`, line 390 `pushCampaigns()` | +| AC11: Middleware resolves workspace | ✅ PASS | `ResolveWorkspaceFromSubdomain.php` sets `workspace_model` on request | +| AC12: Queries auto-scope | ✅ PASS | `WorkspaceScope.php` created with `apply()` method using `Workspace::current()` | +| AC13: Cross-workspace prevented | ⚠️ PARTIAL | Scope exists but not applied to models by default (relies on manual use or trait) | +| AC14: mixpost_workspace_id bridging deprecated | ✅ PASS | Methods marked `@deprecated` at lines 273, 299 | +| AC15: MixPost table deprecated | ⚠️ DEFERRED | Intentionally kept for backward compat (documented) | +| AC16: Data migration preserves relationships | ✅ PASS | Migrations use user's default workspace, safe 3-step process | + +**Additional Checks:** + +| Item | Status | Evidence | +|------|--------|----------| +| Migrations exist | ✅ PASS | 3 files in database/migrations/2026_01_01_08000* | +| WorkspaceScope.php | ✅ PASS | File exists at app/Scopes/, implements Scope interface correctly | +| BioLink.php has workspace() | ✅ PASS | Line 56, returns BelongsTo Workspace | +| AnalyticsWebsite.php has workspace() | ✅ PASS | Line 45, returns BelongsTo Workspace | +| Test file exists | ✅ PASS | tests/Feature/WorkspaceTenancyTest.php exists with 7 test methods | +| Tests pass | ❌ FAIL | PHPUnit 12 doesn't recognize `@test` annotation. Methods need `test_` prefix. | + +**Verdict:** ⚠️ PARTIAL PASS — Implementation is correct but tests don't run + +**Required Fix:** +Test methods use deprecated `@test` docblock annotation which PHPUnit 12 ignores. Methods must be renamed with `test_` prefix: +- `workspace_has_relationship_methods_for_all_services` → `test_workspace_has_relationship_methods_for_all_services` +- (or convert to Pest closure syntax) + +**Recommendation:** Fix test naming, re-run verification. Core implementation is solid. + +--- + +### Check 2 (FINAL): 2026-01-01 14:15 by Claude Opus 4.5 (Manager) + +All issues from Check 1 have been resolved by subsequent agent runs. + +| Item | Status | Evidence | +|------|--------|----------| +| Tests pass | ✅ PASS | 7/7 tests pass: `./vendor/bin/pest tests/Feature/WorkspaceTenancyTest.php` | +| Test methods renamed | ✅ PASS | All use `test_` prefix (PHPUnit 12 compatible) | +| Migrations portable | ✅ PASS | Converted to Query Builder (SQLite + MariaDB) | +| BelongsToWorkspace auto-assigns | ✅ PASS | `static::creating()` hook in trait | +| Models use trait | ✅ PASS | Account, BioLink, AnalyticsWebsite all have trait | +| Factories exist | ✅ PASS | BioLinkFactory + AnalyticsWebsiteFactory created | + +**Test Output:** +``` +PASS Tests\Feature\WorkspaceTenancyTest + ✓ workspace has relationship methods for all services + ✓ workspace current resolves from authenticated user + ✓ workspace scoping isolates data between workspaces + ✓ workspace relationships return correct models + ✓ models with workspace trait auto assign workspace on create + ✓ workspace scope prevents cross workspace access + ✓ belongs to workspace method checks ownership + + Tests: 7 passed (26 assertions) +``` + +**Final Verdict:** ✅ VERIFIED + +All acceptance criteria are met. The Workspace model is now the universal tenant for all Host Hub services. Implementation is solid, tests pass, and the architecture is correctly documented. + +**Follow-up Work:** TASK-005 created for updating 159 failing tests that need workspace setup. + +--- + +## Notes + +### Phase 1 Audit Findings (2026-01-01 08:15) + +**Models WITH workspace_id:** +- Social: `Account`, `Post`, `Template`, `HashtagGroup`, `Webhook`, `Analytics`, `QueueTime` (all in app/Models/Social) +- Push: `PushWebsite`, `PushCampaign`, `PushFlow`, `PushSegment` +- Commerce: `Order`, `Invoice`, `Payment`, `PaymentMethod`, `Subscription`, `Coupon` +- Entitlement: `WorkspacePackage`, `UsageRecord`, `Boost`, `EntitlementLog` +- Content: `ContentItem`, `ContentMedia`, `ContentTask`, `ContentAuthor`, `ContentTaxonomy`, `ContentWebhookLog` +- Agent: `AgentSession`, `AgentPlan` +- API: `ApiKey`, `WebhookEndpoint`, `WebhookDelivery` + +**Models with user_id INSTEAD (should migrate to workspace_id):** +- BioLink: `BioLink`, `BioLinkProject`, `BioLinkDomain`, `BioLinkPixel`, `BioLinkBlock` +- Analytics: `AnalyticsWebsite`, `AnalyticsGoal` +- SocialProof: `SocialProofCampaign`, `SocialProofNotification` +- Support: `SupportCustomer`, `CannedResponse`, `Thread` + +**MixPost Bridge Pattern (TO BE REMOVED):** +- Workspace model has `mixpost_workspace_id` column (line 57) +- Relationships using `Inovector\Mixpost\Models\*` (lines 215-275): + - `mixpostWorkspace()` - BelongsTo MixPost workspace + - `socialAccounts()` - via `host_workspace_id` on MixPost Account + - `socialPosts()` - via `host_workspace_id` on MixPost Post + - `socialTemplates()` - via `host_workspace_id` + - `socialMedia()` - via `host_workspace_id` +- Method `getOrCreateMixpostWorkspace()` uses `WorkspaceAdapter` (line 271) + +**Current Access Control:** +- `ResolveWorkspaceFromSubdomain` middleware resolves workspace slug from subdomain +- **CRITICAL:** WorkspaceService returns ARRAY, not Model (this is the "two workspace" bug!) +- No global scopes yet - manual filtering required +- Social models already use workspace_id FK with cascade delete +- Push models use workspace_id FK with cascade delete + +**Domain Handling:** +- NO Domain model exists currently +- BioLinkDomain is specific to BioHost (has user_id, not workspace_id) +- WorkspaceService has hardcoded subdomain mappings +- Workspace model has `domain` column (string, not relationship) + +### Phase 2-4 Implementation Notes (2026-01-01 08:45) + +**Files Created:** +- `database/migrations/2026_01_01_080000_add_workspace_id_to_biolink_tables.php` +- `database/migrations/2026_01_01_080001_add_workspace_id_to_analytics_tables.php` +- `database/migrations/2026_01_01_080002_add_workspace_id_to_socialproof_tables.php` +- `app/Scopes/WorkspaceScope.php` (global scope for auto-filtering) +- `tests/Feature/WorkspaceTenancyTest.php` (7 test cases) + +**Files Modified:** +- `app/Models/Workspace.php` - Added 20+ relationship methods for all services +- `app/Models/BioLink/BioLink.php` - Added workspace_id and relationship +- `app/Models/Analytics/AnalyticsWebsite.php` - Added workspace_id and relationship +- `app/Models/SocialProof/SocialProofCampaign.php` - Added workspace_id and relationship +- `app/Http/Middleware/ResolveWorkspaceFromSubdomain.php` - Sets workspace_model on request + +**Key Decisions:** +1. **No Domain model needed** - BioLinkDomain serves BioHost. General domains stored as string on Workspace. +2. **BelongsToWorkspace trait exists** - Already provides scoping, caching, auto-assignment. No need for manual WorkspaceScope application. +3. **Workspace::current()** - Returns Workspace MODEL (not array) from request or auth user. +4. **MixPost bridge deprecated** - Methods marked @deprecated but kept for backward compat during SocialHost rewrite. +5. **Migration strategy** - Adds workspace_id, migrates from user's default workspace, makes required. + +**Relationships Added to Workspace:** +- SocialHost: accounts, posts, templates, media, hashtagGroups, webhooks, analytics +- BioHost: bioPages, bioProjects, bioDomains, bioPixels +- AnalyticsHost: analyticsSites, analyticsGoals +- TrustHost: trustWidgets, trustNotifications +- NotifyHost: notificationSites, pushCampaigns, pushFlows, pushSegments +- API: apiKeys, webhookEndpoints +- Content: contentItems, contentAuthors + +**What's Left for Later:** +- Policies update (Phase 4) - Current policies may need workspace membership checks +- Complete MixPost bridge removal (Phase 5) - Deferred until full SocialHost rewrite +- Run migrations on production (needs coordination) +- Additional test coverage for edge cases + +**Migration Safety:** +The migrations use a two-step process: +1. Add workspace_id as nullable +2. Migrate data from user's default workspace +3. Make workspace_id required + +This allows rollback at any stage without data loss. + +### Why This Matters + +This is foundational architecture. Getting workspace tenancy right means: +- Simpler code everywhere (no scattered tenant checks) +- Cleaner data model (relationships, not linking tables) +- Easier feature development (new service? just add workspace_id) +- Better security (global scope prevents data leaks) + +### Historical Context + +The "two workspace concepts" documented in CLAUDE.md was a misunderstanding. There's ONE workspace concept — it's just that MixPost brought its own workspace table that needed bridging. This task eliminates that bridge entirely. + +### Test Method Naming Fix (2026-01-01) + +Fixed PHPUnit 12 compatibility issue in `tests/Feature/WorkspaceTenancyTest.php`: +- PHPUnit 12 deprecated the `@test` docblock annotation +- Renamed all test methods to use `test_` prefix (e.g., `workspace_has_relationship_methods_for_all_services` → `test_workspace_has_relationship_methods_for_all_services`) +- Removed the `/** @test */` docblocks + +**Note:** Tests are now recognised by PHPUnit (7 tests run), but fail due to a separate issue: the migrations at `database/migrations/2026_01_01_08000*.php` use MySQL-specific `UPDATE ... JOIN` syntax which is incompatible with SQLite (used by Pest tests). This migration issue needs to be fixed separately. + +### Migration SQLite Compatibility Fix (2026-01-01) + +Fixed MySQL-specific `UPDATE ... JOIN` syntax in three migration files: +- `database/migrations/2026_01_01_080000_add_workspace_id_to_biolink_tables.php` +- `database/migrations/2026_01_01_080001_add_workspace_id_to_analytics_tables.php` +- `database/migrations/2026_01_01_080002_add_workspace_id_to_socialproof_tables.php` + +**Problem:** Raw SQL `UPDATE table JOIN ... SET` is MySQL-specific and fails on SQLite (used by test suite). + +**Solution:** Replaced with Laravel Query Builder using a reusable `migrateTableToWorkspace()` helper method: +```php +private function migrateTableToWorkspace(string $table): void +{ + $records = DB::table("{$table} as t") + ->join('user_workspace as uw', function ($join) { + $join->on('t.user_id', '=', 'uw.user_id') + ->where('uw.is_default', '=', true); + }) + ->whereNull('t.workspace_id') + ->select('t.id', 'uw.workspace_id') + ->get(); + + foreach ($records as $record) { + DB::table($table) + ->where('id', $record->id) + ->update(['workspace_id' => $record->workspace_id]); + } +} +``` + +This approach: +- Uses Laravel Query Builder for database portability +- Works with SQLite (test suite) and MySQL/MariaDB (production) +- Preserves the same migration logic (assigns user's default workspace) +- For biolink_blocks, uses a similar pattern joining to parent biolinks table + +**Remaining test failures** are unrelated to migrations — they're about missing trait methods (`ownedByCurrentWorkspace()`, `belongsToWorkspace()`) and factories on models. These are test infrastructure issues, not migration issues. + +### Model Infrastructure Fix (2026-01-01) by Claude Opus 4.5 (Implementation Agent) + +**Problem Discovered:** Previous agent claimed "BelongsToWorkspace trait already exists" but: +1. The trait existed but models were NOT using it +2. The trait lacked auto-assignment of `workspace_id` on model creation +3. Missing factories for `BioLink` and `AnalyticsWebsite` models + +**Audit Findings:** + +| Model | Had BelongsToWorkspace? | Had workspace()? | Had Factory? | +|-------|------------------------|------------------|--------------| +| `Mod\Social\Models\Account` | No | Yes (duplicate) | Yes | +| `App\Models\BioLink\BioLink` | No | Yes (duplicate) | No | +| `App\Models\Analytics\AnalyticsWebsite` | No | Yes (duplicate) | No | + +**Fixes Applied:** + +1. **Enhanced `BelongsToWorkspace` trait** (`app/Traits/BelongsToWorkspace.php`): + - Added `static::creating()` hook to auto-assign `workspace_id` from current user's default workspace + - The trait already had `scopeOwnedByCurrentWorkspace()` and `belongsToWorkspace()` methods + +2. **Added trait to models:** + - `Mod\Social\Models\Account` — added `use BelongsToWorkspace`, removed duplicate `workspace()` method + - `App\Models\BioLink\BioLink` — added `use BelongsToWorkspace` and `use HasFactory`, removed duplicate `workspace()` method + - `App\Models\Analytics\AnalyticsWebsite` — added `use BelongsToWorkspace` and `use HasFactory`, removed duplicate `workspace()` method + +3. **Created missing factories:** + - `database/factories/BioLink/BioLinkFactory.php` + - `database/factories/Analytics/AnalyticsWebsiteFactory.php` + +4. **Fixed test:** + - `tests/Feature/WorkspaceTenancyTest.php` line 125 — added required `credentials` field to `Account::create()` call + +**Test Results:** +``` +PASS Tests\Feature\WorkspaceTenancyTest + ✓ workspace has relationship methods for all services + ✓ workspace current resolves from authenticated user + ✓ workspace scoping isolates data between workspaces + ✓ workspace relationships return correct models + ✓ models with workspace trait auto assign workspace on create + ✓ workspace scope prevents cross workspace access + ✓ belongs to workspace method checks ownership + + Tests: 7 passed (26 assertions) +``` + +### Remaining Work (2026-01-01) by Claude Opus 4.5 + +The core TASK-004 tests pass (7/7), but 159 other tests fail across the codebase. These failures are NOT bugs in the implementation - they're tests that were written before workspace tenancy and don't set up workspaces. + +**Pattern of failures:** +- Tests create models (AnalyticsWebsite, SocialProofCampaign, etc.) using `Model::create()` without workspace_id +- The BelongsToWorkspace trait auto-assigns workspace only if authenticated user has a workspace +- Tests that don't call `actingAs()` before creating models fail with NOT NULL constraint violations + +**Additional models fixed with trait:** +- `App\Models\Analytics\AnalyticsGoal` — added `use BelongsToWorkspace` +- `App\Models\SocialProof\SocialProofCampaign` — added `use BelongsToWorkspace` + +**Tests fixed:** +- `tests/Feature/Api/AnalyticsApiTest.php` — added workspace setup in beforeEach, added workspace_id to all create() calls + +**Tests that still need fixing (not in TASK-004 scope):** +- `tests/Feature/SocialProof/SocialProofWidgetApiTest.php` +- And approximately 158 other test files + +**Recommendation:** Create a follow-up task TASK-XXX to systematically update all test files to: +1. Create workspaces in `beforeEach()` +2. Attach workspaces to users as default +3. Include workspace_id in all model create() calls, OR call actingAs() before creating models + +### For Verification Agent + +This is a refactoring task. Verify by: +1. Checking relationship methods exist and return correct types +2. Running queries and confirming workspace scoping works +3. Testing cross-workspace isolation +4. Confirming MixPost bridge code is removed +5. Running full test suite (NOTE: 159 tests fail due to missing workspace setup in tests, not implementation bugs) diff --git a/changelog/2026/jan/code-review.md b/changelog/2026/jan/code-review.md new file mode 100644 index 0000000..2412f25 --- /dev/null +++ b/changelog/2026/jan/code-review.md @@ -0,0 +1,123 @@ +# Tenant Module Review + +**Updated:** 2026-01-21 - All implementations verified complete. Rate limiting, deletion logging, query optimisation, soft deletes for WorkspacePackage, and cache invalidation optimisation implemented + +## Overview + +The Tenant module is the core multi-tenancy system for Host Hub, handling: +- **Users and Authentication**: User model, 2FA, API tokens, email verification +- **Workspaces**: The tenant boundary - users belong to workspaces, resources are scoped to workspaces +- **Entitlements**: Feature access control via packages, boosts, and usage tracking +- **Account Management**: Account deletion with grace period, settings +- **Referrals**: Agent referral tracking for the Trees programme + +This is a foundational module that other modules depend on heavily. + +## Production Readiness Score: 92/100 (was 90/100 - cache optimisation and soft deletes added 2026-01-21) + +The module has solid architecture, good test coverage for core functionality, proper security patterns, and configuration externalisation. Critical namespace issues fixed in Wave 2. Cache invalidation now uses efficient version-based approach. WorkspacePackage now has soft deletes for audit history. + +## Critical Issues (Must Fix) + +- [x] **Workspace.packages() uses wrong namespace**: FIXED - Now uses `Mod\Tenant\Models\Package::class` +- [x] **Workspace.bioPages/bioProjects/bioDomains/bioPixels use wrong namespace**: FIXED - 12 namespace replacements from `\App\Models\BioLink\*` to `Mod\Web\Models\*` across DemoTestUserSeeder.php, WorkspaceDetails.php, and WorkspaceManager.php +- [ ] **User boosts relationship may conflict**: User has a `boosts()` relationship but Boost has `workspace_id` as the primary foreign key, not `user_id`. The relationship exists but the intended use case is unclear +- [x] **WorkspaceService.get() bypasses user authorisation**: VERIFIED SECURE - The `get()` method calls `getModel()` which correctly queries through `$user->workspaces()` relationship, ensuring only accessible workspaces are returned. The review item was outdated. + +## Recommended Improvements + +- [x] **Add rate limiting to referral tracking**: DONE - `ReferralController::track()` now has rate limiting applied. +- [x] **Add logging to account deletion confirmation**: DONE - `ConfirmDeletion` Livewire component now logs deletion actions. +- [x] **Cache invalidation in EntitlementService is expensive**: DONE - Refactored to use version-based cache keys. `buildCacheKey()` now includes version in key (e.g., `entitlement:{id}:v{version}:limit:{code}`). Invalidation simply increments the version, making all old keys stale without iteration. +- [x] **WorkspaceManager.setDefault() could be optimised**: DONE - Refactored to use a single optimised query. +- [ ] **Add index hints for UsageRecord queries**: `getTotalUsage()` and `getRollingUsage()` query by workspace_id + feature_code + recorded_at - ensure composite index exists +- [x] **Consider soft deletes for WorkspacePackage**: DONE - Added `SoftDeletes` trait to WorkspacePackage model and migration `2026_01_21_200000_add_soft_deletes_to_workspace_packages.php` for the `deleted_at` column. +- [x] **EntitlementService.getTotalLimit() cache key does not include version**: DONE - Cache keys now include version via `buildCacheKey()` method. All entitlement cache keys use format `entitlement:{id}:v{version}:{type}:{code}`. + +## Missing Features (Future) + +- [ ] **UserStatsService has multiple TODO comments**: Lines 83-93 for social accounts, scheduled posts, and storage usage tracking +- [ ] **Workspace teams/roles beyond owner**: Current pivot only has 'role' but no team management UI or additional role types implemented +- [ ] **Entitlement webhook notifications**: No webhook dispatch when limits are reached or packages change +- [ ] **Usage alerts/notifications**: No mechanism to notify users when approaching limits +- [ ] **Billing cycle reset automation**: `expireCycleBoundBoosts()` exists but no scheduler entry visible +- [ ] **Web routes file missing**: No `Routes/web.php` file exists despite the Boot.php checking for it +- [ ] **Workspace invitation system**: No invite flow for adding users to workspaces + +## Test Coverage Assessment + +**Well Tested (Good Coverage):** +- `EntitlementServiceTest.php` - Comprehensive coverage of can(), recordUsage(), provisionPackage/Boost, suspend/reactivate, revokePackage (600+ lines) +- `AccountDeletionTest.php` - Full lifecycle including model methods, job, and command (300+ lines) +- `WorkspaceTenancyTest.php` - Workspace isolation, scoping, relationships +- `AccessTokenGuardTest.php` - Token authentication, expiry, creation, revocation + +**Tested but Limited:** +- `EntitlementApiTest.php` - Tests API endpoints but uses non-existent package code 'social-creator' (tests may fail) +- `AuthenticationTest.php`, `ProfileTest.php`, `SettingsTest.php` - Exist but not reviewed in detail + +**Missing Test Coverage:** +- [ ] `WorkspaceService` - No dedicated tests +- [ ] `WorkspaceManager` - No dedicated tests +- [ ] `UserStatsService` - No tests +- [ ] `ResolveWorkspaceFromSubdomain` middleware - No tests +- [ ] `ReferralController` - No tests +- [ ] `BelongsToWorkspace` trait - Only integration tests via WorkspaceTenancyTest +- [ ] `TwoFactorAuthenticatable` concern - Test file exists but not reviewed +- [ ] Livewire components (ConfirmDeletion, CancelDeletion, WorkspaceHome) - No dedicated tests + +## Security Concerns + +**Positive Security Patterns:** +- Tokens stored as SHA-256 hashes (UserToken) +- Password re-verification required for account deletion (ConfirmDeletion) +- LIKE pattern escaping in WorkspaceService.findBySubdomain() (SQL injection prevention) +- Sensitive fields ($hidden) on User and Workspace models +- WP connector secret guarded against mass assignment + +**Concerns:** +- [x] **WorkspaceService.get() has no authorisation**: VERIFIED SECURE - Method correctly uses `$user->workspaces()` relationship, not a raw query +- [ ] **ReferralController stores IP in cookie**: IP address stored in JSON cookie - GDPR consideration, should be session-only +- [ ] **No CSRF protection visible for Livewire deletion**: executeDelete() is called via Livewire dispatch - verify CSRF is enforced +- [ ] **Workspace.validateWebhookSignature() timing attack**: Uses hash_equals which is good, but hash_hmac result should also be compared in constant time +- [ ] **User tier bypass for email verification**: Hades users bypass email verification - ensure this is intentional business logic + +## Notes + +### Architecture Observations +- Clean separation between WorkspaceManager (request-scoped operations) and WorkspaceService (session/persistence) +- EntitlementService is well-designed with proper caching, logging, and transaction handling +- Good use of value objects (EntitlementResult) for type safety +- Backward compatibility aliases in Boot.php for migration from old namespace + +### Code Quality +- Consistent use of strict types +- Good docblocks on most public methods +- Uses Laravel conventions (factories, seeders, Pest tests) +- Models have proper casts, fillable/guarded definitions + +### Configuration +- Proper config externalisation in `config/tenant.php` +- Environment variables for cache TTL and grace period +- No hardcoded secrets or credentials found + +### Dual Entitlement Systems +The module has two entitlement approaches that may cause confusion: +1. **UserTier enum** (FREE/APOLLO/HADES) - Used on User model, defines features as simple array +2. **Package/Feature/Boost system** - Used on Workspace model, full database-driven entitlements + +These should be reconciled - currently User.getTier() returns enum-based limits while Workspace.can() uses the database-driven system. + +### Dependencies +The module has relationships to models in other modules: +- `Mod\Analytics\Models\*` +- `Mod\Social\Models\*` +- `Mod\Web\Models\*` +- `Mod\Trust\Models\*` +- `Mod\Notify\Models\*` +- `Mod\Commerce\Models\*` +- `Mod\Trees\Models\*` +- `Mod\Api\Models\*` +- `Mod\Content\Models\*` + +These cross-module dependencies are appropriate for a tenant module but ensure circular dependencies are avoided. diff --git a/changelog/2026/jan/features.md b/changelog/2026/jan/features.md new file mode 100644 index 0000000..6a77180 --- /dev/null +++ b/changelog/2026/jan/features.md @@ -0,0 +1,50 @@ +# Core-Tenant - January 2026 + +## Features Implemented + +### Workspace as Universal Tenant (TASK-003) + +Consolidated tenancy model with workspace as the primary organisational unit. + +**Changes:** +- All workspace_id columns now nullable for system-level entities +- Workspace invitations system +- User tier system (free, pro, hades) +- Namespace system for sub-workspace scoping + +**Models:** +- `Workspace` - enhanced with invitation support +- `WorkspaceInvitation` - new model with notification +- `Namespace` - sub-workspace resource grouping +- `User` - tier management + +**Files:** +- `Models/Workspace.php` +- `Models/WorkspaceInvitation.php` +- `Models/Namespace.php` +- `Notifications/WorkspaceInvitationNotification.php` + +--- + +### Web Routes + +Created `Routes/web.php` with: +- Account deletion flow +- Workspace management routes +- Invitation acceptance + +--- + +### Two-Factor Authentication + +User 2FA support with TOTP. + +**Files:** +- `Models/UserTwoFactorAuth.php` +- Migration for 2FA table + +--- + +### Soft Deletes + +Added soft delete support to User model for GDPR compliance. diff --git a/composer.json b/composer.json index 17a19f1..37fecd0 100644 --- a/composer.json +++ b/composer.json @@ -1,48 +1,48 @@ { - "name": "host-uk/core-tenant", - "description": "Multi-tenancy and workspaces for Laravel", - "keywords": [ - "multi-tenant", - "workspaces", - "teams" - ], - "license": "EUPL-1.2", - "require": { - "php": "^8.2", - "host-uk/core": "dev-main" - }, - "require-dev": { - "laravel/pint": "^1.18", - "orchestra/testbench": "^9.0|^10.0", - "pestphp/pest": "^3.0" - }, - "autoload": { - "psr-4": { - "Core\\Mod\\Tenant\\": "" - } - }, - "autoload-dev": { - "psr-4": { - "Core\\Mod\\Tenant\\Tests\\": "tests/" - } - }, - "extra": { - "laravel": { - "providers": [ - "Core\\Mod\\Tenant\\Boot" - ] - } - }, - "scripts": { - "lint": "pint", - "test": "pest" - }, - "config": { - "sort-packages": true, - "allow-plugins": { - "pestphp/pest-plugin": true - } - }, - "minimum-stability": "dev", - "prefer-stable": true + "name": "host-uk/core-tenant", + "description": "Multi-tenancy and workspaces for Laravel", + "keywords": [ + "multi-tenant", + "workspaces", + "teams" + ], + "license": "EUPL-1.2", + "require": { + "php": "^8.2", + "host-uk/core": "dev-main" + }, + "require-dev": { + "laravel/pint": "^1.18", + "orchestra/testbench": "^9.0|^10.0", + "pestphp/pest": "^3.0" + }, + "autoload": { + "psr-4": { + "Core\\Tenant\\": "" + } + }, + "autoload-dev": { + "psr-4": { + "Core\\Tenant\\Tests\\": "Tests/" + } + }, + "extra": { + "laravel": { + "providers": [ + "Core\\Tenant\\Boot" + ] + } + }, + "scripts": { + "lint": "pint", + "test": "pest" + }, + "config": { + "sort-packages": true, + "allow-plugins": { + "pestphp/pest-plugin": true + } + }, + "minimum-stability": "dev", + "prefer-stable": true } diff --git a/tests/Feature/AccountDeletionTest.php b/tests/Feature/AccountDeletionTest.php index 7d9455b..fcb3737 100644 --- a/tests/Feature/AccountDeletionTest.php +++ b/tests/Feature/AccountDeletionTest.php @@ -2,10 +2,10 @@ declare(strict_types=1); -use Core\Mod\Tenant\Jobs\ProcessAccountDeletion; -use Core\Mod\Tenant\Models\AccountDeletionRequest; -use Core\Mod\Tenant\Models\User; -use Core\Mod\Tenant\Models\Workspace; +use Core\Tenant\Jobs\ProcessAccountDeletion; +use Core\Tenant\Models\AccountDeletionRequest; +use Core\Tenant\Models\User; +use Core\Tenant\Models\Workspace; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Queue; diff --git a/tests/Feature/AuthenticationTest.php b/tests/Feature/AuthenticationTest.php index f165040..d69a762 100644 --- a/tests/Feature/AuthenticationTest.php +++ b/tests/Feature/AuthenticationTest.php @@ -1,8 +1,8 @@ create(); diff --git a/tests/Feature/ProfileTest.php b/tests/Feature/ProfileTest.php index 0c75a4b..53d8111 100644 --- a/tests/Feature/ProfileTest.php +++ b/tests/Feature/ProfileTest.php @@ -1,9 +1,9 @@