fix(tenant): add strict_types and fix PSR-12 compliance across all PHP files
Some checks failed
CI / PHP 8.3 (pull_request) Failing after 3s
CI / PHP 8.4 (pull_request) Failing after 3s

Added declare(strict_types=1) to 27 files that were missing it.
Ran Pint to fix PSR-12 issues (import ordering, operator spacing, brace
positioning) across 33 files.

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Snider 2026-03-17 09:11:54 +00:00
parent 1fec8cea16
commit a24ee4bfa8
52 changed files with 365 additions and 197 deletions

View file

@ -4,10 +4,20 @@ declare(strict_types=1);
namespace Core\Tenant; namespace Core\Tenant;
use App\Services\UserStatsService;
use App\Services\WorkspaceCacheManager;
use App\Services\WorkspaceManager;
use App\Services\WorkspaceService;
use Core\Events\AdminPanelBooting; use Core\Events\AdminPanelBooting;
use Core\Events\ApiRoutesRegistering; use Core\Events\ApiRoutesRegistering;
use Core\Events\ConsoleBooting; use Core\Events\ConsoleBooting;
use Core\Events\WebRoutesRegistering; use Core\Events\WebRoutesRegistering;
use Core\Tenant\Contracts\TwoFactorAuthenticationProvider;
use Core\Tenant\Services\EntitlementService;
use Core\Tenant\Services\EntitlementWebhookService;
use Core\Tenant\Services\TotpService;
use Core\Tenant\Services\UsageAlertService;
use Core\Tenant\Services\WorkspaceTeamService;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
@ -40,48 +50,48 @@ class Boot extends ServiceProvider
public function register(): void public function register(): void
{ {
$this->app->singleton( $this->app->singleton(
\Core\Tenant\Contracts\TwoFactorAuthenticationProvider::class, TwoFactorAuthenticationProvider::class,
\Core\Tenant\Services\TotpService::class TotpService::class
); );
$this->app->singleton( $this->app->singleton(
\Core\Tenant\Services\EntitlementService::class, EntitlementService::class,
\Core\Tenant\Services\EntitlementService::class EntitlementService::class
); );
$this->app->singleton( $this->app->singleton(
\Core\Tenant\Services\WorkspaceManager::class, Services\WorkspaceManager::class,
\Core\Tenant\Services\WorkspaceManager::class Services\WorkspaceManager::class
); );
$this->app->singleton( $this->app->singleton(
\Core\Tenant\Services\UserStatsService::class, Services\UserStatsService::class,
\Core\Tenant\Services\UserStatsService::class Services\UserStatsService::class
); );
$this->app->singleton( $this->app->singleton(
\Core\Tenant\Services\WorkspaceService::class, Services\WorkspaceService::class,
\Core\Tenant\Services\WorkspaceService::class Services\WorkspaceService::class
); );
$this->app->singleton( $this->app->singleton(
\Core\Tenant\Services\WorkspaceCacheManager::class, Services\WorkspaceCacheManager::class,
\Core\Tenant\Services\WorkspaceCacheManager::class Services\WorkspaceCacheManager::class
); );
$this->app->singleton( $this->app->singleton(
\Core\Tenant\Services\UsageAlertService::class, UsageAlertService::class,
\Core\Tenant\Services\UsageAlertService::class UsageAlertService::class
); );
$this->app->singleton( $this->app->singleton(
\Core\Tenant\Services\EntitlementWebhookService::class, EntitlementWebhookService::class,
\Core\Tenant\Services\EntitlementWebhookService::class EntitlementWebhookService::class
); );
$this->app->singleton( $this->app->singleton(
\Core\Tenant\Services\WorkspaceTeamService::class, WorkspaceTeamService::class,
\Core\Tenant\Services\WorkspaceTeamService::class WorkspaceTeamService::class
); );
$this->registerBackwardCompatAliases(); $this->registerBackwardCompatAliases();
@ -89,31 +99,31 @@ class Boot extends ServiceProvider
protected function registerBackwardCompatAliases(): void protected function registerBackwardCompatAliases(): void
{ {
if (! class_exists(\App\Services\WorkspaceManager::class)) { if (! class_exists(WorkspaceManager::class)) {
class_alias( class_alias(
\Core\Tenant\Services\WorkspaceManager::class, Services\WorkspaceManager::class,
\App\Services\WorkspaceManager::class WorkspaceManager::class
); );
} }
if (! class_exists(\App\Services\UserStatsService::class)) { if (! class_exists(UserStatsService::class)) {
class_alias( class_alias(
\Core\Tenant\Services\UserStatsService::class, Services\UserStatsService::class,
\App\Services\UserStatsService::class UserStatsService::class
); );
} }
if (! class_exists(\App\Services\WorkspaceService::class)) { if (! class_exists(WorkspaceService::class)) {
class_alias( class_alias(
\Core\Tenant\Services\WorkspaceService::class, Services\WorkspaceService::class,
\App\Services\WorkspaceService::class WorkspaceService::class
); );
} }
if (! class_exists(\App\Services\WorkspaceCacheManager::class)) { if (! class_exists(WorkspaceCacheManager::class)) {
class_alias( class_alias(
\Core\Tenant\Services\WorkspaceCacheManager::class, Services\WorkspaceCacheManager::class,
\App\Services\WorkspaceCacheManager::class WorkspaceCacheManager::class
); );
} }
} }

View file

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Core\Tenant\Controllers; namespace Core\Tenant\Controllers;
use Core\Front\Controller;
use Core\Helpers\PrivacyHelper; use Core\Helpers\PrivacyHelper;
use Core\Mod\Trees\Models\TreePlanting; use Core\Mod\Trees\Models\TreePlanting;
use Core\Mod\Trees\Models\TreePlantingStats; use Core\Mod\Trees\Models\TreePlantingStats;
@ -21,7 +22,7 @@ use Illuminate\Support\Facades\Cookie;
* *
* On signup, PlantTreeForAgentReferral listener plants a tree for the referrer. * On signup, PlantTreeForAgentReferral listener plants a tree for the referrer.
*/ */
class ReferralController extends \Core\Front\Controller class ReferralController extends Controller
{ {
/** /**
* Cookie name for agent referral tracking. * Cookie name for agent referral tracking.

View file

@ -9,6 +9,8 @@ use Core\Tenant\Models\User;
use Core\Tenant\Models\Workspace; use Core\Tenant\Models\Workspace;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Mod\Api\Controllers\Concerns\HasApiResponses; use Mod\Api\Controllers\Concerns\HasApiResponses;
use Mod\Api\Controllers\Concerns\ResolvesWorkspace; use Mod\Api\Controllers\Concerns\ResolvesWorkspace;
use Mod\Api\Resources\PaginatedCollection; use Mod\Api\Resources\PaginatedCollection;
@ -133,7 +135,7 @@ class WorkspaceController extends Controller
// Generate slug if not provided // Generate slug if not provided
if (empty($validated['slug'])) { if (empty($validated['slug'])) {
$validated['slug'] = \Illuminate\Support\Str::slug($validated['name']).'-'.\Illuminate\Support\Str::random(6); $validated['slug'] = Str::slug($validated['name']).'-'.Str::random(6);
} }
// Set default domain // Set default domain
@ -252,9 +254,9 @@ class WorkspaceController extends Controller
// Use a single transaction with optimised query: // Use a single transaction with optimised query:
// Clear all defaults and set the new one in one operation using raw update // Clear all defaults and set the new one in one operation using raw update
\Illuminate\Support\Facades\DB::transaction(function () use ($user, $workspace) { DB::transaction(function () use ($user, $workspace) {
// Clear all existing defaults for this user's hub workspaces // Clear all existing defaults for this user's hub workspaces
\Illuminate\Support\Facades\DB::table('user_workspace') DB::table('user_workspace')
->where('user_id', $user->id) ->where('user_id', $user->id)
->whereIn('workspace_id', function ($query) { ->whereIn('workspace_id', function ($query) {
$query->select('id') $query->select('id')
@ -264,7 +266,7 @@ class WorkspaceController extends Controller
->update(['is_default' => false]); ->update(['is_default' => false]);
// Set the new default // Set the new default
\Illuminate\Support\Facades\DB::table('user_workspace') DB::table('user_workspace')
->where('user_id', $user->id) ->where('user_id', $user->id)
->where('workspace_id', $workspace->id) ->where('workspace_id', $workspace->id)
->update(['is_default' => true]); ->update(['is_default' => true]);

View file

@ -1,13 +1,16 @@
<?php <?php
declare(strict_types=1);
namespace Core\Tenant\Database\Factories; namespace Core\Tenant\Database\Factories;
use Core\Tenant\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str; use Illuminate\Support\Str;
/** /**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\Core\Tenant\Models\User> * @extends Factory<User>
*/ */
class UserFactory extends Factory class UserFactory extends Factory
{ {
@ -17,7 +20,7 @@ class UserFactory extends Factory
* Uses the backward-compatible alias class to ensure type compatibility * Uses the backward-compatible alias class to ensure type compatibility
* with existing code that expects Mod\Tenant\Models\User. * with existing code that expects Mod\Tenant\Models\User.
*/ */
protected $model = \Core\Tenant\Models\User::class; protected $model = User::class;
/** /**
* The current password being used by the factory. * The current password being used by the factory.

View file

@ -8,7 +8,7 @@ use Core\Tenant\Models\WaitlistEntry;
use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Database\Eloquent\Factories\Factory;
/** /**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\Core\Tenant\Models\WaitlistEntry> * @extends Factory<WaitlistEntry>
*/ */
class WaitlistEntryFactory extends Factory class WaitlistEntryFactory extends Factory
{ {

View file

@ -1,12 +1,14 @@
<?php <?php
declare(strict_types=1);
namespace Core\Tenant\Database\Factories; namespace Core\Tenant\Database\Factories;
use Core\Tenant\Models\Workspace; use Core\Tenant\Models\Workspace;
use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Database\Eloquent\Factories\Factory;
/** /**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\Core\Tenant\Models\Workspace> * @extends Factory<Workspace>
*/ */
class WorkspaceFactory extends Factory class WorkspaceFactory extends Factory
{ {

View file

@ -9,7 +9,7 @@ use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str; use Illuminate\Support\Str;
/** /**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\Core\Tenant\Models\WorkspaceInvitation> * @extends Factory<WorkspaceInvitation>
*/ */
class WorkspaceInvitationFactory extends Factory class WorkspaceInvitationFactory extends Factory
{ {

View file

@ -1,7 +1,10 @@
<?php <?php
declare(strict_types=1);
namespace Core\Tenant\Database\Seeders; namespace Core\Tenant\Database\Seeders;
use Core\Mod\Web\Models\Page;
use Core\Tenant\Models\Package; use Core\Tenant\Models\Package;
use Core\Tenant\Models\User; use Core\Tenant\Models\User;
use Core\Tenant\Models\Workspace; use Core\Tenant\Models\Workspace;
@ -94,11 +97,11 @@ class DemoTestUserSeeder extends Seeder
protected function createTestBioPage(Workspace $workspace, User $user): void protected function createTestBioPage(Workspace $workspace, User $user): void
{ {
// Only create if Web Page model exists and no test page exists // Only create if Web Page model exists and no test page exists
if (! class_exists(\Core\Mod\Web\Models\Page::class)) { if (! class_exists(Page::class)) {
return; return;
} }
$existingPage = \Core\Mod\Web\Models\Page::where('workspace_id', $workspace->id) $existingPage = Page::where('workspace_id', $workspace->id)
->where('url', 'nyx-test') ->where('url', 'nyx-test')
->first(); ->first();
@ -106,7 +109,7 @@ class DemoTestUserSeeder extends Seeder
return; return;
} }
\Core\Mod\Web\Models\Page::create([ Page::create([
'workspace_id' => $workspace->id, 'workspace_id' => $workspace->id,
'user_id' => $user->id, 'user_id' => $user->id,
'url' => 'nyx-test', 'url' => 'nyx-test',
@ -146,11 +149,11 @@ class DemoTestUserSeeder extends Seeder
protected function createTestShortLink(Workspace $workspace, User $user): void protected function createTestShortLink(Workspace $workspace, User $user): void
{ {
// Only create if Web Page model exists // Only create if Web Page model exists
if (! class_exists(\Core\Mod\Web\Models\Page::class)) { if (! class_exists(Page::class)) {
return; return;
} }
$existingLink = \Core\Mod\Web\Models\Page::where('workspace_id', $workspace->id) $existingLink = Page::where('workspace_id', $workspace->id)
->where('url', 'nyx-short') ->where('url', 'nyx-short')
->first(); ->first();
@ -158,7 +161,7 @@ class DemoTestUserSeeder extends Seeder
return; return;
} }
\Core\Mod\Web\Models\Page::create([ Page::create([
'workspace_id' => $workspace->id, 'workspace_id' => $workspace->id,
'user_id' => $user->id, 'user_id' => $user->id,
'url' => 'nyx-short', 'url' => 'nyx-short',

View file

@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace Core\Tenant\Database\Seeders; namespace Core\Tenant\Database\Seeders;
use Core\Tenant\Models\Feature; use Core\Tenant\Models\Feature;

View file

@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace Core\Tenant\Database\Seeders; namespace Core\Tenant\Database\Seeders;
use Core\Tenant\Models\Feature; use Core\Tenant\Models\Feature;

View file

@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace Core\Tenant\Database\Seeders; namespace Core\Tenant\Database\Seeders;
use Core\Tenant\Models\Package; use Core\Tenant\Models\Package;

View file

@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace Core\Tenant\Database\Seeders; namespace Core\Tenant\Database\Seeders;
use Core\Tenant\Enums\UserTier; use Core\Tenant\Enums\UserTier;

View file

@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace Core\Tenant\Enums; namespace Core\Tenant\Enums;
enum UserTier: string enum UserTier: string

View file

@ -8,6 +8,7 @@ use Core\Tenant\Models\AccountDeletionRequest;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable; use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Attachment;
use Illuminate\Mail\Mailables\Content; use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope; use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
@ -53,7 +54,7 @@ class AccountDeletionRequested extends Mailable implements ShouldQueue
/** /**
* Get the attachments for the message. * Get the attachments for the message.
* *
* @return array<int, \Illuminate\Mail\Mailables\Attachment> * @return array<int, Attachment>
*/ */
public function attachments(): array public function attachments(): array
{ {

View file

@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace Core\Tenant\Models; namespace Core\Tenant\Models;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;

View file

@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace Core\Tenant\Models; namespace Core\Tenant\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;

View file

@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace Core\Tenant\Models; namespace Core\Tenant\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;

View file

@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace Core\Tenant\Models; namespace Core\Tenant\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;

View file

@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace Core\Tenant\Models; namespace Core\Tenant\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;

View file

@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace Core\Tenant\Models; namespace Core\Tenant\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;

View file

@ -4,8 +4,11 @@ declare(strict_types=1);
namespace Core\Tenant\Models; namespace Core\Tenant\Models;
use Core\Tenant\Database\Factories\UserFactory;
use Core\Tenant\Enums\UserTier; use Core\Tenant\Enums\UserTier;
use Illuminate\Auth\Notifications\VerifyEmail;
use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\BelongsToMany;
@ -22,9 +25,9 @@ class User extends Authenticatable implements MustVerifyEmail
/** /**
* Create a new factory instance for the model. * Create a new factory instance for the model.
*/ */
protected static function newFactory(): \Core\Tenant\Database\Factories\UserFactory protected static function newFactory(): UserFactory
{ {
return \Core\Tenant\Database\Factories\UserFactory::new(); return UserFactory::new();
} }
/** /**
@ -235,7 +238,7 @@ class User extends Authenticatable implements MustVerifyEmail
/** /**
* Get all namespaces accessible by this user (owned + via workspaces). * Get all namespaces accessible by this user (owned + via workspaces).
*/ */
public function accessibleNamespaces(): \Illuminate\Database\Eloquent\Builder public function accessibleNamespaces(): Builder
{ {
return Namespace_::accessibleBy($this); return Namespace_::accessibleBy($this);
} }
@ -269,7 +272,7 @@ class User extends Authenticatable implements MustVerifyEmail
*/ */
public function sendEmailVerificationNotification(): void public function sendEmailVerificationNotification(): void
{ {
$this->notify(new \Illuminate\Auth\Notifications\VerifyEmail); $this->notify(new VerifyEmail);
} }
/** /**

View file

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Core\Tenant\Models; namespace Core\Tenant\Models;
use Core\Tenant\Database\Factories\UserTokenFactory;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
@ -18,9 +19,9 @@ class UserToken extends Model
{ {
use HasFactory; use HasFactory;
protected static function newFactory(): \Core\Tenant\Database\Factories\UserTokenFactory protected static function newFactory(): UserTokenFactory
{ {
return \Core\Tenant\Database\Factories\UserTokenFactory::new(); return UserTokenFactory::new();
} }
/** /**

View file

@ -1,7 +1,10 @@
<?php <?php
declare(strict_types=1);
namespace Core\Tenant\Models; namespace Core\Tenant\Models;
use Core\Tenant\Database\Factories\WaitlistEntryFactory;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
@ -13,9 +16,9 @@ class WaitlistEntry extends Model
use HasFactory; use HasFactory;
use Notifiable; use Notifiable;
protected static function newFactory(): \Core\Tenant\Database\Factories\WaitlistEntryFactory protected static function newFactory(): WaitlistEntryFactory
{ {
return \Core\Tenant\Database\Factories\WaitlistEntryFactory::new(); return WaitlistEntryFactory::new();
} }
protected $fillable = [ protected $fillable = [

View file

@ -4,6 +4,39 @@ declare(strict_types=1);
namespace Core\Tenant\Models; namespace Core\Tenant\Models;
use App\Services\WorkspaceService;
use Core\Mod\Analytics\Models\AnalyticsGoal;
use Core\Mod\Analytics\Models\AnalyticsWebsite;
use Core\Mod\Analytics\Models\Goal;
use Core\Mod\Analytics\Models\Website;
use Core\Mod\Api\Models\ApiKey;
use Core\Mod\Api\Models\WebhookEndpoint;
use Core\Mod\Content\Models\ContentAuthor;
use Core\Mod\Content\Models\ContentItem;
use Core\Mod\Notify\Models\PushCampaign;
use Core\Mod\Notify\Models\PushFlow;
use Core\Mod\Notify\Models\PushSegment;
use Core\Mod\Notify\Models\PushWebsite;
use Core\Mod\Social\Models\Account;
use Core\Mod\Social\Models\Analytics;
use Core\Mod\Social\Models\Audience;
use Core\Mod\Social\Models\FacebookInsight;
use Core\Mod\Social\Models\HashtagGroup;
use Core\Mod\Social\Models\ImportedPost;
use Core\Mod\Social\Models\InstagramInsight;
use Core\Mod\Social\Models\Media;
use Core\Mod\Social\Models\Metric;
use Core\Mod\Social\Models\PinterestAnalytic;
use Core\Mod\Social\Models\Post;
use Core\Mod\Social\Models\PostingSchedule;
use Core\Mod\Social\Models\Template;
use Core\Mod\Social\Models\Variable;
use Core\Mod\Social\Models\Webhook;
use Core\Mod\Trees\Models\TreePlanting;
use Core\Mod\Trust\Models\Campaign;
use Core\Mod\Trust\Models\Notification;
use Core\Tenant\Database\Factories\WorkspaceFactory;
use Core\Tenant\Notifications\WorkspaceInvitationNotification;
use Core\Tenant\Services\EntitlementResult; use Core\Tenant\Services\EntitlementResult;
use Core\Tenant\Services\EntitlementService; use Core\Tenant\Services\EntitlementService;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
@ -11,14 +44,19 @@ use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphMany; use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Support\Collection;
use Mod\Commerce\Models\Invoice;
use Mod\Commerce\Models\Order;
use Mod\Commerce\Models\PaymentMethod;
use Mod\Commerce\Models\Subscription;
class Workspace extends Model class Workspace extends Model
{ {
use HasFactory; use HasFactory;
protected static function newFactory(): \Core\Tenant\Database\Factories\WorkspaceFactory protected static function newFactory(): WorkspaceFactory
{ {
return \Core\Tenant\Database\Factories\WorkspaceFactory::new(); return WorkspaceFactory::new();
} }
protected $fillable = [ protected $fillable = [
@ -235,7 +273,7 @@ class Workspace extends Model
*/ */
public function socialAccounts(): HasMany public function socialAccounts(): HasMany
{ {
return $this->hasMany(\Core\Mod\Social\Models\Account::class); return $this->hasMany(Account::class);
} }
/** /**
@ -243,7 +281,7 @@ class Workspace extends Model
*/ */
public function socialPosts(): HasMany public function socialPosts(): HasMany
{ {
return $this->hasMany(\Core\Mod\Social\Models\Post::class); return $this->hasMany(Post::class);
} }
/** /**
@ -251,7 +289,7 @@ class Workspace extends Model
*/ */
public function socialTemplates(): HasMany public function socialTemplates(): HasMany
{ {
return $this->hasMany(\Core\Mod\Social\Models\Template::class); return $this->hasMany(Template::class);
} }
/** /**
@ -259,7 +297,7 @@ class Workspace extends Model
*/ */
public function socialMedia(): HasMany public function socialMedia(): HasMany
{ {
return $this->hasMany(\Core\Mod\Social\Models\Media::class); return $this->hasMany(Media::class);
} }
/** /**
@ -267,7 +305,7 @@ class Workspace extends Model
*/ */
public function socialHashtagGroups(): HasMany public function socialHashtagGroups(): HasMany
{ {
return $this->hasMany(\Core\Mod\Social\Models\HashtagGroup::class); return $this->hasMany(HashtagGroup::class);
} }
/** /**
@ -275,7 +313,7 @@ class Workspace extends Model
*/ */
public function socialWebhooks(): HasMany public function socialWebhooks(): HasMany
{ {
return $this->hasMany(\Core\Mod\Social\Models\Webhook::class); return $this->hasMany(Webhook::class);
} }
/** /**
@ -283,7 +321,7 @@ class Workspace extends Model
*/ */
public function socialAnalytics(): HasMany public function socialAnalytics(): HasMany
{ {
return $this->hasMany(\Core\Mod\Social\Models\Analytics::class); return $this->hasMany(Analytics::class);
} }
/** /**
@ -291,7 +329,7 @@ class Workspace extends Model
*/ */
public function socialVariables(): HasMany public function socialVariables(): HasMany
{ {
return $this->hasMany(\Core\Mod\Social\Models\Variable::class); return $this->hasMany(Variable::class);
} }
/** /**
@ -299,7 +337,7 @@ class Workspace extends Model
*/ */
public function socialPostingSchedule(): HasMany public function socialPostingSchedule(): HasMany
{ {
return $this->hasMany(\Core\Mod\Social\Models\PostingSchedule::class); return $this->hasMany(PostingSchedule::class);
} }
/** /**
@ -307,7 +345,7 @@ class Workspace extends Model
*/ */
public function socialImportedPosts(): HasMany public function socialImportedPosts(): HasMany
{ {
return $this->hasMany(\Core\Mod\Social\Models\ImportedPost::class); return $this->hasMany(ImportedPost::class);
} }
/** /**
@ -315,7 +353,7 @@ class Workspace extends Model
*/ */
public function socialMetrics(): HasMany public function socialMetrics(): HasMany
{ {
return $this->hasMany(\Core\Mod\Social\Models\Metric::class); return $this->hasMany(Metric::class);
} }
/** /**
@ -323,7 +361,7 @@ class Workspace extends Model
*/ */
public function socialAudience(): HasMany public function socialAudience(): HasMany
{ {
return $this->hasMany(\Core\Mod\Social\Models\Audience::class); return $this->hasMany(Audience::class);
} }
/** /**
@ -331,7 +369,7 @@ class Workspace extends Model
*/ */
public function socialFacebookInsights(): HasMany public function socialFacebookInsights(): HasMany
{ {
return $this->hasMany(\Core\Mod\Social\Models\FacebookInsight::class); return $this->hasMany(FacebookInsight::class);
} }
/** /**
@ -339,7 +377,7 @@ class Workspace extends Model
*/ */
public function socialInstagramInsights(): HasMany public function socialInstagramInsights(): HasMany
{ {
return $this->hasMany(\Core\Mod\Social\Models\InstagramInsight::class); return $this->hasMany(InstagramInsight::class);
} }
/** /**
@ -347,7 +385,7 @@ class Workspace extends Model
*/ */
public function socialPinterestAnalytics(): HasMany public function socialPinterestAnalytics(): HasMany
{ {
return $this->hasMany(\Core\Mod\Social\Models\PinterestAnalytic::class); return $this->hasMany(PinterestAnalytic::class);
} }
/** /**
@ -376,7 +414,7 @@ class Workspace extends Model
*/ */
public function analyticsSites(): HasMany public function analyticsSites(): HasMany
{ {
return $this->hasMany(\Core\Mod\Analytics\Models\Website::class); return $this->hasMany(Website::class);
} }
/** /**
@ -384,7 +422,7 @@ class Workspace extends Model
*/ */
public function socialAnalyticsWebsites(): HasMany public function socialAnalyticsWebsites(): HasMany
{ {
return $this->hasMany(\Core\Mod\Analytics\Models\AnalyticsWebsite::class); return $this->hasMany(AnalyticsWebsite::class);
} }
/** /**
@ -392,7 +430,7 @@ class Workspace extends Model
*/ */
public function analyticsGoals(): HasMany public function analyticsGoals(): HasMany
{ {
return $this->hasMany(\Core\Mod\Analytics\Models\Goal::class); return $this->hasMany(Goal::class);
} }
/** /**
@ -400,7 +438,7 @@ class Workspace extends Model
*/ */
public function socialAnalyticsGoals(): HasMany public function socialAnalyticsGoals(): HasMany
{ {
return $this->hasMany(\Core\Mod\Analytics\Models\AnalyticsGoal::class); return $this->hasMany(AnalyticsGoal::class);
} }
// TrustHost Relationships // TrustHost Relationships
@ -410,7 +448,7 @@ class Workspace extends Model
*/ */
public function trustWidgets(): HasMany public function trustWidgets(): HasMany
{ {
return $this->hasMany(\Core\Mod\Trust\Models\Campaign::class); return $this->hasMany(Campaign::class);
} }
/** /**
@ -418,7 +456,7 @@ class Workspace extends Model
*/ */
public function trustNotifications(): HasMany public function trustNotifications(): HasMany
{ {
return $this->hasMany(\Core\Mod\Trust\Models\Notification::class); return $this->hasMany(Notification::class);
} }
// NotifyHost Relationships // NotifyHost Relationships
@ -428,7 +466,7 @@ class Workspace extends Model
*/ */
public function notificationSites(): HasMany public function notificationSites(): HasMany
{ {
return $this->hasMany(\Core\Mod\Notify\Models\PushWebsite::class); return $this->hasMany(PushWebsite::class);
} }
/** /**
@ -436,7 +474,7 @@ class Workspace extends Model
*/ */
public function pushCampaigns(): HasMany public function pushCampaigns(): HasMany
{ {
return $this->hasMany(\Core\Mod\Notify\Models\PushCampaign::class); return $this->hasMany(PushCampaign::class);
} }
/** /**
@ -444,7 +482,7 @@ class Workspace extends Model
*/ */
public function pushFlows(): HasMany public function pushFlows(): HasMany
{ {
return $this->hasMany(\Core\Mod\Notify\Models\PushFlow::class); return $this->hasMany(PushFlow::class);
} }
/** /**
@ -452,7 +490,7 @@ class Workspace extends Model
*/ */
public function pushSegments(): HasMany public function pushSegments(): HasMany
{ {
return $this->hasMany(\Core\Mod\Notify\Models\PushSegment::class); return $this->hasMany(PushSegment::class);
} }
// API & Webhooks Relationships // API & Webhooks Relationships
@ -462,7 +500,7 @@ class Workspace extends Model
*/ */
public function apiKeys(): HasMany public function apiKeys(): HasMany
{ {
return $this->hasMany(\Core\Mod\Api\Models\ApiKey::class); return $this->hasMany(ApiKey::class);
} }
/** /**
@ -470,7 +508,7 @@ class Workspace extends Model
*/ */
public function webhookEndpoints(): HasMany public function webhookEndpoints(): HasMany
{ {
return $this->hasMany(\Core\Mod\Api\Models\WebhookEndpoint::class); return $this->hasMany(WebhookEndpoint::class);
} }
/** /**
@ -488,7 +526,7 @@ class Workspace extends Model
*/ */
public function treePlantings(): HasMany public function treePlantings(): HasMany
{ {
return $this->hasMany(\Core\Mod\Trees\Models\TreePlanting::class); return $this->hasMany(TreePlanting::class);
} }
/** /**
@ -519,7 +557,7 @@ class Workspace extends Model
*/ */
public function contentItems(): HasMany public function contentItems(): HasMany
{ {
return $this->hasMany(\Core\Mod\Content\Models\ContentItem::class); return $this->hasMany(ContentItem::class);
} }
/** /**
@ -527,7 +565,7 @@ class Workspace extends Model
*/ */
public function contentAuthors(): HasMany public function contentAuthors(): HasMany
{ {
return $this->hasMany(\Core\Mod\Content\Models\ContentAuthor::class); return $this->hasMany(ContentAuthor::class);
} }
// Commerce Relationships (defined in app Mod\Commerce) // Commerce Relationships (defined in app Mod\Commerce)
@ -537,7 +575,7 @@ class Workspace extends Model
*/ */
public function subscriptions(): HasMany public function subscriptions(): HasMany
{ {
return $this->hasMany(\Mod\Commerce\Models\Subscription::class); return $this->hasMany(Subscription::class);
} }
/** /**
@ -545,7 +583,7 @@ class Workspace extends Model
*/ */
public function invoices(): HasMany public function invoices(): HasMany
{ {
return $this->hasMany(\Mod\Commerce\Models\Invoice::class); return $this->hasMany(Invoice::class);
} }
/** /**
@ -553,7 +591,7 @@ class Workspace extends Model
*/ */
public function paymentMethods(): HasMany public function paymentMethods(): HasMany
{ {
return $this->hasMany(\Mod\Commerce\Models\PaymentMethod::class); return $this->hasMany(PaymentMethod::class);
} }
/** /**
@ -561,7 +599,7 @@ class Workspace extends Model
*/ */
public function orders(): MorphMany public function orders(): MorphMany
{ {
return $this->morphMany(\Mod\Commerce\Models\Order::class, 'orderable'); return $this->morphMany(Order::class, 'orderable');
} }
// Helper Methods // Helper Methods
@ -579,12 +617,12 @@ class Workspace extends Model
} }
// Try to get from authenticated user's default workspace // Try to get from authenticated user's default workspace
if (auth()->check() && auth()->user() instanceof \Core\Tenant\Models\User) { if (auth()->check() && auth()->user() instanceof User) {
return auth()->user()->defaultHostWorkspace(); return auth()->user()->defaultHostWorkspace();
} }
// Try to resolve from subdomain via WorkspaceService // Try to resolve from subdomain via WorkspaceService
$workspaceService = app(\App\Services\WorkspaceService::class); $workspaceService = app(WorkspaceService::class);
$slug = $workspaceService->currentSlug(); $slug = $workspaceService->currentSlug();
return static::where('slug', $slug)->first(); return static::where('slug', $slug)->first();
@ -609,7 +647,7 @@ class Workspace extends Model
/** /**
* Get usage summary for all features. * Get usage summary for all features.
*/ */
public function getUsageSummary(): \Illuminate\Support\Collection public function getUsageSummary(): Collection
{ {
return app(EntitlementService::class)->getUsageSummary($this); return app(EntitlementService::class)->getUsageSummary($this);
} }
@ -675,7 +713,7 @@ class Workspace extends Model
$existing->save(); $existing->save();
// Send notification with the new plaintext token // Send notification with the new plaintext token
$existing->notify(new \Core\Tenant\Notifications\WorkspaceInvitationNotification($existing, $plaintextToken)); $existing->notify(new WorkspaceInvitationNotification($existing, $plaintextToken));
return $existing; return $existing;
} }
@ -693,7 +731,7 @@ class Workspace extends Model
]); ]);
// Send notification with the plaintext token (not the hashed one) // Send notification with the plaintext token (not the hashed one)
$invitation->notify(new \Core\Tenant\Notifications\WorkspaceInvitationNotification($invitation, $plaintextToken)); $invitation->notify(new WorkspaceInvitationNotification($invitation, $plaintextToken));
return $invitation; return $invitation;
} }

View file

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Core\Tenant\Models; namespace Core\Tenant\Models;
use Core\Tenant\Database\Factories\WorkspaceInvitationFactory;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
@ -16,9 +17,9 @@ class WorkspaceInvitation extends Model
use HasFactory; use HasFactory;
use Notifiable; use Notifiable;
protected static function newFactory(): \Core\Tenant\Database\Factories\WorkspaceInvitationFactory protected static function newFactory(): WorkspaceInvitationFactory
{ {
return \Core\Tenant\Database\Factories\WorkspaceInvitationFactory::new(); return WorkspaceInvitationFactory::new();
} }
protected $fillable = [ protected $fillable = [

View file

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Core\Tenant\Models; namespace Core\Tenant\Models;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
@ -20,10 +21,10 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
* @property int|null $team_id * @property int|null $team_id
* @property array|null $custom_permissions * @property array|null $custom_permissions
* @property bool $is_default * @property bool $is_default
* @property \Carbon\Carbon|null $joined_at * @property Carbon|null $joined_at
* @property int|null $invited_by * @property int|null $invited_by
* @property \Carbon\Carbon $created_at * @property Carbon $created_at
* @property \Carbon\Carbon $updated_at * @property Carbon $updated_at
*/ */
class WorkspaceMember extends Model class WorkspaceMember extends Model
{ {

View file

@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace Core\Tenant\Models; namespace Core\Tenant\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;

View file

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Core\Tenant\Models; namespace Core\Tenant\Models;
use Carbon\Carbon;
use Core\Tenant\Concerns\BelongsToWorkspace; use Core\Tenant\Concerns\BelongsToWorkspace;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
@ -26,8 +27,8 @@ use Illuminate\Support\Str;
* @property bool $is_system * @property bool $is_system
* @property string $colour * @property string $colour
* @property int $sort_order * @property int $sort_order
* @property \Carbon\Carbon $created_at * @property Carbon $created_at
* @property \Carbon\Carbon $updated_at * @property Carbon $updated_at
*/ */
class WorkspaceTeam extends Model class WorkspaceTeam extends Model
{ {

View file

@ -2,6 +2,8 @@
declare(strict_types=1); declare(strict_types=1);
use Core\Tenant\View\Modal\Admin\MemberManager;
use Core\Tenant\View\Modal\Admin\TeamManager;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
/* /*
@ -15,10 +17,10 @@ use Illuminate\Support\Facades\Route;
Route::middleware(['web', 'auth', 'admin.domain'])->prefix('admin/tenant')->name('hub.admin.tenant.')->group(function () { Route::middleware(['web', 'auth', 'admin.domain'])->prefix('admin/tenant')->name('hub.admin.tenant.')->group(function () {
// Team Manager // Team Manager
Route::get('/teams', \Core\Tenant\View\Modal\Admin\TeamManager::class) Route::get('/teams', TeamManager::class)
->name('teams'); ->name('teams');
// Member Manager // Member Manager
Route::get('/members', \Core\Tenant\View\Modal\Admin\MemberManager::class) Route::get('/members', MemberManager::class)
->name('members'); ->name('members');
}); });

View file

@ -8,6 +8,7 @@ declare(strict_types=1);
* Account management and workspace routes. * Account management and workspace routes.
*/ */
use Core\Tenant\Controllers\WorkspaceInvitationController;
use Core\Tenant\View\Modal\Web\CancelDeletion; use Core\Tenant\View\Modal\Web\CancelDeletion;
use Core\Tenant\View\Modal\Web\ConfirmDeletion; use Core\Tenant\View\Modal\Web\ConfirmDeletion;
use Core\Tenant\View\Modal\Web\WorkspaceHome; use Core\Tenant\View\Modal\Web\WorkspaceHome;
@ -41,7 +42,7 @@ Route::prefix('account')->name('account.')->group(function () {
| |
*/ */
Route::get('/workspace/invitation/{token}', \Core\Tenant\Controllers\WorkspaceInvitationController::class) Route::get('/workspace/invitation/{token}', WorkspaceInvitationController::class)
->name('workspace.invitation.accept'); ->name('workspace.invitation.accept');
/* /*

View file

@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace Core\Tenant\Services; namespace Core\Tenant\Services;
/** /**

View file

@ -17,6 +17,7 @@ use Core\Tenant\Jobs\DispatchEntitlementWebhook;
use Core\Tenant\Models\EntitlementWebhook; use Core\Tenant\Models\EntitlementWebhook;
use Core\Tenant\Models\EntitlementWebhookDelivery; use Core\Tenant\Models\EntitlementWebhookDelivery;
use Core\Tenant\Models\Workspace; use Core\Tenant\Models\Workspace;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str; use Illuminate\Support\Str;
@ -425,7 +426,7 @@ class EntitlementWebhookService
/** /**
* Get webhooks for a workspace. * Get webhooks for a workspace.
*/ */
public function getWebhooksForWorkspace(Workspace $workspace): \Illuminate\Database\Eloquent\Collection public function getWebhooksForWorkspace(Workspace $workspace): Collection
{ {
return EntitlementWebhook::query() return EntitlementWebhook::query()
->forWorkspace($workspace) ->forWorkspace($workspace)
@ -437,7 +438,7 @@ class EntitlementWebhookService
/** /**
* Get delivery history for a webhook. * Get delivery history for a webhook.
*/ */
public function getDeliveryHistory(EntitlementWebhook $webhook, int $limit = 50): \Illuminate\Database\Eloquent\Collection public function getDeliveryHistory(EntitlementWebhook $webhook, int $limit = 50): Collection
{ {
return $webhook->deliveries() return $webhook->deliveries()
->latest('created_at') ->latest('created_at')

View file

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Core\Tenant\Services; namespace Core\Tenant\Services;
use Core\Tenant\Enums\UserTier; use Core\Tenant\Enums\UserTier;
use Core\Tenant\Jobs\ComputeUserStats;
use Core\Tenant\Models\User; use Core\Tenant\Models\User;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
@ -44,7 +45,7 @@ class UserStatsService
// For page loads, return cached data immediately and queue refresh // For page loads, return cached data immediately and queue refresh
if ($user->cached_stats) { if ($user->cached_stats) {
// Queue background refresh // Queue background refresh
dispatch(new \Core\Tenant\Jobs\ComputeUserStats($user->id))->onQueue('stats'); dispatch(new ComputeUserStats($user->id))->onQueue('stats');
return $user->cached_stats; return $user->cached_stats;
} }

View file

@ -7,7 +7,9 @@ namespace Core\Tenant\View\Modal\Admin;
use Core\Tenant\Models\Workspace; use Core\Tenant\Models\Workspace;
use Core\Tenant\Models\WorkspaceMember; use Core\Tenant\Models\WorkspaceMember;
use Core\Tenant\Models\WorkspaceTeam; use Core\Tenant\Models\WorkspaceTeam;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Contracts\View\View; use Illuminate\Contracts\View\View;
use Illuminate\Database\Eloquent\Collection;
use Livewire\Attributes\Computed; use Livewire\Attributes\Computed;
use Livewire\Component; use Livewire\Component;
use Livewire\WithPagination; use Livewire\WithPagination;
@ -287,7 +289,7 @@ class MemberManager extends Component
// ───────────────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────────────
#[Computed] #[Computed]
public function members(): \Illuminate\Contracts\Pagination\LengthAwarePaginator public function members(): LengthAwarePaginator
{ {
return WorkspaceMember::query() return WorkspaceMember::query()
->with(['user', 'workspace', 'team', 'inviter']) ->with(['user', 'workspace', 'team', 'inviter'])
@ -308,13 +310,13 @@ class MemberManager extends Component
} }
#[Computed] #[Computed]
public function workspaces(): \Illuminate\Database\Eloquent\Collection public function workspaces(): Collection
{ {
return Workspace::orderBy('name')->get(); return Workspace::orderBy('name')->get();
} }
#[Computed] #[Computed]
public function teamsForFilter(): \Illuminate\Database\Eloquent\Collection public function teamsForFilter(): Collection
{ {
$query = WorkspaceTeam::query(); $query = WorkspaceTeam::query();
@ -326,7 +328,7 @@ class MemberManager extends Component
} }
#[Computed] #[Computed]
public function teamsForAssignment(): \Illuminate\Database\Eloquent\Collection public function teamsForAssignment(): Collection
{ {
if ($this->assignMemberId) { if ($this->assignMemberId) {
$member = WorkspaceMember::find($this->assignMemberId); $member = WorkspaceMember::find($this->assignMemberId);
@ -337,11 +339,11 @@ class MemberManager extends Component
} }
} }
return new \Illuminate\Database\Eloquent\Collection; return new Collection;
} }
#[Computed] #[Computed]
public function teamsForBulkAssignment(): \Illuminate\Database\Eloquent\Collection public function teamsForBulkAssignment(): Collection
{ {
// Only show teams from the current workspace filter // Only show teams from the current workspace filter
if ($this->workspaceFilter) { if ($this->workspaceFilter) {
@ -350,7 +352,7 @@ class MemberManager extends Component
->get(); ->get();
} }
return new \Illuminate\Database\Eloquent\Collection; return new Collection;
} }
#[Computed] #[Computed]

View file

@ -8,7 +8,9 @@ use Core\Tenant\Models\Workspace;
use Core\Tenant\Models\WorkspaceMember; use Core\Tenant\Models\WorkspaceMember;
use Core\Tenant\Models\WorkspaceTeam; use Core\Tenant\Models\WorkspaceTeam;
use Core\Tenant\Services\WorkspaceTeamService; use Core\Tenant\Services\WorkspaceTeamService;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Contracts\View\View; use Illuminate\Contracts\View\View;
use Illuminate\Database\Eloquent\Collection;
use Livewire\Attributes\Computed; use Livewire\Attributes\Computed;
use Livewire\Component; use Livewire\Component;
use Livewire\WithPagination; use Livewire\WithPagination;
@ -219,7 +221,7 @@ class TeamManager extends Component
// ───────────────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────────────
#[Computed] #[Computed]
public function teams(): \Illuminate\Contracts\Pagination\LengthAwarePaginator public function teams(): LengthAwarePaginator
{ {
return WorkspaceTeam::query() return WorkspaceTeam::query()
->with(['workspace']) ->with(['workspace'])
@ -239,7 +241,7 @@ class TeamManager extends Component
} }
#[Computed] #[Computed]
public function workspaces(): \Illuminate\Database\Eloquent\Collection public function workspaces(): Collection
{ {
return Workspace::orderBy('name')->get(); return Workspace::orderBy('name')->get();
} }

View file

@ -1,9 +1,29 @@
<?php <?php
declare(strict_types=1);
namespace Core\Tenant\View\Modal\Admin; namespace Core\Tenant\View\Modal\Admin;
use Carbon\Carbon;
use Core\Mod\Analytics\Models\Website;
use Core\Mod\Api\Models\ApiKey;
use Core\Mod\Content\Models\ContentItem;
use Core\Mod\Notify\Models\PushWebsite;
use Core\Mod\Social\Models\Account;
use Core\Mod\Social\Models\Post;
use Core\Mod\Trust\Models\Campaign;
use Core\Mod\Web\Models\Page;
use Core\Mod\Web\Models\Project;
use Core\Tenant\Models\Boost;
use Core\Tenant\Models\EntitlementLog;
use Core\Tenant\Models\Feature;
use Core\Tenant\Models\Package;
use Core\Tenant\Models\UsageRecord;
use Core\Tenant\Models\User; use Core\Tenant\Models\User;
use Core\Tenant\Models\Workspace; use Core\Tenant\Models\Workspace;
use Core\Tenant\Models\WorkspacePackage;
use Core\Tenant\Services\EntitlementService;
use Illuminate\Support\Facades\Schema;
use Livewire\Attributes\Computed; use Livewire\Attributes\Computed;
use Livewire\Component; use Livewire\Component;
@ -87,18 +107,18 @@ class WorkspaceDetails extends Component
public function resourceCounts(): array public function resourceCounts(): array
{ {
$counts = []; $counts = [];
$schema = \Illuminate\Support\Facades\Schema::getFacadeRoot(); $schema = Schema::getFacadeRoot();
$resources = [ $resources = [
['relation' => 'bioPages', 'label' => 'Bio Pages', 'icon' => 'link', 'color' => 'blue', 'model' => \Core\Mod\Web\Models\Page::class], ['relation' => 'bioPages', 'label' => 'Bio Pages', 'icon' => 'link', 'color' => 'blue', 'model' => Page::class],
['relation' => 'bioProjects', 'label' => 'Bio Projects', 'icon' => 'folder', 'color' => 'indigo', 'model' => \Core\Mod\Web\Models\Project::class], ['relation' => 'bioProjects', 'label' => 'Bio Projects', 'icon' => 'folder', 'color' => 'indigo', 'model' => Project::class],
['relation' => 'socialAccounts', 'label' => 'Social Accounts', 'icon' => 'share-nodes', 'color' => 'purple', 'model' => \Core\Mod\Social\Models\Account::class], ['relation' => 'socialAccounts', 'label' => 'Social Accounts', 'icon' => 'share-nodes', 'color' => 'purple', 'model' => Account::class],
['relation' => 'socialPosts', 'label' => 'Social Posts', 'icon' => 'paper-plane', 'color' => 'pink', 'model' => \Core\Mod\Social\Models\Post::class], ['relation' => 'socialPosts', 'label' => 'Social Posts', 'icon' => 'paper-plane', 'color' => 'pink', 'model' => Post::class],
['relation' => 'analyticsSites', 'label' => 'Analytics Sites', 'icon' => 'chart-line', 'color' => 'cyan', 'model' => \Core\Mod\Analytics\Models\Website::class], ['relation' => 'analyticsSites', 'label' => 'Analytics Sites', 'icon' => 'chart-line', 'color' => 'cyan', 'model' => Website::class],
['relation' => 'trustWidgets', 'label' => 'Trust Campaigns', 'icon' => 'shield-check', 'color' => 'emerald', 'model' => \Core\Mod\Trust\Models\Campaign::class], ['relation' => 'trustWidgets', 'label' => 'Trust Campaigns', 'icon' => 'shield-check', 'color' => 'emerald', 'model' => Campaign::class],
['relation' => 'notificationSites', 'label' => 'Notification Sites', 'icon' => 'bell', 'color' => 'amber', 'model' => \Core\Mod\Notify\Models\PushWebsite::class], ['relation' => 'notificationSites', 'label' => 'Notification Sites', 'icon' => 'bell', 'color' => 'amber', 'model' => PushWebsite::class],
['relation' => 'contentItems', 'label' => 'Content Items', 'icon' => 'file-lines', 'color' => 'slate', 'model' => \Core\Mod\Content\Models\ContentItem::class], ['relation' => 'contentItems', 'label' => 'Content Items', 'icon' => 'file-lines', 'color' => 'slate', 'model' => ContentItem::class],
['relation' => 'apiKeys', 'label' => 'API Keys', 'icon' => 'key', 'color' => 'rose', 'model' => \Core\Mod\Api\Models\ApiKey::class], ['relation' => 'apiKeys', 'label' => 'API Keys', 'icon' => 'key', 'color' => 'rose', 'model' => ApiKey::class],
]; ];
foreach ($resources as $resource) { foreach ($resources as $resource) {
@ -125,7 +145,7 @@ class WorkspaceDetails extends Component
$activities = collect(); $activities = collect();
// Entitlement logs // Entitlement logs
if (class_exists(\Core\Tenant\Models\EntitlementLog::class)) { if (class_exists(EntitlementLog::class)) {
try { try {
$logs = $this->workspace->entitlementLogs() $logs = $this->workspace->entitlementLogs()
->with('user', 'feature') ->with('user', 'feature')
@ -147,7 +167,7 @@ class WorkspaceDetails extends Component
} }
// Usage records // Usage records
if (class_exists(\Core\Tenant\Models\UsageRecord::class)) { if (class_exists(UsageRecord::class)) {
try { try {
$usage = $this->workspace->usageRecords() $usage = $this->workspace->usageRecords()
->with('user', 'feature') ->with('user', 'feature')
@ -325,7 +345,7 @@ class WorkspaceDetails extends Component
#[Computed] #[Computed]
public function allPackages() public function allPackages()
{ {
return \Core\Tenant\Models\Package::active() return Package::active()
->ordered() ->ordered()
->get(); ->get();
} }
@ -333,7 +353,7 @@ class WorkspaceDetails extends Component
#[Computed] #[Computed]
public function allFeatures() public function allFeatures()
{ {
return \Core\Tenant\Models\Feature::active() return Feature::active()
->orderBy('category') ->orderBy('category')
->orderBy('sort_order') ->orderBy('sort_order')
->get(); ->get();
@ -403,7 +423,7 @@ class WorkspaceDetails extends Component
public function resolvedEntitlements() public function resolvedEntitlements()
{ {
try { try {
return app(\Core\Tenant\Services\EntitlementService::class) return app(EntitlementService::class)
->getUsageSummary($this->workspace); ->getUsageSummary($this->workspace);
} catch (\Exception $e) { } catch (\Exception $e) {
return collect(); return collect();
@ -431,7 +451,7 @@ class WorkspaceDetails extends Component
return; return;
} }
$package = \Core\Tenant\Models\Package::findOrFail($this->selectedPackageId); $package = Package::findOrFail($this->selectedPackageId);
// Check if already assigned // Check if already assigned
$existing = $this->workspace->workspacePackages() $existing = $this->workspace->workspacePackages()
@ -446,7 +466,7 @@ class WorkspaceDetails extends Component
return; return;
} }
\Core\Tenant\Models\WorkspacePackage::create([ WorkspacePackage::create([
'workspace_id' => $this->workspace->id, 'workspace_id' => $this->workspace->id,
'package_id' => $package->id, 'package_id' => $package->id,
'status' => 'active', 'status' => 'active',
@ -461,7 +481,7 @@ class WorkspaceDetails extends Component
public function removePackage(int $workspacePackageId): void public function removePackage(int $workspacePackageId): void
{ {
$wp = \Core\Tenant\Models\WorkspacePackage::where('workspace_id', $this->workspace->id) $wp = WorkspacePackage::where('workspace_id', $this->workspace->id)
->findOrFail($workspacePackageId); ->findOrFail($workspacePackageId);
$packageName = $wp->package?->name ?? 'Package'; $packageName = $wp->package?->name ?? 'Package';
@ -474,7 +494,7 @@ class WorkspaceDetails extends Component
public function suspendPackage(int $workspacePackageId): void public function suspendPackage(int $workspacePackageId): void
{ {
$wp = \Core\Tenant\Models\WorkspacePackage::where('workspace_id', $this->workspace->id) $wp = WorkspacePackage::where('workspace_id', $this->workspace->id)
->findOrFail($workspacePackageId); ->findOrFail($workspacePackageId);
$wp->suspend(); $wp->suspend();
@ -486,7 +506,7 @@ class WorkspaceDetails extends Component
public function reactivatePackage(int $workspacePackageId): void public function reactivatePackage(int $workspacePackageId): void
{ {
$wp = \Core\Tenant\Models\WorkspacePackage::where('workspace_id', $this->workspace->id) $wp = WorkspacePackage::where('workspace_id', $this->workspace->id)
->findOrFail($workspacePackageId); ->findOrFail($workspacePackageId);
$wp->reactivate(); $wp->reactivate();
@ -523,7 +543,7 @@ class WorkspaceDetails extends Component
return; return;
} }
$feature = \Core\Tenant\Models\Feature::where('code', $this->selectedFeatureCode)->first(); $feature = Feature::where('code', $this->selectedFeatureCode)->first();
if (! $feature) { if (! $feature) {
$this->actionMessage = 'Feature not found.'; $this->actionMessage = 'Feature not found.';
@ -534,26 +554,26 @@ class WorkspaceDetails extends Component
// Map type to boost type constant // Map type to boost type constant
$boostType = match ($this->entitlementType) { $boostType = match ($this->entitlementType) {
'enable' => \Core\Tenant\Models\Boost::BOOST_TYPE_ENABLE, 'enable' => Boost::BOOST_TYPE_ENABLE,
'add_limit' => \Core\Tenant\Models\Boost::BOOST_TYPE_ADD_LIMIT, 'add_limit' => Boost::BOOST_TYPE_ADD_LIMIT,
'unlimited' => \Core\Tenant\Models\Boost::BOOST_TYPE_UNLIMITED, 'unlimited' => Boost::BOOST_TYPE_UNLIMITED,
default => \Core\Tenant\Models\Boost::BOOST_TYPE_ENABLE, default => Boost::BOOST_TYPE_ENABLE,
}; };
$durationType = $this->entitlementDuration === 'permanent' $durationType = $this->entitlementDuration === 'permanent'
? \Core\Tenant\Models\Boost::DURATION_PERMANENT ? Boost::DURATION_PERMANENT
: \Core\Tenant\Models\Boost::DURATION_DURATION; : Boost::DURATION_DURATION;
\Core\Tenant\Models\Boost::create([ Boost::create([
'workspace_id' => $this->workspace->id, 'workspace_id' => $this->workspace->id,
'feature_code' => $this->selectedFeatureCode, 'feature_code' => $this->selectedFeatureCode,
'boost_type' => $boostType, 'boost_type' => $boostType,
'duration_type' => $durationType, 'duration_type' => $durationType,
'limit_value' => $this->entitlementType === 'add_limit' ? $this->entitlementLimit : null, 'limit_value' => $this->entitlementType === 'add_limit' ? $this->entitlementLimit : null,
'consumed_quantity' => 0, 'consumed_quantity' => 0,
'status' => \Core\Tenant\Models\Boost::STATUS_ACTIVE, 'status' => Boost::STATUS_ACTIVE,
'starts_at' => now(), 'starts_at' => now(),
'expires_at' => $this->entitlementExpiresAt ? \Carbon\Carbon::parse($this->entitlementExpiresAt) : null, 'expires_at' => $this->entitlementExpiresAt ? Carbon::parse($this->entitlementExpiresAt) : null,
'metadata' => ['granted_by' => auth()->id(), 'granted_at' => now()->toDateTimeString()], 'metadata' => ['granted_by' => auth()->id(), 'granted_at' => now()->toDateTimeString()],
]); ]);
@ -565,7 +585,7 @@ class WorkspaceDetails extends Component
public function removeBoost(int $boostId): void public function removeBoost(int $boostId): void
{ {
$boost = \Core\Tenant\Models\Boost::where('workspace_id', $this->workspace->id) $boost = Boost::where('workspace_id', $this->workspace->id)
->findOrFail($boostId); ->findOrFail($boostId);
$featureCode = $boost->feature_code; $featureCode = $boost->feature_code;

View file

@ -1,10 +1,20 @@
<?php <?php
declare(strict_types=1);
namespace Core\Tenant\View\Modal\Admin; namespace Core\Tenant\View\Modal\Admin;
use Core\Mod\Analytics\Models\Website;
use Core\Mod\Notify\Models\PushWebsite;
use Core\Mod\Social\Models\Account;
use Core\Mod\Trust\Models\Campaign;
use Core\Mod\Web\Models\Page;
use Core\Mod\Web\Models\Project;
use Core\Tenant\Models\User; use Core\Tenant\Models\User;
use Core\Tenant\Models\Workspace; use Core\Tenant\Models\Workspace;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Str;
use Livewire\Attributes\Computed; use Livewire\Attributes\Computed;
use Livewire\Component; use Livewire\Component;
use Livewire\WithPagination; use Livewire\WithPagination;
@ -117,15 +127,15 @@ class WorkspaceManager extends Component
// Check each relation's model exists and has workspace_id column // Check each relation's model exists and has workspace_id column
$checks = [ $checks = [
'bioPages' => ['model' => \Core\Mod\Web\Models\Page::class, 'table' => 'pages'], 'bioPages' => ['model' => Page::class, 'table' => 'pages'],
'bioProjects' => ['model' => \Core\Mod\Web\Models\Project::class, 'table' => 'page_projects'], 'bioProjects' => ['model' => Project::class, 'table' => 'page_projects'],
'socialAccounts' => ['model' => \Core\Mod\Social\Models\Account::class, 'table' => 'social_accounts'], 'socialAccounts' => ['model' => Account::class, 'table' => 'social_accounts'],
'analyticsSites' => ['model' => \Core\Mod\Analytics\Models\Website::class, 'table' => 'analytics_websites'], 'analyticsSites' => ['model' => Website::class, 'table' => 'analytics_websites'],
'trustWidgets' => ['model' => \Core\Mod\Trust\Models\Campaign::class, 'table' => 'trust_campaigns'], 'trustWidgets' => ['model' => Campaign::class, 'table' => 'trust_campaigns'],
'notificationSites' => ['model' => \Core\Mod\Notify\Models\PushWebsite::class, 'table' => 'push_websites'], 'notificationSites' => ['model' => PushWebsite::class, 'table' => 'push_websites'],
]; ];
$schema = \Illuminate\Support\Facades\Schema::getFacadeRoot(); $schema = Schema::getFacadeRoot();
foreach ($checks as $relation => $info) { foreach ($checks as $relation => $info) {
if (class_exists($info['model'])) { if (class_exists($info['model'])) {
@ -153,16 +163,16 @@ class WorkspaceManager extends Component
public function resourceTypes(): array public function resourceTypes(): array
{ {
$types = []; $types = [];
$schema = \Illuminate\Support\Facades\Schema::getFacadeRoot(); $schema = Schema::getFacadeRoot();
// Only include resource types for models that exist and have valid relations // Only include resource types for models that exist and have valid relations
$checks = [ $checks = [
'bio_pages' => ['model' => \Core\Mod\Web\Models\Page::class, 'table' => 'pages', 'label' => 'Bio Pages', 'relation' => 'bioPages', 'icon' => 'link'], 'bio_pages' => ['model' => Page::class, 'table' => 'pages', 'label' => 'Bio Pages', 'relation' => 'bioPages', 'icon' => 'link'],
'bio_projects' => ['model' => \Core\Mod\Web\Models\Project::class, 'table' => 'page_projects', 'label' => 'Bio Projects', 'relation' => 'bioProjects', 'icon' => 'folder'], 'bio_projects' => ['model' => Project::class, 'table' => 'page_projects', 'label' => 'Bio Projects', 'relation' => 'bioProjects', 'icon' => 'folder'],
'social_accounts' => ['model' => \Core\Mod\Social\Models\Account::class, 'table' => 'social_accounts', 'label' => 'Social Accounts', 'relation' => 'socialAccounts', 'icon' => 'share-nodes'], 'social_accounts' => ['model' => Account::class, 'table' => 'social_accounts', 'label' => 'Social Accounts', 'relation' => 'socialAccounts', 'icon' => 'share-nodes'],
'analytics_sites' => ['model' => \Core\Mod\Analytics\Models\Website::class, 'table' => 'analytics_websites', 'label' => 'Analytics Sites', 'relation' => 'analyticsSites', 'icon' => 'chart-line'], 'analytics_sites' => ['model' => Website::class, 'table' => 'analytics_websites', 'label' => 'Analytics Sites', 'relation' => 'analyticsSites', 'icon' => 'chart-line'],
'trust_widgets' => ['model' => \Core\Mod\Trust\Models\Campaign::class, 'table' => 'trust_campaigns', 'label' => 'Trust Campaigns', 'relation' => 'trustWidgets', 'icon' => 'shield-check'], 'trust_widgets' => ['model' => Campaign::class, 'table' => 'trust_campaigns', 'label' => 'Trust Campaigns', 'relation' => 'trustWidgets', 'icon' => 'shield-check'],
'notification_sites' => ['model' => \Core\Mod\Notify\Models\PushWebsite::class, 'table' => 'push_websites', 'label' => 'Notification Sites', 'relation' => 'notificationSites', 'icon' => 'bell'], 'notification_sites' => ['model' => PushWebsite::class, 'table' => 'push_websites', 'label' => 'Notification Sites', 'relation' => 'notificationSites', 'icon' => 'bell'],
]; ];
foreach ($checks as $key => $info) { foreach ($checks as $key => $info) {
@ -530,7 +540,7 @@ class WorkspaceManager extends Component
'icon' => 'link', 'icon' => 'link',
'color' => 'blue', 'color' => 'blue',
'fields' => ['name', 'slug'], 'fields' => ['name', 'slug'],
'model' => \Core\Mod\Web\Models\Page::class, 'model' => Page::class,
'defaults' => ['type' => 'page', 'is_enabled' => true], 'defaults' => ['type' => 'page', 'is_enabled' => true],
], ],
'social_accounts' => [ 'social_accounts' => [
@ -538,7 +548,7 @@ class WorkspaceManager extends Component
'icon' => 'share-nodes', 'icon' => 'share-nodes',
'color' => 'purple', 'color' => 'purple',
'fields' => ['name'], 'fields' => ['name'],
'model' => \Core\Mod\Social\Models\Account::class, 'model' => Account::class,
'defaults' => ['provider' => 'manual', 'status' => 'active'], 'defaults' => ['provider' => 'manual', 'status' => 'active'],
], ],
'analytics_sites' => [ 'analytics_sites' => [
@ -546,7 +556,7 @@ class WorkspaceManager extends Component
'icon' => 'chart-line', 'icon' => 'chart-line',
'color' => 'cyan', 'color' => 'cyan',
'fields' => ['name', 'url'], 'fields' => ['name', 'url'],
'model' => \Core\Mod\Analytics\Models\Website::class, 'model' => Website::class,
'defaults' => ['tracking_enabled' => true, 'is_enabled' => true], 'defaults' => ['tracking_enabled' => true, 'is_enabled' => true],
], ],
'trust_widgets' => [ 'trust_widgets' => [
@ -554,7 +564,7 @@ class WorkspaceManager extends Component
'icon' => 'shield-check', 'icon' => 'shield-check',
'color' => 'emerald', 'color' => 'emerald',
'fields' => ['name'], 'fields' => ['name'],
'model' => \Core\Mod\Trust\Models\Campaign::class, 'model' => Campaign::class,
'defaults' => ['status' => 'draft'], 'defaults' => ['status' => 'draft'],
], ],
'notification_sites' => [ 'notification_sites' => [
@ -562,7 +572,7 @@ class WorkspaceManager extends Component
'icon' => 'bell', 'icon' => 'bell',
'color' => 'amber', 'color' => 'amber',
'fields' => ['name', 'url'], 'fields' => ['name', 'url'],
'model' => \Core\Mod\Notify\Models\PushWebsite::class, 'model' => PushWebsite::class,
'defaults' => ['status' => 'active'], 'defaults' => ['status' => 'active'],
], ],
]; ];
@ -615,7 +625,7 @@ class WorkspaceManager extends Component
// Add slug for bio pages // Add slug for bio pages
if (in_array('slug', $config['fields']) && $this->provisionSlug) { if (in_array('slug', $config['fields']) && $this->provisionSlug) {
$data['url'] = \Illuminate\Support\Str::slug($this->provisionSlug); $data['url'] = Str::slug($this->provisionSlug);
} }
// Add URL-related fields if applicable // Add URL-related fields if applicable

View file

@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace Core\Tenant\View\Modal\Web; namespace Core\Tenant\View\Modal\Web;
use Core\Tenant\Models\AccountDeletionRequest; use Core\Tenant\Models\AccountDeletionRequest;

View file

@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace Core\Tenant\View\Modal\Web; namespace Core\Tenant\View\Modal\Web;
use Core\Tenant\Models\AccountDeletionRequest; use Core\Tenant\Models\AccountDeletionRequest;

View file

@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace Core\Tenant\View\Modal\Web; namespace Core\Tenant\View\Modal\Web;
use Core\Mod\Content\Services\ContentRender; use Core\Mod\Content\Services\ContentRender;

View file

@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace Core\Tenant\Tests\Feature; namespace Core\Tenant\Tests\Feature;
use Core\Tenant\Models\User; use Core\Tenant\Models\User;

View file

@ -2,6 +2,8 @@
declare(strict_types=1); declare(strict_types=1);
use Core\Api\RateLimit\RateLimit;
use Core\Tenant\Controllers\EntitlementApiController;
use Core\Tenant\Models\EntitlementLog; use Core\Tenant\Models\EntitlementLog;
use Core\Tenant\Models\Feature; use Core\Tenant\Models\Feature;
use Core\Tenant\Models\Package; use Core\Tenant\Models\Package;
@ -10,10 +12,11 @@ use Core\Tenant\Models\Workspace;
use Core\Tenant\Models\WorkspacePackage; use Core\Tenant\Models\WorkspacePackage;
use Core\Tenant\Services\EntitlementService; use Core\Tenant\Services\EntitlementService;
use Illuminate\Auth\Events\Registered; use Illuminate\Auth\Events\Registered;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Event;
uses(\Illuminate\Foundation\Testing\RefreshDatabase::class); uses(RefreshDatabase::class);
beforeEach(function () { beforeEach(function () {
Cache::flush(); Cache::flush();
@ -1076,8 +1079,8 @@ describe('Error Response Format', function () {
describe('Rate Limiting', function () { describe('Rate Limiting', function () {
it('controller has rate limit attribute', function () { it('controller has rate limit attribute', function () {
$reflection = new \ReflectionClass(\Core\Tenant\Controllers\EntitlementApiController::class); $reflection = new ReflectionClass(EntitlementApiController::class);
$attributes = $reflection->getAttributes(\Core\Api\RateLimit\RateLimit::class); $attributes = $reflection->getAttributes(RateLimit::class);
expect($attributes)->toHaveCount(1); expect($attributes)->toHaveCount(1);

View file

@ -15,9 +15,11 @@ use Core\Tenant\Models\Workspace;
use Core\Tenant\Models\WorkspacePackage; use Core\Tenant\Models\WorkspacePackage;
use Core\Tenant\Services\EntitlementResult; use Core\Tenant\Services\EntitlementResult;
use Core\Tenant\Services\EntitlementService; use Core\Tenant\Services\EntitlementService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
uses(\Illuminate\Foundation\Testing\RefreshDatabase::class); uses(RefreshDatabase::class);
beforeEach(function () { beforeEach(function () {
// Clear cache before each test // Clear cache before each test
@ -426,7 +428,7 @@ describe('EntitlementService', function () {
$summary = $this->service->getUsageSummary($this->workspace); $summary = $this->service->getUsageSummary($this->workspace);
expect($summary)->toBeInstanceOf(\Illuminate\Support\Collection::class) expect($summary)->toBeInstanceOf(Collection::class)
->and($summary->has('ai'))->toBeTrue() ->and($summary->has('ai'))->toBeTrue()
->and($summary->has('tier'))->toBeTrue() ->and($summary->has('tier'))->toBeTrue()
->and($summary->has('social'))->toBeTrue(); ->and($summary->has('social'))->toBeTrue();
@ -1101,7 +1103,7 @@ describe('Namespace Entitlements', function () {
$summary = $this->service->getNamespaceUsageSummary($this->userNamespace); $summary = $this->service->getNamespaceUsageSummary($this->userNamespace);
expect($summary)->toBeInstanceOf(\Illuminate\Support\Collection::class) expect($summary)->toBeInstanceOf(Collection::class)
->and($summary->has('content'))->toBeTrue() ->and($summary->has('content'))->toBeTrue()
->and($summary->has('features'))->toBeTrue(); ->and($summary->has('features'))->toBeTrue();
}); });

View file

@ -2,16 +2,18 @@
declare(strict_types=1); declare(strict_types=1);
use Core\Mod\Api\Guards\AccessTokenGuard;
use Core\Tenant\Models\User; use Core\Tenant\Models\User;
use Core\Tenant\Models\UserToken; use Core\Tenant\Models\UserToken;
use Illuminate\Http\Request;
test('can authenticate with valid bearer token', function () { test('can authenticate with valid bearer token', function () {
$user = User::factory()->create(); $user = User::factory()->create();
$result = $user->createToken('Test Token'); $result = $user->createToken('Test Token');
// Test the guard directly by invoking it with a mock request // Test the guard directly by invoking it with a mock request
$guard = new \Core\Mod\Api\Guards\AccessTokenGuard(app('auth')); $guard = new AccessTokenGuard(app('auth'));
$request = \Illuminate\Http\Request::create('/test', 'GET'); $request = Request::create('/test', 'GET');
$request->headers->set('Authorization', "Bearer {$result['token']}"); $request->headers->set('Authorization', "Bearer {$result['token']}");
$authenticatedUser = $guard($request); $authenticatedUser = $guard($request);
@ -57,8 +59,8 @@ test('token last_used_at is updated on successful authentication', function () {
expect($tokenModel->last_used_at)->toBeNull(); expect($tokenModel->last_used_at)->toBeNull();
// Test the guard directly by invoking it with a mock request // Test the guard directly by invoking it with a mock request
$guard = new \Core\Mod\Api\Guards\AccessTokenGuard(app('auth')); $guard = new AccessTokenGuard(app('auth'));
$request = \Illuminate\Http\Request::create('/test', 'GET'); $request = Request::create('/test', 'GET');
$request->headers->set('Authorization', "Bearer {$result['token']}"); $request->headers->set('Authorization', "Bearer {$result['token']}");
$guard($request); $guard($request);

View file

@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace Core\Tenant\Tests\Feature; namespace Core\Tenant\Tests\Feature;
use Core\Mod\Hub\View\Modal\Admin\Profile; use Core\Mod\Hub\View\Modal\Admin\Profile;

View file

@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
use Core\Tenant\Models\Boost; use Core\Tenant\Models\Boost;
use Core\Tenant\Models\EntitlementLog; use Core\Tenant\Models\EntitlementLog;
use Core\Tenant\Models\Feature; use Core\Tenant\Models\Feature;
@ -9,10 +11,11 @@ use Core\Tenant\Models\User;
use Core\Tenant\Models\Workspace; use Core\Tenant\Models\Workspace;
use Core\Tenant\Notifications\BoostExpiredNotification; use Core\Tenant\Notifications\BoostExpiredNotification;
use Core\Tenant\Services\EntitlementService; use Core\Tenant\Services\EntitlementService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Notification; use Illuminate\Support\Facades\Notification;
uses(\Illuminate\Foundation\Testing\RefreshDatabase::class); uses(RefreshDatabase::class);
beforeEach(function () { beforeEach(function () {
Cache::flush(); Cache::flush();

View file

@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace Core\Tenant\Tests\Feature; namespace Core\Tenant\Tests\Feature;
use Core\Mod\Hub\View\Modal\Admin\Settings; use Core\Mod\Hub\View\Modal\Admin\Settings;

View file

@ -2,9 +2,13 @@
declare(strict_types=1); declare(strict_types=1);
use Carbon\Carbon;
use Core\Tenant\Models\User; use Core\Tenant\Models\User;
use Core\Tenant\Models\UserTwoFactorAuth; use Core\Tenant\Models\UserTwoFactorAuth;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
beforeEach(function () { beforeEach(function () {
Cache::flush(); Cache::flush();
@ -15,7 +19,7 @@ describe('TwoFactorAuthenticatable Trait', function () {
describe('twoFactorAuth() relationship', function () { describe('twoFactorAuth() relationship', function () {
it('returns HasOne relationship', function () { it('returns HasOne relationship', function () {
expect($this->user->twoFactorAuth())->toBeInstanceOf( expect($this->user->twoFactorAuth())->toBeInstanceOf(
\Illuminate\Database\Eloquent\Relations\HasOne::class HasOne::class
); );
}); });
@ -315,7 +319,7 @@ describe('UserTwoFactorAuth Model', function () {
'recovery_codes' => $codes, 'recovery_codes' => $codes,
]); ]);
expect($twoFactorAuth->recovery_codes)->toBeInstanceOf(\Illuminate\Support\Collection::class) expect($twoFactorAuth->recovery_codes)->toBeInstanceOf(Collection::class)
->and($twoFactorAuth->recovery_codes->toArray())->toBe($codes); ->and($twoFactorAuth->recovery_codes->toArray())->toBe($codes);
}); });
@ -329,7 +333,7 @@ describe('UserTwoFactorAuth Model', function () {
'confirmed_at' => $confirmedAt, 'confirmed_at' => $confirmedAt,
]); ]);
expect($twoFactorAuth->confirmed_at)->toBeInstanceOf(\Carbon\Carbon::class); expect($twoFactorAuth->confirmed_at)->toBeInstanceOf(Carbon::class);
}); });
it('encrypts secret at rest', function () { it('encrypts secret at rest', function () {
@ -348,7 +352,7 @@ describe('UserTwoFactorAuth Model', function () {
// But the raw database value should be encrypted (base64 JSON with iv, value, mac) // But the raw database value should be encrypted (base64 JSON with iv, value, mac)
// Note: DB column is 'secret', not 'secret_key' // Note: DB column is 'secret', not 'secret_key'
$rawValue = \Illuminate\Support\Facades\DB::table('user_two_factor_auth') $rawValue = DB::table('user_two_factor_auth')
->where('id', $twoFactorAuth->id) ->where('id', $twoFactorAuth->id)
->value('secret'); ->value('secret');
@ -372,7 +376,7 @@ describe('UserTwoFactorAuth Model', function () {
expect($twoFactorAuth->recovery_codes->toArray())->toBe($codes); expect($twoFactorAuth->recovery_codes->toArray())->toBe($codes);
// But the raw database value should be encrypted // But the raw database value should be encrypted
$rawValue = \Illuminate\Support\Facades\DB::table('user_two_factor_auth') $rawValue = DB::table('user_two_factor_auth')
->where('id', $twoFactorAuth->id) ->where('id', $twoFactorAuth->id)
->value('recovery_codes'); ->value('recovery_codes');

View file

@ -7,6 +7,7 @@ use Core\Tenant\Notifications\WaitlistInviteNotification;
use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Notification; use Illuminate\Support\Facades\Notification;
use Illuminate\Support\Facades\RateLimiter; use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Str;
use Livewire\Livewire; use Livewire\Livewire;
use Website\Host\View\Modal\Waitlist; use Website\Host\View\Modal\Waitlist;
@ -130,7 +131,7 @@ describe('Waitlist Entry Model', function () {
expect($entry->invite_code)->toBeNull(); expect($entry->invite_code)->toBeNull();
$entry->update([ $entry->update([
'invite_code' => \Illuminate\Support\Str::random(16), 'invite_code' => Str::random(16),
'invited_at' => now(), 'invited_at' => now(),
]); ]);

View file

@ -14,6 +14,7 @@ use Core\Tenant\Scopes\WorkspaceScope;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Tests\TestCase; use Tests\TestCase;
/** /**
@ -208,7 +209,7 @@ class WorkspaceSecurityTest extends TestCase
$this->expectExceptionMessage('create'); $this->expectExceptionMessage('create');
Account::create([ Account::create([
'uuid' => \Illuminate\Support\Str::uuid(), 'uuid' => Str::uuid(),
'provider' => 'twitter', 'provider' => 'twitter',
'provider_id' => '12345', 'provider_id' => '12345',
'name' => 'Test Account', 'name' => 'Test Account',
@ -223,7 +224,7 @@ class WorkspaceSecurityTest extends TestCase
// Should succeed because workspace_id is explicitly provided // Should succeed because workspace_id is explicitly provided
$account = Account::create([ $account = Account::create([
'uuid' => \Illuminate\Support\Str::uuid(), 'uuid' => Str::uuid(),
'workspace_id' => $this->workspace->id, 'workspace_id' => $this->workspace->id,
'provider' => 'twitter', 'provider' => 'twitter',
'provider_id' => '12345', 'provider_id' => '12345',
@ -240,7 +241,7 @@ class WorkspaceSecurityTest extends TestCase
request()->attributes->set('workspace_model', $this->workspace); request()->attributes->set('workspace_model', $this->workspace);
$account = Account::create([ $account = Account::create([
'uuid' => \Illuminate\Support\Str::uuid(), 'uuid' => Str::uuid(),
'provider' => 'twitter', 'provider' => 'twitter',
'provider_id' => '12345', 'provider_id' => '12345',
'name' => 'Test Account', 'name' => 'Test Account',

View file

@ -1,12 +1,16 @@
<?php <?php
declare(strict_types=1);
namespace Core\Tenant\Tests\Feature; namespace Core\Tenant\Tests\Feature;
use Core\Mod\Analytics\Models\Website; use Core\Mod\Analytics\Models\Website;
use Core\Mod\Social\Models\Account; use Core\Mod\Social\Models\Account;
use Core\Tenant\Models\User; use Core\Tenant\Models\User;
use Core\Tenant\Models\Workspace; use Core\Tenant\Models\Workspace;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Str;
use Tests\TestCase; use Tests\TestCase;
/** /**
@ -45,12 +49,12 @@ class WorkspaceTenancyTest extends TestCase
$workspace = Workspace::factory()->create(); $workspace = Workspace::factory()->create();
// Test that all relationship methods exist and return correct type // Test that all relationship methods exist and return correct type
$this->assertInstanceOf(\Illuminate\Database\Eloquent\Relations\HasMany::class, $workspace->socialAccounts()); $this->assertInstanceOf(HasMany::class, $workspace->socialAccounts());
$this->assertInstanceOf(\Illuminate\Database\Eloquent\Relations\HasMany::class, $workspace->socialPosts()); $this->assertInstanceOf(HasMany::class, $workspace->socialPosts());
$this->assertInstanceOf(\Illuminate\Database\Eloquent\Relations\HasMany::class, $workspace->analyticsSites()); $this->assertInstanceOf(HasMany::class, $workspace->analyticsSites());
$this->assertInstanceOf(\Illuminate\Database\Eloquent\Relations\HasMany::class, $workspace->trustWidgets()); $this->assertInstanceOf(HasMany::class, $workspace->trustWidgets());
$this->assertInstanceOf(\Illuminate\Database\Eloquent\Relations\HasMany::class, $workspace->notificationSites()); $this->assertInstanceOf(HasMany::class, $workspace->notificationSites());
$this->assertInstanceOf(\Illuminate\Database\Eloquent\Relations\HasMany::class, $workspace->pushCampaigns()); $this->assertInstanceOf(HasMany::class, $workspace->pushCampaigns());
// NOTE: bioPages relationship has been moved to Host UK app's Mod\Bio module // NOTE: bioPages relationship has been moved to Host UK app's Mod\Bio module
} }
@ -114,7 +118,7 @@ class WorkspaceTenancyTest extends TestCase
// When creating a model with BelongsToWorkspace trait, // When creating a model with BelongsToWorkspace trait,
// it should auto-assign the current user's workspace // it should auto-assign the current user's workspace
$account = Account::create([ $account = Account::create([
'uuid' => \Illuminate\Support\Str::uuid(), 'uuid' => Str::uuid(),
'provider' => 'twitter', 'provider' => 'twitter',
'provider_id' => '12345', 'provider_id' => '12345',
'name' => 'Test Account', 'name' => 'Test Account',

View file

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Tests; namespace Tests;
use Core\Tenant\Boot;
use Orchestra\Testbench\TestCase as BaseTestCase; use Orchestra\Testbench\TestCase as BaseTestCase;
abstract class TestCase extends BaseTestCase abstract class TestCase extends BaseTestCase
@ -11,7 +12,7 @@ abstract class TestCase extends BaseTestCase
protected function getPackageProviders($app): array protected function getPackageProviders($app): array
{ {
return [ return [
\Core\Tenant\Boot::class, Boot::class,
]; ];
} }
} }