monorepo sepration
This commit is contained in:
parent
bc9ffd74d3
commit
acceff6d36
126 changed files with 1848 additions and 627 deletions
62
.github/package-workflows/README.md
vendored
62
.github/package-workflows/README.md
vendored
|
|
@ -1,62 +0,0 @@
|
|||
# Package Workflows
|
||||
|
||||
These workflow templates are for **library packages** (host-uk/core, host-uk/core-api, etc.), not application projects.
|
||||
|
||||
## README Badges
|
||||
|
||||
Add these badges to your package README (replace `{package}` with your package name):
|
||||
|
||||
```markdown
|
||||
[](https://github.com/host-uk/{package}/actions/workflows/ci.yml)
|
||||
[](https://codecov.io/gh/host-uk/{package})
|
||||
[](https://packagist.org/packages/host-uk/{package})
|
||||
[](https://packagist.org/packages/host-uk/{package})
|
||||
[](LICENSE)
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Copy the relevant workflows to your library's `.github/workflows/` directory:
|
||||
|
||||
```bash
|
||||
# In your library repo
|
||||
mkdir -p .github/workflows
|
||||
cp path/to/core-template/.github/package-workflows/ci.yml .github/workflows/
|
||||
cp path/to/core-template/.github/package-workflows/release.yml .github/workflows/
|
||||
```
|
||||
|
||||
## Workflows
|
||||
|
||||
### ci.yml
|
||||
- Runs on push/PR to main
|
||||
- Tests against PHP 8.2, 8.3, 8.4
|
||||
- Tests against Laravel 11 and 12
|
||||
- Runs Pint linting
|
||||
- Runs Pest tests
|
||||
|
||||
### release.yml
|
||||
- Triggers on version tags (v*)
|
||||
- Generates changelog using git-cliff
|
||||
- Creates GitHub release
|
||||
|
||||
## Requirements
|
||||
|
||||
For these workflows to work, your package needs:
|
||||
|
||||
1. **cliff.toml** - Copy from core-template root
|
||||
2. **Pest configured** - `composer require pestphp/pest --dev`
|
||||
3. **Pint configured** - `composer require laravel/pint --dev`
|
||||
4. **CODECOV_TOKEN** - Add to repo secrets for coverage uploads
|
||||
5. **FUNDING.yml** - Copy `.github/FUNDING.yml` for sponsor button
|
||||
|
||||
## Recommended composer.json scripts
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"lint": "pint",
|
||||
"test": "pest",
|
||||
"test:coverage": "pest --coverage"
|
||||
}
|
||||
}
|
||||
```
|
||||
55
.github/package-workflows/ci.yml
vendored
55
.github/package-workflows/ci.yml
vendored
|
|
@ -1,55 +0,0 @@
|
|||
# CI workflow for library packages (host-uk/core-*, etc.)
|
||||
# Copy this to .github/workflows/ci.yml in library repos
|
||||
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
php: [8.2, 8.3, 8.4]
|
||||
laravel: [11.*, 12.*]
|
||||
exclude:
|
||||
- php: 8.2
|
||||
laravel: 12.*
|
||||
|
||||
name: PHP ${{ matrix.php }} / Laravel ${{ matrix.laravel }}
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite
|
||||
coverage: pcov
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
composer require "laravel/framework:${{ matrix.laravel }}" --no-interaction --no-update
|
||||
composer update --prefer-dist --no-interaction --no-progress
|
||||
|
||||
- name: Run Pint
|
||||
run: vendor/bin/pint --test
|
||||
|
||||
- name: Run tests
|
||||
run: vendor/bin/pest --ci --coverage --coverage-clover coverage.xml
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
if: matrix.php == '8.3' && matrix.laravel == '12.*'
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
files: coverage.xml
|
||||
fail_ci_if_error: false
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
40
.github/package-workflows/release.yml
vendored
40
.github/package-workflows/release.yml
vendored
|
|
@ -1,40 +0,0 @@
|
|||
# Release workflow for library packages
|
||||
# Copy this to .github/workflows/release.yml in library repos
|
||||
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
name: Create Release
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Generate changelog
|
||||
id: changelog
|
||||
uses: orhun/git-cliff-action@v3
|
||||
with:
|
||||
config: cliff.toml
|
||||
args: --latest --strip header
|
||||
env:
|
||||
OUTPUT: CHANGELOG.md
|
||||
|
||||
- name: Create release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
body_path: CHANGELOG.md
|
||||
generate_release_notes: true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
56
Boot.php
Normal file
56
Boot.php
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Tenant;
|
||||
|
||||
use Core\Events\AdminPanelBooting;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
/**
|
||||
* Tenant Module Boot (Host UK extension).
|
||||
*
|
||||
* Extends core Tenant module with Host UK specific admin features:
|
||||
* - Team Manager
|
||||
* - Member Manager
|
||||
*/
|
||||
class Boot extends ServiceProvider
|
||||
{
|
||||
protected string $moduleName = 'tenant';
|
||||
|
||||
/**
|
||||
* Events this module listens to for lazy loading.
|
||||
*
|
||||
* @var array<class-string, string>
|
||||
*/
|
||||
public static array $listens = [
|
||||
AdminPanelBooting::class => 'onAdminPanel',
|
||||
];
|
||||
|
||||
public function register(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
public function boot(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Event-driven handlers
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function onAdminPanel(AdminPanelBooting $event): void
|
||||
{
|
||||
$event->views($this->moduleName, __DIR__.'/View/Blade');
|
||||
|
||||
if (file_exists(__DIR__.'/Routes/admin.php')) {
|
||||
$event->routes(fn () => require __DIR__.'/Routes/admin.php');
|
||||
}
|
||||
|
||||
// Admin components
|
||||
$event->livewire('tenant.admin.team-manager', View\Modal\Admin\TeamManager::class);
|
||||
$event->livewire('tenant.admin.member-manager', View\Modal\Admin\MemberManager::class);
|
||||
}
|
||||
}
|
||||
|
|
@ -125,7 +125,7 @@ The module uses the Core PHP configuration system. Key settings can be configure
|
|||
## Documentation
|
||||
|
||||
- [Core PHP Framework](https://github.com/host-uk/core-php)
|
||||
- [Getting Started Guide](https://host-uk.github.io/core-php/guide/)
|
||||
- [Getting Started Guide](https://core.help/guide/)
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
|||
24
Routes/admin.php
Normal file
24
Routes/admin.php
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Tenant Admin Routes
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Routes for workspace team and member management in the admin panel.
|
||||
|
|
||||
*/
|
||||
|
||||
Route::middleware(['web', 'auth', 'admin.domain'])->prefix('admin/tenant')->name('hub.admin.tenant.')->group(function () {
|
||||
// Team Manager
|
||||
Route::get('/teams', \Core\Tenant\View\Modal\Admin\TeamManager::class)
|
||||
->name('teams');
|
||||
|
||||
// Member Manager
|
||||
Route::get('/members', \Core\Tenant\View\Modal\Admin\MemberManager::class)
|
||||
->name('members');
|
||||
});
|
||||
370
View/Blade/admin/member-manager.blade.php
Normal file
370
View/Blade/admin/member-manager.blade.php
Normal file
|
|
@ -0,0 +1,370 @@
|
|||
<admin:module title="{{ __('tenant::tenant.admin.member_manager.title') }}" subtitle="{{ __('tenant::tenant.admin.member_manager.subtitle') }}">
|
||||
<admin:flash />
|
||||
|
||||
{{-- Stats cards --}}
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-sm p-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="p-2 rounded-lg bg-blue-100 dark:bg-blue-900/30">
|
||||
<flux:icon name="users" class="size-5 text-blue-600 dark:text-blue-400" />
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-2xl font-semibold text-gray-900 dark:text-gray-100">{{ number_format($this->stats['total_members']) }}</div>
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400">{{ __('tenant::tenant.admin.member_manager.stats.total_members') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-sm p-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="p-2 rounded-lg bg-violet-100 dark:bg-violet-900/30">
|
||||
<flux:icon name="user-group" class="size-5 text-violet-600 dark:text-violet-400" />
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-2xl font-semibold text-gray-900 dark:text-gray-100">{{ number_format($this->stats['with_team']) }}</div>
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400">{{ __('tenant::tenant.admin.member_manager.stats.with_team') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-sm p-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="p-2 rounded-lg bg-amber-100 dark:bg-amber-900/30">
|
||||
<flux:icon name="adjustments-horizontal" class="size-5 text-amber-600 dark:text-amber-400" />
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-2xl font-semibold text-gray-900 dark:text-gray-100">{{ number_format($this->stats['with_custom_permissions']) }}</div>
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400">{{ __('tenant::tenant.admin.member_manager.stats.with_custom') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<admin:filter-bar cols="3">
|
||||
<admin:search model="search" placeholder="{{ __('tenant::tenant.admin.member_manager.search.placeholder') }}" />
|
||||
<admin:filter model="workspaceFilter" :options="$this->workspaces" placeholder="{{ __('tenant::tenant.admin.member_manager.filter.all_workspaces') }}" />
|
||||
<admin:filter model="teamFilter" :options="$this->teamsForFilter" placeholder="{{ __('tenant::tenant.admin.member_manager.filter.all_teams') }}" />
|
||||
</admin:filter-bar>
|
||||
|
||||
{{-- Bulk action bar --}}
|
||||
@if(count($selected) > 0)
|
||||
<div class="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg p-3 mb-4 flex items-center justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<flux:icon name="check-circle" class="text-blue-600 size-5" />
|
||||
<span class="text-sm font-medium text-blue-700 dark:text-blue-300">
|
||||
{{ __('tenant::tenant.admin.member_manager.bulk.selected', ['count' => count($selected)]) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
@if($workspaceFilter)
|
||||
<flux:button wire:click="openBulkAssignModal" size="sm" variant="ghost" icon="user-group">
|
||||
{{ __('tenant::tenant.admin.member_manager.bulk.assign_team') }}
|
||||
</flux:button>
|
||||
@endif
|
||||
<flux:button wire:click="bulkRemoveFromTeam" wire:confirm="{{ __('tenant::tenant.admin.member_manager.confirm.bulk_remove_team') }}" size="sm" variant="ghost" icon="user-minus">
|
||||
{{ __('tenant::tenant.admin.member_manager.bulk.remove_team') }}
|
||||
</flux:button>
|
||||
<flux:button wire:click="bulkClearPermissions" wire:confirm="{{ __('tenant::tenant.admin.member_manager.confirm.bulk_clear_permissions') }}" size="sm" variant="ghost" icon="shield-exclamation">
|
||||
{{ __('tenant::tenant.admin.member_manager.bulk.clear_permissions') }}
|
||||
</flux:button>
|
||||
<flux:button wire:click="clearSelection" size="sm" variant="ghost" icon="x-mark">
|
||||
{{ __('tenant::tenant.admin.member_manager.bulk.clear') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Members table --}}
|
||||
@if($this->members->isEmpty())
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-sm">
|
||||
<div class="text-center py-16 px-6">
|
||||
<div class="mx-auto size-16 rounded-full bg-zinc-100 dark:bg-zinc-800 flex items-center justify-center mb-4">
|
||||
<flux:icon name="users" class="size-8 text-zinc-400 dark:text-zinc-500" />
|
||||
</div>
|
||||
<flux:heading size="lg" class="text-gray-900 dark:text-gray-100">{{ __('tenant::tenant.admin.member_manager.empty_state.title') }}</flux:heading>
|
||||
<flux:text class="mt-2 text-zinc-500 dark:text-zinc-400 max-w-md mx-auto">{{ __('tenant::tenant.admin.member_manager.empty_state.description') }}</flux:text>
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
<div class="overflow-hidden rounded-lg bg-white shadow-sm dark:bg-gray-800">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full">
|
||||
<thead class="bg-gray-50 dark:bg-gray-700">
|
||||
<tr>
|
||||
<th class="px-4 py-3 text-left">
|
||||
<flux:checkbox wire:model.live="selectAll" />
|
||||
</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">{{ __('tenant::tenant.admin.member_manager.columns.member') }}</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">{{ __('tenant::tenant.admin.member_manager.columns.workspace') }}</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">{{ __('tenant::tenant.admin.member_manager.columns.team') }}</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">{{ __('tenant::tenant.admin.member_manager.columns.role') }}</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">{{ __('tenant::tenant.admin.member_manager.columns.permissions') }}</th>
|
||||
<th class="px-6 py-3 text-center text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">{{ __('tenant::tenant.admin.member_manager.columns.actions') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 bg-white dark:divide-gray-700 dark:bg-gray-800">
|
||||
@foreach($this->members as $member)
|
||||
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50">
|
||||
{{-- Checkbox --}}
|
||||
<td class="whitespace-nowrap px-4 py-4">
|
||||
<flux:checkbox wire:model.live="selected" value="{{ $member->id }}" />
|
||||
</td>
|
||||
|
||||
{{-- Member info --}}
|
||||
<td class="whitespace-nowrap px-6 py-4">
|
||||
<div class="flex items-center gap-3">
|
||||
@if($member->user?->avatar_url)
|
||||
<img src="{{ $member->user->avatar_url }}" alt="" class="size-8 rounded-full" />
|
||||
@else
|
||||
<div class="size-8 rounded-full bg-gray-200 dark:bg-gray-700 flex items-center justify-center">
|
||||
<flux:icon name="user" class="size-4 text-gray-500" />
|
||||
</div>
|
||||
@endif
|
||||
<div>
|
||||
<div class="font-medium text-gray-900 dark:text-gray-100">{{ $member->user?->name ?? __('tenant::tenant.common.unknown') }}</div>
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400">{{ $member->user?->email }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
{{-- Workspace --}}
|
||||
<td class="whitespace-nowrap px-6 py-4">
|
||||
<div class="font-medium text-gray-900 dark:text-gray-100">{{ $member->workspace?->name ?? __('tenant::tenant.common.na') }}</div>
|
||||
</td>
|
||||
|
||||
{{-- Team --}}
|
||||
<td class="whitespace-nowrap px-6 py-4">
|
||||
@if($member->team)
|
||||
<flux:badge size="sm" color="{{ $member->team->colour }}">
|
||||
{{ $member->team->name }}
|
||||
</flux:badge>
|
||||
@else
|
||||
<span class="text-sm text-gray-400">{{ __('tenant::tenant.admin.member_manager.labels.no_team') }}</span>
|
||||
@endif
|
||||
</td>
|
||||
|
||||
{{-- Legacy role --}}
|
||||
<td class="whitespace-nowrap px-6 py-4">
|
||||
<span class="text-sm text-gray-500 dark:text-gray-400 capitalize">{{ $member->role }}</span>
|
||||
</td>
|
||||
|
||||
{{-- Custom permissions indicator --}}
|
||||
<td class="whitespace-nowrap px-6 py-4">
|
||||
@php
|
||||
$customPerms = $member->custom_permissions ?? [];
|
||||
$grantCount = count(array_filter($customPerms, fn($p) => !str_starts_with($p, '-')));
|
||||
$revokeCount = count(array_filter($customPerms, fn($p) => str_starts_with($p, '-')));
|
||||
@endphp
|
||||
@if(!empty($customPerms))
|
||||
<div class="flex items-center gap-1">
|
||||
@if($grantCount > 0)
|
||||
<flux:badge size="sm" color="green">+{{ $grantCount }}</flux:badge>
|
||||
@endif
|
||||
@if($revokeCount > 0)
|
||||
<flux:badge size="sm" color="red">-{{ $revokeCount }}</flux:badge>
|
||||
@endif
|
||||
</div>
|
||||
@else
|
||||
<span class="text-sm text-gray-400">{{ __('tenant::tenant.admin.member_manager.labels.inherited') }}</span>
|
||||
@endif
|
||||
</td>
|
||||
|
||||
{{-- Actions --}}
|
||||
<td class="whitespace-nowrap px-6 py-4 text-center">
|
||||
<flux:dropdown align="end">
|
||||
<flux:button variant="ghost" icon="ellipsis-vertical" size="sm" square />
|
||||
<flux:menu>
|
||||
<flux:menu.item wire:click="openAssignModal({{ $member->id }})" icon="user-group">
|
||||
{{ __('tenant::tenant.admin.member_manager.actions.assign_team') }}
|
||||
</flux:menu.item>
|
||||
<flux:menu.item wire:click="openPermissionsModal({{ $member->id }})" icon="key">
|
||||
{{ __('tenant::tenant.admin.member_manager.actions.custom_permissions') }}
|
||||
</flux:menu.item>
|
||||
@if($member->team_id)
|
||||
<flux:menu.item wire:click="removeFromTeam({{ $member->id }})" icon="user-minus">
|
||||
{{ __('tenant::tenant.admin.member_manager.actions.remove_from_team') }}
|
||||
</flux:menu.item>
|
||||
@endif
|
||||
@if(!empty($member->custom_permissions))
|
||||
<flux:menu.separator />
|
||||
<flux:menu.item wire:click="clearPermissions({{ $member->id }})" wire:confirm="{{ __('tenant::tenant.admin.member_manager.confirm.clear_permissions') }}" icon="shield-exclamation" variant="danger">
|
||||
{{ __('tenant::tenant.admin.member_manager.actions.clear_permissions') }}
|
||||
</flux:menu.item>
|
||||
@endif
|
||||
</flux:menu>
|
||||
</flux:dropdown>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@if($this->members->hasPages())
|
||||
<div class="border-t border-gray-200 px-6 py-4 dark:border-gray-700">
|
||||
{{ $this->members->links() }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Assign to Team Modal --}}
|
||||
<core:modal wire:model="showAssignModal" class="max-w-md">
|
||||
<core:heading size="lg">
|
||||
{{ __('tenant::tenant.admin.member_manager.assign_modal.title') }}
|
||||
</core:heading>
|
||||
|
||||
<form wire:submit="saveAssignment" class="space-y-4 mt-4">
|
||||
<core:select
|
||||
wire:model="assignTeamId"
|
||||
label="{{ __('tenant::tenant.admin.member_manager.assign_modal.team') }}"
|
||||
>
|
||||
<option value="">{{ __('tenant::tenant.admin.member_manager.assign_modal.no_team') }}</option>
|
||||
@foreach($this->teamsForAssignment as $team)
|
||||
<option value="{{ $team->id }}">{{ $team->name }}</option>
|
||||
@endforeach
|
||||
</core:select>
|
||||
|
||||
<div class="flex justify-end gap-2 pt-4">
|
||||
<core:button variant="ghost" wire:click="closeAssignModal">
|
||||
{{ __('tenant::tenant.admin.member_manager.modal.actions.cancel') }}
|
||||
</core:button>
|
||||
<core:button type="submit" variant="primary">
|
||||
{{ __('tenant::tenant.admin.member_manager.modal.actions.save') }}
|
||||
</core:button>
|
||||
</div>
|
||||
</form>
|
||||
</core:modal>
|
||||
|
||||
{{-- Custom Permissions Modal --}}
|
||||
<core:modal wire:model="showPermissionsModal" class="max-w-3xl">
|
||||
<core:heading size="lg">
|
||||
{{ __('tenant::tenant.admin.member_manager.permissions_modal.title') }}
|
||||
</core:heading>
|
||||
|
||||
@if($this->memberForPermissions)
|
||||
<div class="mt-4 space-y-6">
|
||||
{{-- Member info --}}
|
||||
<div class="flex items-center gap-3 p-3 bg-gray-50 dark:bg-gray-700/50 rounded-lg">
|
||||
@if($this->memberForPermissions->user?->avatar_url)
|
||||
<img src="{{ $this->memberForPermissions->user->avatar_url }}" alt="" class="size-10 rounded-full" />
|
||||
@else
|
||||
<div class="size-10 rounded-full bg-gray-200 dark:bg-gray-700 flex items-center justify-center">
|
||||
<flux:icon name="user" class="size-5 text-gray-500" />
|
||||
</div>
|
||||
@endif
|
||||
<div>
|
||||
<div class="font-medium text-gray-900 dark:text-gray-100">{{ $this->memberForPermissions->user?->name }}</div>
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ __('tenant::tenant.admin.member_manager.permissions_modal.team_permissions', ['team' => $this->memberForPermissions->team?->name ?? __('tenant::tenant.common.none')]) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-sm text-gray-600 dark:text-gray-400 bg-amber-50 dark:bg-amber-900/20 p-3 rounded-lg">
|
||||
<flux:icon name="information-circle" class="inline size-4 mr-1" />
|
||||
{{ __('tenant::tenant.admin.member_manager.permissions_modal.description') }}
|
||||
</div>
|
||||
|
||||
<form wire:submit="savePermissions" class="space-y-6">
|
||||
{{-- Granted permissions (additions) --}}
|
||||
<div class="space-y-3">
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
<flux:icon name="plus-circle" class="inline size-4 mr-1 text-green-600" />
|
||||
{{ __('tenant::tenant.admin.member_manager.permissions_modal.grant_label') }}
|
||||
</label>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3 max-h-48 overflow-y-auto pr-2">
|
||||
@foreach($this->permissionGroups as $groupKey => $group)
|
||||
<div class="bg-green-50 dark:bg-green-900/20 rounded-lg p-3 border border-green-200 dark:border-green-800">
|
||||
<div class="font-medium text-green-900 dark:text-green-100 mb-2 text-sm">{{ $group['label'] }}</div>
|
||||
<div class="space-y-1.5">
|
||||
@foreach($group['permissions'] as $permKey => $permLabel)
|
||||
<label class="flex items-center gap-2 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
wire:model="grantedPermissions"
|
||||
value="{{ $permKey }}"
|
||||
class="rounded border-green-300 text-green-600 focus:ring-green-500"
|
||||
/>
|
||||
<span class="text-xs text-green-800 dark:text-green-200">{{ $permLabel }}</span>
|
||||
</label>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Revoked permissions (removals) --}}
|
||||
<div class="space-y-3">
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
<flux:icon name="minus-circle" class="inline size-4 mr-1 text-red-600" />
|
||||
{{ __('tenant::tenant.admin.member_manager.permissions_modal.revoke_label') }}
|
||||
</label>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3 max-h-48 overflow-y-auto pr-2">
|
||||
@foreach($this->permissionGroups as $groupKey => $group)
|
||||
<div class="bg-red-50 dark:bg-red-900/20 rounded-lg p-3 border border-red-200 dark:border-red-800">
|
||||
<div class="font-medium text-red-900 dark:text-red-100 mb-2 text-sm">{{ $group['label'] }}</div>
|
||||
<div class="space-y-1.5">
|
||||
@foreach($group['permissions'] as $permKey => $permLabel)
|
||||
<label class="flex items-center gap-2 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
wire:model="revokedPermissions"
|
||||
value="{{ $permKey }}"
|
||||
class="rounded border-red-300 text-red-600 focus:ring-red-500"
|
||||
/>
|
||||
<span class="text-xs text-red-800 dark:text-red-200">{{ $permLabel }}</span>
|
||||
</label>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end gap-2 pt-4">
|
||||
<core:button variant="ghost" wire:click="closePermissionsModal">
|
||||
{{ __('tenant::tenant.admin.member_manager.modal.actions.cancel') }}
|
||||
</core:button>
|
||||
<core:button type="submit" variant="primary">
|
||||
{{ __('tenant::tenant.admin.member_manager.modal.actions.save') }}
|
||||
</core:button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@endif
|
||||
</core:modal>
|
||||
|
||||
{{-- Bulk Assign Modal --}}
|
||||
<core:modal wire:model="showBulkAssignModal" class="max-w-md">
|
||||
<core:heading size="lg">
|
||||
{{ __('tenant::tenant.admin.member_manager.bulk_assign_modal.title') }}
|
||||
</core:heading>
|
||||
|
||||
<form wire:submit="bulkAssignTeam" class="space-y-4 mt-4">
|
||||
<div class="bg-blue-50 dark:bg-blue-900/20 p-3 rounded-lg text-sm text-blue-700 dark:text-blue-300">
|
||||
{{ __('tenant::tenant.admin.member_manager.bulk_assign_modal.description', ['count' => count($selected)]) }}
|
||||
</div>
|
||||
|
||||
<core:select
|
||||
wire:model="bulkTeamId"
|
||||
label="{{ __('tenant::tenant.admin.member_manager.bulk_assign_modal.team') }}"
|
||||
>
|
||||
<option value="">{{ __('tenant::tenant.admin.member_manager.bulk_assign_modal.no_team') }}</option>
|
||||
@foreach($this->teamsForBulkAssignment as $team)
|
||||
<option value="{{ $team->id }}">{{ $team->name }}</option>
|
||||
@endforeach
|
||||
</core:select>
|
||||
|
||||
<div class="flex justify-end gap-2 pt-4">
|
||||
<core:button variant="ghost" wire:click="closeBulkAssignModal">
|
||||
{{ __('tenant::tenant.admin.member_manager.modal.actions.cancel') }}
|
||||
</core:button>
|
||||
<core:button type="submit" variant="primary">
|
||||
{{ __('tenant::tenant.admin.member_manager.modal.actions.assign') }}
|
||||
</core:button>
|
||||
</div>
|
||||
</form>
|
||||
</core:modal>
|
||||
</admin:module>
|
||||
276
View/Blade/admin/team-manager.blade.php
Normal file
276
View/Blade/admin/team-manager.blade.php
Normal file
|
|
@ -0,0 +1,276 @@
|
|||
<admin:module title="{{ __('tenant::tenant.admin.team_manager.title') }}" subtitle="{{ __('tenant::tenant.admin.team_manager.subtitle') }}">
|
||||
<admin:flash />
|
||||
|
||||
<x-slot:actions>
|
||||
<div class="flex items-center gap-2">
|
||||
@if($workspaceFilter)
|
||||
<flux:dropdown>
|
||||
<flux:button variant="ghost" icon="ellipsis-vertical" />
|
||||
<flux:menu>
|
||||
<flux:menu.item wire:click="seedDefaultTeams({{ $workspaceFilter }})" icon="sparkles">
|
||||
{{ __('tenant::tenant.admin.team_manager.actions.seed_defaults') }}
|
||||
</flux:menu.item>
|
||||
<flux:menu.item wire:click="migrateMembers({{ $workspaceFilter }})" icon="arrow-right-arrow-left">
|
||||
{{ __('tenant::tenant.admin.team_manager.actions.migrate_members') }}
|
||||
</flux:menu.item>
|
||||
</flux:menu>
|
||||
</flux:dropdown>
|
||||
@endif
|
||||
<flux:button wire:click="openCreateTeam" variant="primary" icon="plus">
|
||||
{{ __('tenant::tenant.admin.team_manager.actions.create_team') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
</x-slot:actions>
|
||||
|
||||
{{-- Stats cards --}}
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-sm p-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="p-2 rounded-lg bg-violet-100 dark:bg-violet-900/30">
|
||||
<flux:icon name="users" class="size-5 text-violet-600 dark:text-violet-400" />
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-2xl font-semibold text-gray-900 dark:text-gray-100">{{ number_format($this->stats['total_teams']) }}</div>
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400">{{ __('tenant::tenant.admin.team_manager.stats.total_teams') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-sm p-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="p-2 rounded-lg bg-blue-100 dark:bg-blue-900/30">
|
||||
<flux:icon name="user" class="size-5 text-blue-600 dark:text-blue-400" />
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-2xl font-semibold text-gray-900 dark:text-gray-100">{{ number_format($this->stats['total_members']) }}</div>
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400">{{ __('tenant::tenant.admin.team_manager.stats.total_members') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-sm p-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="p-2 rounded-lg bg-green-100 dark:bg-green-900/30">
|
||||
<flux:icon name="check-circle" class="size-5 text-green-600 dark:text-green-400" />
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-2xl font-semibold text-gray-900 dark:text-gray-100">{{ number_format($this->stats['members_with_team']) }}</div>
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400">{{ __('tenant::tenant.admin.team_manager.stats.members_assigned') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<admin:filter-bar cols="2">
|
||||
<admin:search model="search" placeholder="{{ __('tenant::tenant.admin.team_manager.search.placeholder') }}" />
|
||||
<admin:filter model="workspaceFilter" :options="$this->workspaces" placeholder="{{ __('tenant::tenant.admin.team_manager.filter.all_workspaces') }}" />
|
||||
</admin:filter-bar>
|
||||
|
||||
{{-- Teams table --}}
|
||||
@if($this->teams->isEmpty())
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-sm">
|
||||
<div class="text-center py-16 px-6">
|
||||
<div class="mx-auto size-16 rounded-full bg-zinc-100 dark:bg-zinc-800 flex items-center justify-center mb-4">
|
||||
<flux:icon name="users" class="size-8 text-zinc-400 dark:text-zinc-500" />
|
||||
</div>
|
||||
<flux:heading size="lg" class="text-gray-900 dark:text-gray-100">{{ __('tenant::tenant.admin.team_manager.empty_state.title') }}</flux:heading>
|
||||
<flux:text class="mt-2 text-zinc-500 dark:text-zinc-400 max-w-md mx-auto">{{ __('tenant::tenant.admin.team_manager.empty_state.description') }}</flux:text>
|
||||
<div class="mt-6 flex items-center justify-center gap-3">
|
||||
<flux:button wire:click="openCreateTeam" variant="primary" icon="plus">
|
||||
{{ __('tenant::tenant.admin.team_manager.actions.create_team') }}
|
||||
</flux:button>
|
||||
@if($workspaceFilter)
|
||||
<flux:button wire:click="seedDefaultTeams({{ $workspaceFilter }})" variant="ghost" icon="sparkles">
|
||||
{{ __('tenant::tenant.admin.team_manager.actions.seed_defaults') }}
|
||||
</flux:button>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
<div class="overflow-hidden rounded-lg bg-white shadow-sm dark:bg-gray-800">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full">
|
||||
<thead class="bg-gray-50 dark:bg-gray-700">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">{{ __('tenant::tenant.admin.team_manager.columns.team') }}</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">{{ __('tenant::tenant.admin.team_manager.columns.workspace') }}</th>
|
||||
<th class="px-6 py-3 text-center text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">{{ __('tenant::tenant.admin.team_manager.columns.members') }}</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">{{ __('tenant::tenant.admin.team_manager.columns.permissions') }}</th>
|
||||
<th class="px-6 py-3 text-center text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">{{ __('tenant::tenant.admin.team_manager.columns.actions') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 bg-white dark:divide-gray-700 dark:bg-gray-800">
|
||||
@foreach($this->teams as $team)
|
||||
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50">
|
||||
{{-- Team info --}}
|
||||
<td class="whitespace-nowrap px-6 py-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="size-8 rounded-lg bg-{{ $team->colour }}-500/20 flex items-center justify-center">
|
||||
<flux:icon name="users" class="size-4 text-{{ $team->colour }}-600 dark:text-{{ $team->colour }}-400" />
|
||||
</div>
|
||||
<div class="space-y-0.5">
|
||||
<div class="font-medium text-gray-900 dark:text-gray-100 flex items-center gap-2">
|
||||
{{ $team->name }}
|
||||
@if($team->is_system)
|
||||
<flux:badge size="sm" color="violet">{{ __('tenant::tenant.admin.team_manager.badges.system') }}</flux:badge>
|
||||
@endif
|
||||
@if($team->is_default)
|
||||
<flux:badge size="sm" color="blue">{{ __('tenant::tenant.admin.team_manager.badges.default') }}</flux:badge>
|
||||
@endif
|
||||
</div>
|
||||
@if($team->description)
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400 truncate max-w-xs">{{ $team->description }}</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
{{-- Workspace --}}
|
||||
<td class="whitespace-nowrap px-6 py-4">
|
||||
<div class="font-medium text-gray-900 dark:text-gray-100">{{ $team->workspace?->name ?? __('tenant::tenant.common.na') }}</div>
|
||||
</td>
|
||||
|
||||
{{-- Member count --}}
|
||||
<td class="whitespace-nowrap px-6 py-4 text-center">
|
||||
<flux:badge size="sm" color="{{ $team->colour }}">{{ number_format($team->members_count) }}</flux:badge>
|
||||
</td>
|
||||
|
||||
{{-- Permissions count --}}
|
||||
<td class="whitespace-nowrap px-6 py-4">
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ count($team->permissions ?? []) }} {{ __('tenant::tenant.admin.team_manager.labels.permissions') }}
|
||||
</div>
|
||||
</td>
|
||||
|
||||
{{-- Actions --}}
|
||||
<td class="whitespace-nowrap px-6 py-4 text-center">
|
||||
<flux:dropdown align="end">
|
||||
<flux:button variant="ghost" icon="ellipsis-vertical" size="sm" square />
|
||||
<flux:menu>
|
||||
<flux:menu.item wire:click="openEditTeam({{ $team->id }})" icon="pencil">
|
||||
{{ __('tenant::tenant.admin.team_manager.actions.edit') }}
|
||||
</flux:menu.item>
|
||||
<flux:menu.item href="{{ route('hub.admin.tenant.members', ['workspaceFilter' => $team->workspace_id, 'teamFilter' => $team->id]) }}" icon="users">
|
||||
{{ __('tenant::tenant.admin.team_manager.actions.view_members') }}
|
||||
</flux:menu.item>
|
||||
@unless($team->is_system)
|
||||
<flux:menu.separator />
|
||||
<flux:menu.item wire:click="deleteTeam({{ $team->id }})" wire:confirm="{{ __('tenant::tenant.admin.team_manager.confirm.delete_team') }}" icon="trash" variant="danger">
|
||||
{{ __('tenant::tenant.admin.team_manager.actions.delete') }}
|
||||
</flux:menu.item>
|
||||
@endunless
|
||||
</flux:menu>
|
||||
</flux:dropdown>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@if($this->teams->hasPages())
|
||||
<div class="border-t border-gray-200 px-6 py-4 dark:border-gray-700">
|
||||
{{ $this->teams->links() }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Create/Edit Team Modal --}}
|
||||
<core:modal wire:model="showTeamModal" class="max-w-2xl">
|
||||
<core:heading size="lg">
|
||||
{{ $editingTeamId ? __('tenant::tenant.admin.team_manager.modal.title_edit') : __('tenant::tenant.admin.team_manager.modal.title_create') }}
|
||||
</core:heading>
|
||||
|
||||
<form wire:submit="saveTeam" class="space-y-4 mt-4">
|
||||
<core:select
|
||||
wire:model="teamWorkspaceId"
|
||||
label="{{ __('tenant::tenant.admin.team_manager.modal.fields.workspace') }}"
|
||||
required
|
||||
>
|
||||
<option value="">{{ __('tenant::tenant.admin.team_manager.modal.fields.select_workspace') }}</option>
|
||||
@foreach($this->workspaces as $workspace)
|
||||
<option value="{{ $workspace->id }}">{{ $workspace->name }}</option>
|
||||
@endforeach
|
||||
</core:select>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<core:input
|
||||
wire:model="teamName"
|
||||
label="{{ __('tenant::tenant.admin.team_manager.modal.fields.name') }}"
|
||||
placeholder="{{ __('tenant::tenant.admin.team_manager.modal.fields.name_placeholder') }}"
|
||||
required
|
||||
/>
|
||||
|
||||
@unless($editingTeamId)
|
||||
<core:input
|
||||
wire:model="teamSlug"
|
||||
label="{{ __('tenant::tenant.admin.team_manager.modal.fields.slug') }}"
|
||||
placeholder="{{ __('tenant::tenant.admin.team_manager.modal.fields.slug_placeholder') }}"
|
||||
description="{{ __('tenant::tenant.admin.team_manager.modal.fields.slug_description') }}"
|
||||
/>
|
||||
@endunless
|
||||
</div>
|
||||
|
||||
<core:textarea
|
||||
wire:model="teamDescription"
|
||||
label="{{ __('tenant::tenant.admin.team_manager.modal.fields.description') }}"
|
||||
rows="2"
|
||||
/>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<core:select
|
||||
wire:model="teamColour"
|
||||
label="{{ __('tenant::tenant.admin.team_manager.modal.fields.colour') }}"
|
||||
>
|
||||
@foreach($this->colourOptions as $value => $label)
|
||||
<option value="{{ $value }}">{{ $label }}</option>
|
||||
@endforeach
|
||||
</core:select>
|
||||
|
||||
<div class="flex items-end pb-1">
|
||||
<core:checkbox
|
||||
wire:model="teamIsDefault"
|
||||
label="{{ __('tenant::tenant.admin.team_manager.modal.fields.is_default') }}"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Permissions matrix --}}
|
||||
<div class="space-y-4">
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
{{ __('tenant::tenant.admin.team_manager.modal.fields.permissions') }}
|
||||
</label>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 max-h-72 overflow-y-auto pr-2">
|
||||
@foreach($this->permissionGroups as $groupKey => $group)
|
||||
<div class="bg-gray-50 dark:bg-gray-700/50 rounded-lg p-3">
|
||||
<div class="font-medium text-gray-900 dark:text-gray-100 mb-2 text-sm">{{ $group['label'] }}</div>
|
||||
<div class="space-y-2">
|
||||
@foreach($group['permissions'] as $permKey => $permLabel)
|
||||
<label class="flex items-center gap-2 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
wire:model="teamPermissions"
|
||||
value="{{ $permKey }}"
|
||||
class="rounded border-gray-300 text-violet-600 focus:ring-violet-500"
|
||||
/>
|
||||
<span class="text-sm text-gray-700 dark:text-gray-300">{{ $permLabel }}</span>
|
||||
</label>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end gap-2 pt-4">
|
||||
<core:button variant="ghost" wire:click="closeTeamModal">
|
||||
{{ __('tenant::tenant.admin.team_manager.modal.actions.cancel') }}
|
||||
</core:button>
|
||||
<core:button type="submit" variant="primary">
|
||||
{{ $editingTeamId ? __('tenant::tenant.admin.team_manager.modal.actions.update') : __('tenant::tenant.admin.team_manager.modal.actions.create') }}
|
||||
</core:button>
|
||||
</div>
|
||||
</form>
|
||||
</core:modal>
|
||||
</admin:module>
|
||||
404
View/Modal/Admin/MemberManager.php
Normal file
404
View/Modal/Admin/MemberManager.php
Normal file
|
|
@ -0,0 +1,404 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Tenant\View\Modal\Admin;
|
||||
|
||||
use Core\Core\Tenant\Models\Workspace;
|
||||
use Core\Core\Tenant\Models\WorkspaceMember;
|
||||
use Core\Core\Tenant\Models\WorkspaceTeam;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Livewire\Attributes\Computed;
|
||||
use Livewire\Component;
|
||||
use Livewire\WithPagination;
|
||||
|
||||
class MemberManager extends Component
|
||||
{
|
||||
use WithPagination;
|
||||
|
||||
// Filters
|
||||
public string $search = '';
|
||||
|
||||
public ?int $workspaceFilter = null;
|
||||
|
||||
public ?int $teamFilter = null;
|
||||
|
||||
// Assign to team modal
|
||||
public bool $showAssignModal = false;
|
||||
|
||||
public ?int $assignMemberId = null;
|
||||
|
||||
public ?int $assignTeamId = null;
|
||||
|
||||
// Custom permissions modal
|
||||
public bool $showPermissionsModal = false;
|
||||
|
||||
public ?int $permissionsMemberId = null;
|
||||
|
||||
public array $grantedPermissions = [];
|
||||
|
||||
public array $revokedPermissions = [];
|
||||
|
||||
// Bulk selection
|
||||
public array $selected = [];
|
||||
|
||||
public bool $selectAll = false;
|
||||
|
||||
// Bulk assign modal
|
||||
public bool $showBulkAssignModal = false;
|
||||
|
||||
public ?int $bulkTeamId = null;
|
||||
|
||||
public function mount(?int $workspaceFilter = null, ?int $teamFilter = null): void
|
||||
{
|
||||
$this->checkHadesAccess();
|
||||
$this->workspaceFilter = $workspaceFilter;
|
||||
$this->teamFilter = $teamFilter;
|
||||
}
|
||||
|
||||
public function updatingSearch(): void
|
||||
{
|
||||
$this->resetPage();
|
||||
$this->clearSelection();
|
||||
}
|
||||
|
||||
public function updatingWorkspaceFilter(): void
|
||||
{
|
||||
$this->resetPage();
|
||||
$this->clearSelection();
|
||||
$this->teamFilter = null;
|
||||
}
|
||||
|
||||
public function updatingTeamFilter(): void
|
||||
{
|
||||
$this->resetPage();
|
||||
$this->clearSelection();
|
||||
}
|
||||
|
||||
public function updatedSelectAll(bool $value): void
|
||||
{
|
||||
$this->selected = $value
|
||||
? $this->members->pluck('id')->map(fn ($id) => (string) $id)->toArray()
|
||||
: [];
|
||||
}
|
||||
|
||||
public function clearSelection(): void
|
||||
{
|
||||
$this->selected = [];
|
||||
$this->selectAll = false;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Team Assignment
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
public function openAssignModal(int $memberId): void
|
||||
{
|
||||
$member = WorkspaceMember::findOrFail($memberId);
|
||||
|
||||
$this->assignMemberId = $memberId;
|
||||
$this->assignTeamId = $member->team_id;
|
||||
$this->showAssignModal = true;
|
||||
}
|
||||
|
||||
public function saveAssignment(): void
|
||||
{
|
||||
$member = WorkspaceMember::findOrFail($this->assignMemberId);
|
||||
|
||||
// Validate team belongs to same workspace
|
||||
if ($this->assignTeamId) {
|
||||
$team = WorkspaceTeam::where('id', $this->assignTeamId)
|
||||
->where('workspace_id', $member->workspace_id)
|
||||
->first();
|
||||
|
||||
if (! $team) {
|
||||
session()->flash('error', __('tenant::tenant.admin.member_manager.messages.invalid_team'));
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$member->update(['team_id' => $this->assignTeamId]);
|
||||
|
||||
session()->flash('message', __('tenant::tenant.admin.member_manager.messages.team_assigned'));
|
||||
$this->closeAssignModal();
|
||||
}
|
||||
|
||||
public function removeFromTeam(int $memberId): void
|
||||
{
|
||||
$member = WorkspaceMember::findOrFail($memberId);
|
||||
$member->update(['team_id' => null]);
|
||||
|
||||
session()->flash('message', __('tenant::tenant.admin.member_manager.messages.removed_from_team'));
|
||||
}
|
||||
|
||||
public function closeAssignModal(): void
|
||||
{
|
||||
$this->showAssignModal = false;
|
||||
$this->assignMemberId = null;
|
||||
$this->assignTeamId = null;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Custom Permissions
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
public function openPermissionsModal(int $memberId): void
|
||||
{
|
||||
$member = WorkspaceMember::findOrFail($memberId);
|
||||
|
||||
$this->permissionsMemberId = $memberId;
|
||||
$this->grantedPermissions = [];
|
||||
$this->revokedPermissions = [];
|
||||
|
||||
// Parse existing custom permissions
|
||||
foreach ($member->custom_permissions ?? [] as $permission) {
|
||||
if (str_starts_with($permission, '-')) {
|
||||
$this->revokedPermissions[] = substr($permission, 1);
|
||||
} elseif (str_starts_with($permission, '+')) {
|
||||
$this->grantedPermissions[] = substr($permission, 1);
|
||||
} else {
|
||||
$this->grantedPermissions[] = $permission;
|
||||
}
|
||||
}
|
||||
|
||||
$this->showPermissionsModal = true;
|
||||
}
|
||||
|
||||
public function savePermissions(): void
|
||||
{
|
||||
$member = WorkspaceMember::findOrFail($this->permissionsMemberId);
|
||||
|
||||
// Build custom permissions array
|
||||
$customPermissions = [];
|
||||
|
||||
foreach ($this->grantedPermissions as $permission) {
|
||||
$customPermissions[] = '+'.$permission;
|
||||
}
|
||||
|
||||
foreach ($this->revokedPermissions as $permission) {
|
||||
$customPermissions[] = '-'.$permission;
|
||||
}
|
||||
|
||||
$member->update([
|
||||
'custom_permissions' => ! empty($customPermissions) ? $customPermissions : null,
|
||||
]);
|
||||
|
||||
session()->flash('message', __('tenant::tenant.admin.member_manager.messages.permissions_updated'));
|
||||
$this->closePermissionsModal();
|
||||
}
|
||||
|
||||
public function clearPermissions(int $memberId): void
|
||||
{
|
||||
$member = WorkspaceMember::findOrFail($memberId);
|
||||
$member->update(['custom_permissions' => null]);
|
||||
|
||||
session()->flash('message', __('tenant::tenant.admin.member_manager.messages.permissions_cleared'));
|
||||
}
|
||||
|
||||
public function closePermissionsModal(): void
|
||||
{
|
||||
$this->showPermissionsModal = false;
|
||||
$this->permissionsMemberId = null;
|
||||
$this->grantedPermissions = [];
|
||||
$this->revokedPermissions = [];
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Bulk Operations
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
public function openBulkAssignModal(): void
|
||||
{
|
||||
$this->bulkTeamId = null;
|
||||
$this->showBulkAssignModal = true;
|
||||
}
|
||||
|
||||
public function closeBulkAssignModal(): void
|
||||
{
|
||||
$this->showBulkAssignModal = false;
|
||||
$this->bulkTeamId = null;
|
||||
}
|
||||
|
||||
public function bulkAssignTeam(): void
|
||||
{
|
||||
if (empty($this->selected)) {
|
||||
session()->flash('error', __('tenant::tenant.admin.member_manager.messages.no_members_selected'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate team exists
|
||||
if ($this->bulkTeamId) {
|
||||
$team = WorkspaceTeam::find($this->bulkTeamId);
|
||||
if (! $team) {
|
||||
session()->flash('error', __('tenant::tenant.admin.member_manager.messages.invalid_team'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Update only members from same workspace as the team
|
||||
$updated = WorkspaceMember::whereIn('id', $this->selected)
|
||||
->where('workspace_id', $team->workspace_id)
|
||||
->update(['team_id' => $this->bulkTeamId]);
|
||||
} else {
|
||||
// Remove from teams
|
||||
$updated = WorkspaceMember::whereIn('id', $this->selected)
|
||||
->update(['team_id' => null]);
|
||||
}
|
||||
|
||||
session()->flash('message', __('tenant::tenant.admin.member_manager.messages.bulk_team_assigned', ['count' => $updated]));
|
||||
$this->closeBulkAssignModal();
|
||||
$this->clearSelection();
|
||||
}
|
||||
|
||||
public function bulkRemoveFromTeam(): void
|
||||
{
|
||||
if (empty($this->selected)) {
|
||||
session()->flash('error', __('tenant::tenant.admin.member_manager.messages.no_members_selected'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$updated = WorkspaceMember::whereIn('id', $this->selected)
|
||||
->update(['team_id' => null]);
|
||||
|
||||
session()->flash('message', __('tenant::tenant.admin.member_manager.messages.bulk_removed_from_team', ['count' => $updated]));
|
||||
$this->clearSelection();
|
||||
}
|
||||
|
||||
public function bulkClearPermissions(): void
|
||||
{
|
||||
if (empty($this->selected)) {
|
||||
session()->flash('error', __('tenant::tenant.admin.member_manager.messages.no_members_selected'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$updated = WorkspaceMember::whereIn('id', $this->selected)
|
||||
->update(['custom_permissions' => null]);
|
||||
|
||||
session()->flash('message', __('tenant::tenant.admin.member_manager.messages.bulk_permissions_cleared', ['count' => $updated]));
|
||||
$this->clearSelection();
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Computed Properties
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[Computed]
|
||||
public function members(): \Illuminate\Contracts\Pagination\LengthAwarePaginator
|
||||
{
|
||||
return WorkspaceMember::query()
|
||||
->with(['user', 'workspace', 'team', 'inviter'])
|
||||
->when($this->search, function ($query) {
|
||||
$query->whereHas('user', function ($q) {
|
||||
$q->where('name', 'like', "%{$this->search}%")
|
||||
->orWhere('email', 'like', "%{$this->search}%");
|
||||
});
|
||||
})
|
||||
->when($this->workspaceFilter, function ($query) {
|
||||
$query->where('workspace_id', $this->workspaceFilter);
|
||||
})
|
||||
->when($this->teamFilter, function ($query) {
|
||||
$query->where('team_id', $this->teamFilter);
|
||||
})
|
||||
->orderBy('created_at', 'desc')
|
||||
->paginate(20);
|
||||
}
|
||||
|
||||
#[Computed]
|
||||
public function workspaces(): \Illuminate\Database\Eloquent\Collection
|
||||
{
|
||||
return Workspace::orderBy('name')->get();
|
||||
}
|
||||
|
||||
#[Computed]
|
||||
public function teamsForFilter(): \Illuminate\Database\Eloquent\Collection
|
||||
{
|
||||
$query = WorkspaceTeam::query();
|
||||
|
||||
if ($this->workspaceFilter) {
|
||||
$query->where('workspace_id', $this->workspaceFilter);
|
||||
}
|
||||
|
||||
return $query->ordered()->get();
|
||||
}
|
||||
|
||||
#[Computed]
|
||||
public function teamsForAssignment(): \Illuminate\Database\Eloquent\Collection
|
||||
{
|
||||
if ($this->assignMemberId) {
|
||||
$member = WorkspaceMember::find($this->assignMemberId);
|
||||
if ($member) {
|
||||
return WorkspaceTeam::where('workspace_id', $member->workspace_id)
|
||||
->ordered()
|
||||
->get();
|
||||
}
|
||||
}
|
||||
|
||||
return new \Illuminate\Database\Eloquent\Collection;
|
||||
}
|
||||
|
||||
#[Computed]
|
||||
public function teamsForBulkAssignment(): \Illuminate\Database\Eloquent\Collection
|
||||
{
|
||||
// Only show teams from the current workspace filter
|
||||
if ($this->workspaceFilter) {
|
||||
return WorkspaceTeam::where('workspace_id', $this->workspaceFilter)
|
||||
->ordered()
|
||||
->get();
|
||||
}
|
||||
|
||||
return new \Illuminate\Database\Eloquent\Collection;
|
||||
}
|
||||
|
||||
#[Computed]
|
||||
public function permissionGroups(): array
|
||||
{
|
||||
return WorkspaceTeam::getAvailablePermissions();
|
||||
}
|
||||
|
||||
#[Computed]
|
||||
public function memberForPermissions(): ?WorkspaceMember
|
||||
{
|
||||
if ($this->permissionsMemberId) {
|
||||
return WorkspaceMember::with(['team'])->find($this->permissionsMemberId);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
#[Computed]
|
||||
public function stats(): array
|
||||
{
|
||||
$query = WorkspaceMember::query();
|
||||
|
||||
if ($this->workspaceFilter) {
|
||||
$query->where('workspace_id', $this->workspaceFilter);
|
||||
}
|
||||
|
||||
if ($this->teamFilter) {
|
||||
$query->where('team_id', $this->teamFilter);
|
||||
}
|
||||
|
||||
return [
|
||||
'total_members' => (clone $query)->count(),
|
||||
'with_team' => (clone $query)->whereNotNull('team_id')->count(),
|
||||
'with_custom_permissions' => (clone $query)->whereNotNull('custom_permissions')->count(),
|
||||
];
|
||||
}
|
||||
|
||||
private function checkHadesAccess(): void
|
||||
{
|
||||
if (! auth()->user()?->isHades()) {
|
||||
abort(403, __('tenant::tenant.errors.hades_required'));
|
||||
}
|
||||
}
|
||||
|
||||
public function render(): View
|
||||
{
|
||||
return view('tenant::admin.member-manager')
|
||||
->layout('hub::admin.layouts.app', ['title' => __('tenant::tenant.admin.member_manager.title')]);
|
||||
}
|
||||
}
|
||||
289
View/Modal/Admin/TeamManager.php
Normal file
289
View/Modal/Admin/TeamManager.php
Normal file
|
|
@ -0,0 +1,289 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Tenant\View\Modal\Admin;
|
||||
|
||||
use Core\Core\Tenant\Models\Workspace;
|
||||
use Core\Core\Tenant\Models\WorkspaceMember;
|
||||
use Core\Core\Tenant\Models\WorkspaceTeam;
|
||||
use Core\Core\Tenant\Services\WorkspaceTeamService;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Livewire\Attributes\Computed;
|
||||
use Livewire\Component;
|
||||
use Livewire\WithPagination;
|
||||
|
||||
class TeamManager extends Component
|
||||
{
|
||||
use WithPagination;
|
||||
|
||||
// Filters
|
||||
public string $search = '';
|
||||
|
||||
public ?int $workspaceFilter = null;
|
||||
|
||||
// Team modal
|
||||
public bool $showTeamModal = false;
|
||||
|
||||
public ?int $editingTeamId = null;
|
||||
|
||||
// Team form fields
|
||||
public string $teamName = '';
|
||||
|
||||
public string $teamSlug = '';
|
||||
|
||||
public string $teamDescription = '';
|
||||
|
||||
public array $teamPermissions = [];
|
||||
|
||||
public bool $teamIsDefault = false;
|
||||
|
||||
public string $teamColour = 'zinc';
|
||||
|
||||
public ?int $teamWorkspaceId = null;
|
||||
|
||||
// Bulk selection
|
||||
public array $selected = [];
|
||||
|
||||
public bool $selectAll = false;
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$this->checkHadesAccess();
|
||||
}
|
||||
|
||||
public function updatingSearch(): void
|
||||
{
|
||||
$this->resetPage();
|
||||
$this->clearSelection();
|
||||
}
|
||||
|
||||
public function updatingWorkspaceFilter(): void
|
||||
{
|
||||
$this->resetPage();
|
||||
$this->clearSelection();
|
||||
}
|
||||
|
||||
public function updatedSelectAll(bool $value): void
|
||||
{
|
||||
$this->selected = $value
|
||||
? $this->teams->pluck('id')->map(fn ($id) => (string) $id)->toArray()
|
||||
: [];
|
||||
}
|
||||
|
||||
public function clearSelection(): void
|
||||
{
|
||||
$this->selected = [];
|
||||
$this->selectAll = false;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Team CRUD
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
public function openCreateTeam(): void
|
||||
{
|
||||
$this->resetTeamForm();
|
||||
$this->showTeamModal = true;
|
||||
}
|
||||
|
||||
public function openEditTeam(int $id): void
|
||||
{
|
||||
$team = WorkspaceTeam::findOrFail($id);
|
||||
|
||||
$this->editingTeamId = $id;
|
||||
$this->teamName = $team->name;
|
||||
$this->teamSlug = $team->slug ?? '';
|
||||
$this->teamDescription = $team->description ?? '';
|
||||
$this->teamPermissions = $team->permissions ?? [];
|
||||
$this->teamIsDefault = $team->is_default;
|
||||
$this->teamColour = $team->colour ?? 'zinc';
|
||||
$this->teamWorkspaceId = $team->workspace_id;
|
||||
|
||||
$this->showTeamModal = true;
|
||||
}
|
||||
|
||||
public function saveTeam(): void
|
||||
{
|
||||
$this->validate([
|
||||
'teamName' => ['required', 'string', 'max:255'],
|
||||
'teamSlug' => ['nullable', 'string', 'max:255', 'alpha_dash'],
|
||||
'teamDescription' => ['nullable', 'string', 'max:1000'],
|
||||
'teamPermissions' => ['array'],
|
||||
'teamIsDefault' => ['boolean'],
|
||||
'teamColour' => ['required', 'string', 'max:32'],
|
||||
'teamWorkspaceId' => ['required', 'exists:workspaces,id'],
|
||||
]);
|
||||
|
||||
$data = [
|
||||
'name' => $this->teamName,
|
||||
'description' => $this->teamDescription ?: null,
|
||||
'permissions' => $this->teamPermissions,
|
||||
'is_default' => $this->teamIsDefault,
|
||||
'colour' => $this->teamColour,
|
||||
'workspace_id' => $this->teamWorkspaceId,
|
||||
];
|
||||
|
||||
// Only set slug for new teams or if explicitly provided
|
||||
if (! $this->editingTeamId && $this->teamSlug) {
|
||||
$data['slug'] = $this->teamSlug;
|
||||
}
|
||||
|
||||
// If setting as default, unset other defaults for this workspace
|
||||
if ($this->teamIsDefault) {
|
||||
WorkspaceTeam::where('workspace_id', $this->teamWorkspaceId)
|
||||
->where('is_default', true)
|
||||
->when($this->editingTeamId, fn ($q) => $q->where('id', '!=', $this->editingTeamId))
|
||||
->update(['is_default' => false]);
|
||||
}
|
||||
|
||||
if ($this->editingTeamId) {
|
||||
$team = WorkspaceTeam::findOrFail($this->editingTeamId);
|
||||
|
||||
// Don't allow editing system team slug
|
||||
if ($team->is_system) {
|
||||
unset($data['slug']);
|
||||
}
|
||||
|
||||
$team->update($data);
|
||||
session()->flash('message', __('tenant::tenant.admin.team_manager.messages.team_updated'));
|
||||
} else {
|
||||
WorkspaceTeam::create($data);
|
||||
session()->flash('message', __('tenant::tenant.admin.team_manager.messages.team_created'));
|
||||
}
|
||||
|
||||
$this->closeTeamModal();
|
||||
}
|
||||
|
||||
public function deleteTeam(int $id): void
|
||||
{
|
||||
$team = WorkspaceTeam::findOrFail($id);
|
||||
|
||||
if ($team->is_system) {
|
||||
session()->flash('error', __('tenant::tenant.admin.team_manager.messages.cannot_delete_system'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$memberCount = WorkspaceMember::where('team_id', $team->id)->count();
|
||||
if ($memberCount > 0) {
|
||||
session()->flash('error', __('tenant::tenant.admin.team_manager.messages.cannot_delete_has_members', ['count' => $memberCount]));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$team->delete();
|
||||
session()->flash('message', __('tenant::tenant.admin.team_manager.messages.team_deleted'));
|
||||
}
|
||||
|
||||
public function closeTeamModal(): void
|
||||
{
|
||||
$this->showTeamModal = false;
|
||||
$this->resetTeamForm();
|
||||
}
|
||||
|
||||
protected function resetTeamForm(): void
|
||||
{
|
||||
$this->editingTeamId = null;
|
||||
$this->teamName = '';
|
||||
$this->teamSlug = '';
|
||||
$this->teamDescription = '';
|
||||
$this->teamPermissions = [];
|
||||
$this->teamIsDefault = false;
|
||||
$this->teamColour = 'zinc';
|
||||
$this->teamWorkspaceId = null;
|
||||
}
|
||||
|
||||
public function seedDefaultTeams(int $workspaceId): void
|
||||
{
|
||||
$workspace = Workspace::findOrFail($workspaceId);
|
||||
|
||||
$teamService = app(WorkspaceTeamService::class)->forWorkspace($workspace);
|
||||
$teamService->seedDefaultTeams();
|
||||
|
||||
session()->flash('message', __('tenant::tenant.admin.team_manager.messages.defaults_seeded'));
|
||||
}
|
||||
|
||||
public function migrateMembers(int $workspaceId): void
|
||||
{
|
||||
$workspace = Workspace::findOrFail($workspaceId);
|
||||
|
||||
$teamService = app(WorkspaceTeamService::class)->forWorkspace($workspace);
|
||||
$migrated = $teamService->migrateExistingMembers();
|
||||
|
||||
session()->flash('message', __('tenant::tenant.admin.team_manager.messages.members_migrated', ['count' => $migrated]));
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Computed Properties
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[Computed]
|
||||
public function teams(): \Illuminate\Contracts\Pagination\LengthAwarePaginator
|
||||
{
|
||||
return WorkspaceTeam::query()
|
||||
->with(['workspace'])
|
||||
->when($this->search, function ($query) {
|
||||
$query->where(function ($q) {
|
||||
$q->where('name', 'like', "%{$this->search}%")
|
||||
->orWhere('description', 'like', "%{$this->search}%");
|
||||
});
|
||||
})
|
||||
->when($this->workspaceFilter, function ($query) {
|
||||
$query->where('workspace_id', $this->workspaceFilter);
|
||||
})
|
||||
->withCount('members')
|
||||
->orderBy('workspace_id')
|
||||
->ordered()
|
||||
->paginate(20);
|
||||
}
|
||||
|
||||
#[Computed]
|
||||
public function workspaces(): \Illuminate\Database\Eloquent\Collection
|
||||
{
|
||||
return Workspace::orderBy('name')->get();
|
||||
}
|
||||
|
||||
#[Computed]
|
||||
public function permissionGroups(): array
|
||||
{
|
||||
return WorkspaceTeam::getAvailablePermissions();
|
||||
}
|
||||
|
||||
#[Computed]
|
||||
public function colourOptions(): array
|
||||
{
|
||||
return WorkspaceTeam::getColourOptions();
|
||||
}
|
||||
|
||||
#[Computed]
|
||||
public function stats(): array
|
||||
{
|
||||
$teamQuery = WorkspaceTeam::query();
|
||||
$memberQuery = WorkspaceMember::query();
|
||||
|
||||
if ($this->workspaceFilter) {
|
||||
$teamQuery->where('workspace_id', $this->workspaceFilter);
|
||||
$memberQuery->where('workspace_id', $this->workspaceFilter);
|
||||
}
|
||||
|
||||
return [
|
||||
'total_teams' => $teamQuery->count(),
|
||||
'total_members' => $memberQuery->count(),
|
||||
'members_with_team' => (clone $memberQuery)->whereNotNull('team_id')->count(),
|
||||
];
|
||||
}
|
||||
|
||||
private function checkHadesAccess(): void
|
||||
{
|
||||
if (! auth()->user()?->isHades()) {
|
||||
abort(403, __('tenant::tenant.errors.hades_required'));
|
||||
}
|
||||
}
|
||||
|
||||
public function render(): View
|
||||
{
|
||||
return view('tenant::admin.team-manager')
|
||||
->layout('hub::admin.layouts.app', ['title' => __('tenant::tenant.admin.team_manager.title')]);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,56 +1,48 @@
|
|||
{
|
||||
"name": "host-uk/core-tenant",
|
||||
"description": "Multi-tenancy module for Core PHP Framework - users, workspaces, entitlements",
|
||||
"keywords": ["laravel", "core-php", "tenancy", "multi-tenant", "workspace", "entitlements"],
|
||||
"license": "EUPL-1.2",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Host UK",
|
||||
"email": "support@host.uk.com"
|
||||
}
|
||||
"description": "Multi-tenancy and workspaces for Laravel",
|
||||
"keywords": [
|
||||
"multi-tenant",
|
||||
"workspaces",
|
||||
"teams"
|
||||
],
|
||||
"license": "EUPL-1.2",
|
||||
"require": {
|
||||
"php": "^8.2",
|
||||
"laravel/framework": "^11.0|^12.0",
|
||||
"host-uk/core": "^1.0|dev-main"
|
||||
"host-uk/core": "dev-main"
|
||||
},
|
||||
"require-dev": {
|
||||
"fakerphp/faker": "^1.23",
|
||||
"laravel/pint": "^1.18",
|
||||
"mockery/mockery": "^1.6",
|
||||
"nunomaduro/collision": "^8.6",
|
||||
"orchestra/testbench": "^9.0|^10.0",
|
||||
"phpunit/phpunit": "^11.5"
|
||||
"pestphp/pest": "^3.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Core\\Mod\\Tenant\\": "src/"
|
||||
"Core\\Tenant\\": ""
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Core\\Mod\\Tenant\\Tests\\": "tests/"
|
||||
"Core\\Tenant\\Tests\\": "Tests/"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"test": "vendor/bin/phpunit",
|
||||
"pint": "vendor/bin/pint"
|
||||
},
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"Core\\Mod\\Tenant\\Boot"
|
||||
"Core\\Tenant\\Boot"
|
||||
]
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "pint",
|
||||
"test": "pest"
|
||||
},
|
||||
"config": {
|
||||
"optimize-autoloader": true,
|
||||
"preferred-install": "dist",
|
||||
"sort-packages": true,
|
||||
"allow-plugins": {
|
||||
"php-http/discovery": true
|
||||
"pestphp/pest-plugin": true
|
||||
}
|
||||
},
|
||||
"minimum-stability": "stable",
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true
|
||||
}
|
||||
|
|
|
|||
33
phpunit.xml
33
phpunit.xml
|
|
@ -1,33 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
|
||||
bootstrap="vendor/autoload.php"
|
||||
colors="true"
|
||||
>
|
||||
<testsuites>
|
||||
<testsuite name="Unit">
|
||||
<directory>tests/Unit</directory>
|
||||
</testsuite>
|
||||
<testsuite name="Feature">
|
||||
<directory>tests/Feature</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<source>
|
||||
<include>
|
||||
<directory>src</directory>
|
||||
</include>
|
||||
</source>
|
||||
<php>
|
||||
<env name="APP_ENV" value="testing"/>
|
||||
<env name="APP_MAINTENANCE_DRIVER" value="file"/>
|
||||
<env name="BCRYPT_ROUNDS" value="4"/>
|
||||
<env name="CACHE_STORE" value="array"/>
|
||||
<env name="DB_CONNECTION" value="sqlite"/>
|
||||
<env name="DB_DATABASE" value=":memory:"/>
|
||||
<env name="MAIL_MAILER" value="array"/>
|
||||
<env name="PULSE_ENABLED" value="false"/>
|
||||
<env name="QUEUE_CONNECTION" value="sync"/>
|
||||
<env name="SESSION_DRIVER" value="array"/>
|
||||
<env name="TELESCOPE_ENABLED" value="false"/>
|
||||
</php>
|
||||
</phpunit>
|
||||
46
src/Boot.php
46
src/Boot.php
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant;
|
||||
namespace Core\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\Core\Tenant\Contracts\TwoFactorAuthenticationProvider::class,
|
||||
\Core\Core\Tenant\Services\TotpService::class
|
||||
);
|
||||
|
||||
$this->app->singleton(
|
||||
\Core\Mod\Tenant\Services\EntitlementService::class,
|
||||
\Core\Mod\Tenant\Services\EntitlementService::class
|
||||
\Core\Core\Tenant\Services\EntitlementService::class,
|
||||
\Core\Core\Tenant\Services\EntitlementService::class
|
||||
);
|
||||
|
||||
$this->app->singleton(
|
||||
\Core\Mod\Tenant\Services\WorkspaceManager::class,
|
||||
\Core\Mod\Tenant\Services\WorkspaceManager::class
|
||||
\Core\Core\Tenant\Services\WorkspaceManager::class,
|
||||
\Core\Core\Tenant\Services\WorkspaceManager::class
|
||||
);
|
||||
|
||||
$this->app->singleton(
|
||||
\Core\Mod\Tenant\Services\UserStatsService::class,
|
||||
\Core\Mod\Tenant\Services\UserStatsService::class
|
||||
\Core\Core\Tenant\Services\UserStatsService::class,
|
||||
\Core\Core\Tenant\Services\UserStatsService::class
|
||||
);
|
||||
|
||||
$this->app->singleton(
|
||||
\Core\Mod\Tenant\Services\WorkspaceService::class,
|
||||
\Core\Mod\Tenant\Services\WorkspaceService::class
|
||||
\Core\Core\Tenant\Services\WorkspaceService::class,
|
||||
\Core\Core\Tenant\Services\WorkspaceService::class
|
||||
);
|
||||
|
||||
$this->app->singleton(
|
||||
\Core\Mod\Tenant\Services\WorkspaceCacheManager::class,
|
||||
\Core\Mod\Tenant\Services\WorkspaceCacheManager::class
|
||||
\Core\Core\Tenant\Services\WorkspaceCacheManager::class,
|
||||
\Core\Core\Tenant\Services\WorkspaceCacheManager::class
|
||||
);
|
||||
|
||||
$this->app->singleton(
|
||||
\Core\Mod\Tenant\Services\UsageAlertService::class,
|
||||
\Core\Mod\Tenant\Services\UsageAlertService::class
|
||||
\Core\Core\Tenant\Services\UsageAlertService::class,
|
||||
\Core\Core\Tenant\Services\UsageAlertService::class
|
||||
);
|
||||
|
||||
$this->app->singleton(
|
||||
\Core\Mod\Tenant\Services\EntitlementWebhookService::class,
|
||||
\Core\Mod\Tenant\Services\EntitlementWebhookService::class
|
||||
\Core\Core\Tenant\Services\EntitlementWebhookService::class,
|
||||
\Core\Core\Tenant\Services\EntitlementWebhookService::class
|
||||
);
|
||||
|
||||
$this->app->singleton(
|
||||
\Core\Mod\Tenant\Services\WorkspaceTeamService::class,
|
||||
\Core\Mod\Tenant\Services\WorkspaceTeamService::class
|
||||
\Core\Core\Tenant\Services\WorkspaceTeamService::class,
|
||||
\Core\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\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\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\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\Core\Tenant\Services\WorkspaceCacheManager::class,
|
||||
\App\Services\WorkspaceCacheManager::class
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Concerns;
|
||||
namespace Core\Core\Tenant\Concerns;
|
||||
|
||||
use Core\Mod\Tenant\Models\Namespace_;
|
||||
use Core\Mod\Tenant\Models\User;
|
||||
use Core\Core\Tenant\Models\Namespace_;
|
||||
use Core\Core\Tenant\Models\User;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Support\Collection;
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Concerns;
|
||||
namespace Core\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\Core\Tenant\Exceptions\MissingWorkspaceContextException;
|
||||
use Core\Core\Tenant\Models\Workspace;
|
||||
use Core\Core\Tenant\Scopes\WorkspaceScope;
|
||||
use Core\Core\Tenant\Services\WorkspaceCacheManager;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Support\Collection;
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Concerns;
|
||||
namespace Core\Core\Tenant\Concerns;
|
||||
|
||||
use Closure;
|
||||
use Core\Mod\Tenant\Models\Workspace;
|
||||
use Core\Mod\Tenant\Services\WorkspaceCacheManager;
|
||||
use Core\Core\Tenant\Models\Workspace;
|
||||
use Core\Core\Tenant\Services\WorkspaceCacheManager;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Concerns;
|
||||
namespace Core\Core\Tenant\Concerns;
|
||||
|
||||
use Core\Mod\Tenant\Contracts\TwoFactorAuthenticationProvider;
|
||||
use Core\Mod\Tenant\Models\UserTwoFactorAuth;
|
||||
use Core\Mod\Tenant\Services\TotpService;
|
||||
use Core\Core\Tenant\Contracts\TwoFactorAuthenticationProvider;
|
||||
use Core\Core\Tenant\Models\UserTwoFactorAuth;
|
||||
use Core\Core\Tenant\Services\TotpService;
|
||||
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Console\Commands;
|
||||
namespace Core\Core\Tenant\Console\Commands;
|
||||
|
||||
use Core\Mod\Tenant\Models\Workspace;
|
||||
use Core\Mod\Tenant\Services\UsageAlertService;
|
||||
use Core\Core\Tenant\Models\Workspace;
|
||||
use Core\Core\Tenant\Services\UsageAlertService;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Console\Commands;
|
||||
namespace Core\Core\Tenant\Console\Commands;
|
||||
|
||||
use Core\Mod\Tenant\Models\AccountDeletionRequest;
|
||||
use Core\Core\Tenant\Models\AccountDeletionRequest;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Console\Commands;
|
||||
namespace Core\Core\Tenant\Console\Commands;
|
||||
|
||||
use Core\Mod\Tenant\Jobs\ComputeUserStats;
|
||||
use Core\Mod\Tenant\Models\User;
|
||||
use Core\Core\Tenant\Jobs\ComputeUserStats;
|
||||
use Core\Core\Tenant\Models\User;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class RefreshUserStats extends Command
|
||||
|
|
|
|||
|
|
@ -2,14 +2,14 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Console\Commands;
|
||||
namespace Core\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\Core\Tenant\Models\Boost;
|
||||
use Core\Core\Tenant\Models\EntitlementLog;
|
||||
use Core\Core\Tenant\Models\UsageRecord;
|
||||
use Core\Core\Tenant\Models\Workspace;
|
||||
use Core\Core\Tenant\Notifications\BoostExpiredNotification;
|
||||
use Core\Core\Tenant\Services\EntitlementService;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Collection;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Contracts;
|
||||
namespace Core\Core\Tenant\Contracts;
|
||||
|
||||
/**
|
||||
* Contract for entitlement webhook events.
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Contracts;
|
||||
namespace Core\Core\Tenant\Contracts;
|
||||
|
||||
/**
|
||||
* Contract for two-factor authentication providers.
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Controllers\Api;
|
||||
namespace Core\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\Core\Tenant\Models\EntitlementWebhook;
|
||||
use Core\Core\Tenant\Models\EntitlementWebhookDelivery;
|
||||
use Core\Core\Tenant\Models\Workspace;
|
||||
use Core\Core\Tenant\Services\EntitlementWebhookService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Controller;
|
||||
|
|
|
|||
|
|
@ -2,19 +2,19 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Controllers;
|
||||
namespace Core\Core\Tenant\Controllers;
|
||||
|
||||
use Core\Front\Controller;
|
||||
use Illuminate\Auth\Events\Registered;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Str;
|
||||
use Mod\Tenant\Models\EntitlementLog;
|
||||
use Mod\Tenant\Models\Package;
|
||||
use Mod\Tenant\Models\User;
|
||||
use Mod\Tenant\Models\Workspace;
|
||||
use Mod\Tenant\Models\WorkspacePackage;
|
||||
use Mod\Tenant\Services\EntitlementService;
|
||||
use Core\Tenant\Models\EntitlementLog;
|
||||
use Core\Tenant\Models\Package;
|
||||
use Core\Tenant\Models\User;
|
||||
use Core\Tenant\Models\Workspace;
|
||||
use Core\Tenant\Models\WorkspacePackage;
|
||||
use Core\Tenant\Services\EntitlementService;
|
||||
|
||||
class EntitlementApiController extends Controller
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Controllers;
|
||||
namespace Core\Core\Tenant\Controllers;
|
||||
|
||||
use Core\Helpers\PrivacyHelper;
|
||||
use Core\Mod\Trees\Models\TreePlanting;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Controllers;
|
||||
namespace Core\Core\Tenant\Controllers;
|
||||
|
||||
use Core\Front\Controller;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
|
@ -11,8 +11,8 @@ use Mod\Api\Controllers\Concerns\HasApiResponses;
|
|||
use Mod\Api\Controllers\Concerns\ResolvesWorkspace;
|
||||
use Mod\Api\Resources\PaginatedCollection;
|
||||
use Mod\Api\Resources\WorkspaceResource;
|
||||
use Mod\Tenant\Models\User;
|
||||
use Mod\Tenant\Models\Workspace;
|
||||
use Core\Tenant\Models\User;
|
||||
use Core\Tenant\Models\Workspace;
|
||||
|
||||
/**
|
||||
* Workspace API controller.
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Controllers;
|
||||
namespace Core\Core\Tenant\Controllers;
|
||||
|
||||
use Core\Mod\Tenant\Models\Workspace;
|
||||
use Core\Mod\Tenant\Models\WorkspaceInvitation;
|
||||
use Core\Core\Tenant\Models\Workspace;
|
||||
use Core\Core\Tenant\Models\WorkspaceInvitation;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Controller;
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace Core\Mod\Tenant\Database\Factories;
|
||||
namespace Core\Core\Tenant\Database\Factories;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\Core\Mod\Tenant\Models\User>
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\Core\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\Core\Tenant\Models\User::class;
|
||||
|
||||
/**
|
||||
* The current password being used by the factory.
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Database\Factories;
|
||||
namespace Core\Core\Tenant\Database\Factories;
|
||||
|
||||
use Core\Mod\Tenant\Models\User;
|
||||
use Core\Mod\Tenant\Models\UserToken;
|
||||
use Core\Core\Tenant\Models\User;
|
||||
use Core\Core\Tenant\Models\UserToken;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Database\Factories;
|
||||
namespace Core\Core\Tenant\Database\Factories;
|
||||
|
||||
use Core\Mod\Tenant\Models\WaitlistEntry;
|
||||
use Core\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\Core\Tenant\Models\WaitlistEntry>
|
||||
*/
|
||||
class WaitlistEntryFactory extends Factory
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
<?php
|
||||
|
||||
namespace Core\Mod\Tenant\Database\Factories;
|
||||
namespace Core\Core\Tenant\Database\Factories;
|
||||
|
||||
use Core\Mod\Tenant\Models\Workspace;
|
||||
use Core\Core\Tenant\Models\Workspace;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\Core\Mod\Tenant\Models\Workspace>
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\Core\Core\Tenant\Models\Workspace>
|
||||
*/
|
||||
class WorkspaceFactory extends Factory
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,14 +2,14 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Database\Factories;
|
||||
namespace Core\Core\Tenant\Database\Factories;
|
||||
|
||||
use Core\Mod\Tenant\Models\WorkspaceInvitation;
|
||||
use Core\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\Core\Tenant\Models\WorkspaceInvitation>
|
||||
*/
|
||||
class WorkspaceInvitationFactory extends Factory
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace Core\Mod\Tenant\Database\Seeders;
|
||||
namespace Core\Core\Tenant\Database\Seeders;
|
||||
|
||||
use Core\Mod\Tenant\Models\Package;
|
||||
use Core\Mod\Tenant\Models\User;
|
||||
use Core\Mod\Tenant\Models\Workspace;
|
||||
use Core\Core\Tenant\Models\Package;
|
||||
use Core\Core\Tenant\Models\User;
|
||||
use Core\Core\Tenant\Models\Workspace;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
<?php
|
||||
|
||||
namespace Core\Mod\Tenant\Database\Seeders;
|
||||
namespace Core\Core\Tenant\Database\Seeders;
|
||||
|
||||
use Core\Mod\Tenant\Models\Feature;
|
||||
use Core\Mod\Tenant\Models\Package;
|
||||
use Core\Mod\Tenant\Models\User;
|
||||
use Core\Mod\Tenant\Models\Workspace;
|
||||
use Core\Mod\Tenant\Services\EntitlementService;
|
||||
use Core\Core\Tenant\Models\Feature;
|
||||
use Core\Core\Tenant\Models\Package;
|
||||
use Core\Core\Tenant\Models\User;
|
||||
use Core\Core\Tenant\Models\Workspace;
|
||||
use Core\Core\Tenant\Services\EntitlementService;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace Core\Mod\Tenant\Database\Seeders;
|
||||
namespace Core\Core\Tenant\Database\Seeders;
|
||||
|
||||
use Core\Mod\Tenant\Models\Feature;
|
||||
use Core\Core\Tenant\Models\Feature;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace Core\Mod\Tenant\Database\Seeders;
|
||||
namespace Core\Core\Tenant\Database\Seeders;
|
||||
|
||||
use Core\Mod\Tenant\Models\Package;
|
||||
use Core\Mod\Tenant\Models\Workspace;
|
||||
use Core\Mod\Tenant\Models\WorkspacePackage;
|
||||
use Core\Core\Tenant\Models\Package;
|
||||
use Core\Core\Tenant\Models\Workspace;
|
||||
use Core\Core\Tenant\Models\WorkspacePackage;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class SystemWorkspaceSeeder extends Seeder
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
<?php
|
||||
|
||||
namespace Core\Mod\Tenant\Database\Seeders;
|
||||
namespace Core\Core\Tenant\Database\Seeders;
|
||||
|
||||
use Core\Mod\Tenant\Enums\UserTier;
|
||||
use Core\Mod\Tenant\Models\Package;
|
||||
use Core\Mod\Tenant\Models\User;
|
||||
use Core\Mod\Tenant\Models\Workspace;
|
||||
use Core\Mod\Tenant\Models\WorkspacePackage;
|
||||
use Core\Core\Tenant\Enums\UserTier;
|
||||
use Core\Core\Tenant\Models\Package;
|
||||
use Core\Core\Tenant\Models\User;
|
||||
use Core\Core\Tenant\Models\Workspace;
|
||||
use Core\Core\Tenant\Models\WorkspacePackage;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Core\Mod\Tenant\Enums;
|
||||
namespace Core\Core\Tenant\Enums;
|
||||
|
||||
enum UserTier: string
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Enums;
|
||||
namespace Core\Core\Tenant\Enums;
|
||||
|
||||
enum WebhookDeliveryStatus: string
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Events\Webhook;
|
||||
namespace Core\Core\Tenant\Events\Webhook;
|
||||
|
||||
use Core\Mod\Tenant\Contracts\EntitlementWebhookEvent;
|
||||
use Core\Mod\Tenant\Models\Boost;
|
||||
use Core\Mod\Tenant\Models\Feature;
|
||||
use Core\Mod\Tenant\Models\Workspace;
|
||||
use Core\Core\Tenant\Contracts\EntitlementWebhookEvent;
|
||||
use Core\Core\Tenant\Models\Boost;
|
||||
use Core\Core\Tenant\Models\Feature;
|
||||
use Core\Core\Tenant\Models\Workspace;
|
||||
|
||||
/**
|
||||
* Event fired when a boost is activated for a workspace.
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Events\Webhook;
|
||||
namespace Core\Core\Tenant\Events\Webhook;
|
||||
|
||||
use Core\Mod\Tenant\Contracts\EntitlementWebhookEvent;
|
||||
use Core\Mod\Tenant\Models\Boost;
|
||||
use Core\Mod\Tenant\Models\Feature;
|
||||
use Core\Mod\Tenant\Models\Workspace;
|
||||
use Core\Core\Tenant\Contracts\EntitlementWebhookEvent;
|
||||
use Core\Core\Tenant\Models\Boost;
|
||||
use Core\Core\Tenant\Models\Feature;
|
||||
use Core\Core\Tenant\Models\Workspace;
|
||||
|
||||
/**
|
||||
* Event fired when a boost expires for a workspace.
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Events\Webhook;
|
||||
namespace Core\Core\Tenant\Events\Webhook;
|
||||
|
||||
use Core\Mod\Tenant\Contracts\EntitlementWebhookEvent;
|
||||
use Core\Mod\Tenant\Models\Feature;
|
||||
use Core\Mod\Tenant\Models\Workspace;
|
||||
use Core\Core\Tenant\Contracts\EntitlementWebhookEvent;
|
||||
use Core\Core\Tenant\Models\Feature;
|
||||
use Core\Core\Tenant\Models\Workspace;
|
||||
|
||||
/**
|
||||
* Event fired when workspace usage reaches 100% of the limit.
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Events\Webhook;
|
||||
namespace Core\Core\Tenant\Events\Webhook;
|
||||
|
||||
use Core\Mod\Tenant\Contracts\EntitlementWebhookEvent;
|
||||
use Core\Mod\Tenant\Models\Feature;
|
||||
use Core\Mod\Tenant\Models\Workspace;
|
||||
use Core\Core\Tenant\Contracts\EntitlementWebhookEvent;
|
||||
use Core\Core\Tenant\Models\Feature;
|
||||
use Core\Core\Tenant\Models\Workspace;
|
||||
|
||||
/**
|
||||
* Event fired when workspace usage reaches the warning threshold (80%).
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Events\Webhook;
|
||||
namespace Core\Core\Tenant\Events\Webhook;
|
||||
|
||||
use Core\Mod\Tenant\Contracts\EntitlementWebhookEvent;
|
||||
use Core\Mod\Tenant\Models\Package;
|
||||
use Core\Mod\Tenant\Models\Workspace;
|
||||
use Core\Core\Tenant\Contracts\EntitlementWebhookEvent;
|
||||
use Core\Core\Tenant\Models\Package;
|
||||
use Core\Core\Tenant\Models\Workspace;
|
||||
|
||||
/**
|
||||
* Event fired when a workspace's package changes (upgrade, downgrade, or new assignment).
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Exceptions;
|
||||
namespace Core\Core\Tenant\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Exceptions;
|
||||
namespace Core\Core\Tenant\Exceptions;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Http\Request;
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Features;
|
||||
namespace Core\Core\Tenant\Features;
|
||||
|
||||
use Core\Mod\Tenant\Enums\UserTier;
|
||||
use Core\Mod\Tenant\Models\User;
|
||||
use Core\Mod\Tenant\Models\Workspace;
|
||||
use Core\Mod\Tenant\Services\EntitlementService;
|
||||
use Core\Core\Tenant\Enums\UserTier;
|
||||
use Core\Core\Tenant\Models\User;
|
||||
use Core\Core\Tenant\Models\Workspace;
|
||||
use Core\Core\Tenant\Services\EntitlementService;
|
||||
|
||||
class ApolloTier
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Features;
|
||||
namespace Core\Core\Tenant\Features;
|
||||
|
||||
use Illuminate\Support\Lottery;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Features;
|
||||
namespace Core\Core\Tenant\Features;
|
||||
|
||||
use Core\Mod\Tenant\Enums\UserTier;
|
||||
use Core\Mod\Tenant\Models\User;
|
||||
use Core\Mod\Tenant\Models\Workspace;
|
||||
use Core\Mod\Tenant\Services\EntitlementService;
|
||||
use Core\Core\Tenant\Enums\UserTier;
|
||||
use Core\Core\Tenant\Models\User;
|
||||
use Core\Core\Tenant\Models\Workspace;
|
||||
use Core\Core\Tenant\Services\EntitlementService;
|
||||
|
||||
class HadesTier
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Features;
|
||||
namespace Core\Core\Tenant\Features;
|
||||
|
||||
use Core\Mod\Tenant\Enums\UserTier;
|
||||
use Core\Mod\Tenant\Models\User;
|
||||
use Core\Mod\Tenant\Models\Workspace;
|
||||
use Core\Mod\Tenant\Services\EntitlementService;
|
||||
use Core\Core\Tenant\Enums\UserTier;
|
||||
use Core\Core\Tenant\Models\User;
|
||||
use Core\Core\Tenant\Models\Workspace;
|
||||
use Core\Core\Tenant\Services\EntitlementService;
|
||||
|
||||
class UnlimitedWorkspaces
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Jobs;
|
||||
namespace Core\Core\Tenant\Jobs;
|
||||
|
||||
use Core\Mod\Tenant\Models\User;
|
||||
use Core\Mod\Tenant\Services\UserStatsService;
|
||||
use Core\Core\Tenant\Models\User;
|
||||
use Core\Core\Tenant\Services\UserStatsService;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Jobs;
|
||||
namespace Core\Core\Tenant\Jobs;
|
||||
|
||||
use Core\Mod\Tenant\Enums\WebhookDeliveryStatus;
|
||||
use Core\Mod\Tenant\Models\EntitlementWebhook;
|
||||
use Core\Core\Tenant\Enums\WebhookDeliveryStatus;
|
||||
use Core\Core\Tenant\Models\EntitlementWebhook;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Jobs;
|
||||
namespace Core\Core\Tenant\Jobs;
|
||||
|
||||
use Core\Mod\Tenant\Models\AccountDeletionRequest;
|
||||
use Core\Core\Tenant\Models\AccountDeletionRequest;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Listeners;
|
||||
namespace Core\Core\Tenant\Listeners;
|
||||
|
||||
use Core\Mod\Tenant\Notifications\WelcomeNotification;
|
||||
use Core\Core\Tenant\Notifications\WelcomeNotification;
|
||||
use Illuminate\Auth\Events\Registered;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Mail;
|
||||
namespace Core\Core\Tenant\Mail;
|
||||
|
||||
use Core\Mod\Tenant\Models\AccountDeletionRequest;
|
||||
use Core\Core\Tenant\Models\AccountDeletionRequest;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Mail\Mailable;
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Middleware;
|
||||
namespace Core\Core\Tenant\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Core\Mod\Tenant\Models\Workspace;
|
||||
use Core\Mod\Tenant\Services\WorkspaceTeamService;
|
||||
use Core\Core\Tenant\Models\Workspace;
|
||||
use Core\Core\Tenant\Services\WorkspaceTeamService;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Middleware;
|
||||
namespace Core\Core\Tenant\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Middleware;
|
||||
namespace Core\Core\Tenant\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Core\Mod\Tenant\Exceptions\MissingWorkspaceContextException;
|
||||
use Core\Mod\Tenant\Models\Workspace;
|
||||
use Core\Core\Tenant\Exceptions\MissingWorkspaceContextException;
|
||||
use Core\Core\Tenant\Models\Workspace;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
|
|
@ -26,7 +26,7 @@ use Symfony\Component\HttpFoundation\Response;
|
|||
* });
|
||||
*
|
||||
* Register in Kernel.php:
|
||||
* 'workspace.required' => \Core\Mod\Tenant\Middleware\RequireWorkspaceContext::class,
|
||||
* 'workspace.required' => \Core\Core\Tenant\Middleware\RequireWorkspaceContext::class,
|
||||
*/
|
||||
class RequireWorkspaceContext
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Middleware;
|
||||
namespace Core\Core\Tenant\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Core\Mod\Tenant\Services\NamespaceService;
|
||||
use Core\Core\Tenant\Services\NamespaceService;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Middleware;
|
||||
namespace Core\Core\Tenant\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Core\Mod\Tenant\Models\Workspace;
|
||||
use Core\Mod\Tenant\Services\WorkspaceService;
|
||||
use Core\Core\Tenant\Models\Workspace;
|
||||
use Core\Core\Tenant\Services\WorkspaceService;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Core\Mod\Tenant\Models;
|
||||
namespace Core\Core\Tenant\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Models;
|
||||
namespace Core\Core\Tenant\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Core\Mod\Tenant\Models;
|
||||
namespace Core\Core\Tenant\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Core\Mod\Tenant\Models;
|
||||
namespace Core\Core\Tenant\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Models;
|
||||
namespace Core\Core\Tenant\Models;
|
||||
|
||||
use Core\Mod\Tenant\Contracts\EntitlementWebhookEvent;
|
||||
use Core\Mod\Tenant\Enums\WebhookDeliveryStatus;
|
||||
use Core\Core\Tenant\Contracts\EntitlementWebhookEvent;
|
||||
use Core\Core\Tenant\Enums\WebhookDeliveryStatus;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Models;
|
||||
namespace Core\Core\Tenant\Models;
|
||||
|
||||
use Core\Mod\Tenant\Enums\WebhookDeliveryStatus;
|
||||
use Core\Core\Tenant\Enums\WebhookDeliveryStatus;
|
||||
use DateTimeInterface;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Core\Mod\Tenant\Models;
|
||||
namespace Core\Core\Tenant\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Models;
|
||||
namespace Core\Core\Tenant\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Models;
|
||||
namespace Core\Core\Tenant\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Core\Mod\Tenant\Models;
|
||||
namespace Core\Core\Tenant\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Models;
|
||||
namespace Core\Core\Tenant\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Core\Mod\Tenant\Models;
|
||||
namespace Core\Core\Tenant\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace Core\Mod\Tenant\Models;
|
||||
namespace Core\Core\Tenant\Models;
|
||||
|
||||
use Core\Mod\Tenant\Enums\UserTier;
|
||||
use Core\Core\Tenant\Enums\UserTier;
|
||||
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
|
@ -20,9 +20,9 @@ class User extends Authenticatable implements MustVerifyEmail
|
|||
/**
|
||||
* Create a new factory instance for the model.
|
||||
*/
|
||||
protected static function newFactory(): \Core\Mod\Tenant\Database\Factories\UserFactory
|
||||
protected static function newFactory(): \Core\Core\Tenant\Database\Factories\UserFactory
|
||||
{
|
||||
return \Core\Mod\Tenant\Database\Factories\UserFactory::new();
|
||||
return \Core\Core\Tenant\Database\Factories\UserFactory::new();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Models;
|
||||
namespace Core\Core\Tenant\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
|
@ -18,9 +18,9 @@ class UserToken extends Model
|
|||
{
|
||||
use HasFactory;
|
||||
|
||||
protected static function newFactory(): \Core\Mod\Tenant\Database\Factories\UserTokenFactory
|
||||
protected static function newFactory(): \Core\Core\Tenant\Database\Factories\UserTokenFactory
|
||||
{
|
||||
return \Core\Mod\Tenant\Database\Factories\UserTokenFactory::new();
|
||||
return \Core\Core\Tenant\Database\Factories\UserTokenFactory::new();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Models;
|
||||
namespace Core\Core\Tenant\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Core\Mod\Tenant\Models;
|
||||
namespace Core\Core\Tenant\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
|
@ -13,9 +13,9 @@ class WaitlistEntry extends Model
|
|||
use HasFactory;
|
||||
use Notifiable;
|
||||
|
||||
protected static function newFactory(): \Core\Mod\Tenant\Database\Factories\WaitlistEntryFactory
|
||||
protected static function newFactory(): \Core\Core\Tenant\Database\Factories\WaitlistEntryFactory
|
||||
{
|
||||
return \Core\Mod\Tenant\Database\Factories\WaitlistEntryFactory::new();
|
||||
return \Core\Core\Tenant\Database\Factories\WaitlistEntryFactory::new();
|
||||
}
|
||||
|
||||
protected $fillable = [
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
<?php
|
||||
|
||||
namespace Core\Mod\Tenant\Models;
|
||||
namespace Core\Core\Tenant\Models;
|
||||
|
||||
use Core\Mod\Tenant\Services\EntitlementResult;
|
||||
use Core\Mod\Tenant\Services\EntitlementService;
|
||||
use Core\Core\Tenant\Services\EntitlementResult;
|
||||
use Core\Core\Tenant\Services\EntitlementService;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
|
|
@ -14,9 +14,9 @@ class Workspace extends Model
|
|||
{
|
||||
use HasFactory;
|
||||
|
||||
protected static function newFactory(): \Core\Mod\Tenant\Database\Factories\WorkspaceFactory
|
||||
protected static function newFactory(): \Core\Core\Tenant\Database\Factories\WorkspaceFactory
|
||||
{
|
||||
return \Core\Mod\Tenant\Database\Factories\WorkspaceFactory::new();
|
||||
return \Core\Core\Tenant\Database\Factories\WorkspaceFactory::new();
|
||||
}
|
||||
|
||||
protected $fillable = [
|
||||
|
|
@ -577,7 +577,7 @@ class Workspace extends Model
|
|||
}
|
||||
|
||||
// Try to get from authenticated user's default workspace
|
||||
if (auth()->check() && auth()->user() instanceof \Core\Mod\Tenant\Models\User) {
|
||||
if (auth()->check() && auth()->user() instanceof \Core\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\Core\Tenant\Notifications\WorkspaceInvitationNotification($invitation));
|
||||
|
||||
return $invitation;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Models;
|
||||
namespace Core\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\Core\Tenant\Database\Factories\WorkspaceInvitationFactory
|
||||
{
|
||||
return \Core\Mod\Tenant\Database\Factories\WorkspaceInvitationFactory::new();
|
||||
return \Core\Core\Tenant\Database\Factories\WorkspaceInvitationFactory::new();
|
||||
}
|
||||
|
||||
protected $fillable = [
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Models;
|
||||
namespace Core\Core\Tenant\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Core\Mod\Tenant\Models;
|
||||
namespace Core\Core\Tenant\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Models;
|
||||
namespace Core\Core\Tenant\Models;
|
||||
|
||||
use Core\Mod\Tenant\Concerns\BelongsToWorkspace;
|
||||
use Core\Core\Tenant\Concerns\BelongsToWorkspace;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Notifications;
|
||||
namespace Core\Core\Tenant\Notifications;
|
||||
|
||||
use Core\Mod\Tenant\Models\Boost;
|
||||
use Core\Mod\Tenant\Models\Feature;
|
||||
use Core\Mod\Tenant\Models\Workspace;
|
||||
use Core\Core\Tenant\Models\Boost;
|
||||
use Core\Core\Tenant\Models\Feature;
|
||||
use Core\Core\Tenant\Models\Workspace;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Notifications;
|
||||
namespace Core\Core\Tenant\Notifications;
|
||||
|
||||
use Core\Mod\Tenant\Models\Feature;
|
||||
use Core\Mod\Tenant\Models\UsageAlertHistory;
|
||||
use Core\Mod\Tenant\Models\Workspace;
|
||||
use Core\Core\Tenant\Models\Feature;
|
||||
use Core\Core\Tenant\Models\UsageAlertHistory;
|
||||
use Core\Core\Tenant\Models\Workspace;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Notifications;
|
||||
namespace Core\Core\Tenant\Notifications;
|
||||
|
||||
use Core\Mod\Tenant\Models\WaitlistEntry;
|
||||
use Core\Core\Tenant\Models\WaitlistEntry;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Notifications;
|
||||
namespace Core\Core\Tenant\Notifications;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Notifications;
|
||||
namespace Core\Core\Tenant\Notifications;
|
||||
|
||||
use Core\Mod\Tenant\Models\WorkspaceInvitation;
|
||||
use Core\Core\Tenant\Models\WorkspaceInvitation;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ declare(strict_types=1);
|
|||
*/
|
||||
|
||||
use Core\Mod\Api\Controllers\WorkspaceController;
|
||||
use Core\Mod\Tenant\Controllers\Api\EntitlementWebhookController;
|
||||
use Core\Core\Tenant\Controllers\Api\EntitlementWebhookController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -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\Core\Tenant\View\Modal\Web\CancelDeletion;
|
||||
use Core\Core\Tenant\View\Modal\Web\ConfirmDeletion;
|
||||
use Core\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\Core\Tenant\Controllers\WorkspaceInvitationController::class)
|
||||
->name('workspace.invitation.accept');
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Rules;
|
||||
namespace Core\Core\Tenant\Rules;
|
||||
|
||||
use Closure;
|
||||
use Core\Mod\Tenant\Models\User;
|
||||
use Core\Core\Tenant\Models\User;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Rules;
|
||||
namespace Core\Core\Tenant\Rules;
|
||||
|
||||
use Closure;
|
||||
use Core\Mod\Social\Enums\ResourceStatus;
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Scopes;
|
||||
namespace Core\Core\Tenant\Scopes;
|
||||
|
||||
use Core\Mod\Tenant\Exceptions\MissingWorkspaceContextException;
|
||||
use Core\Mod\Tenant\Models\Workspace;
|
||||
use Core\Core\Tenant\Exceptions\MissingWorkspaceContextException;
|
||||
use Core\Core\Tenant\Models\Workspace;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Scope;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Core\Mod\Tenant\Services;
|
||||
namespace Core\Core\Tenant\Services;
|
||||
|
||||
/**
|
||||
* Value object representing the result of an entitlement check.
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
<?php
|
||||
|
||||
namespace Core\Mod\Tenant\Services;
|
||||
namespace Core\Core\Tenant\Services;
|
||||
|
||||
use Core\Mod\Tenant\Models\Boost;
|
||||
use Core\Mod\Tenant\Models\EntitlementLog;
|
||||
use Core\Mod\Tenant\Models\Feature;
|
||||
use Core\Mod\Tenant\Models\Namespace_;
|
||||
use Core\Mod\Tenant\Models\NamespacePackage;
|
||||
use Core\Mod\Tenant\Models\Package;
|
||||
use Core\Mod\Tenant\Models\UsageRecord;
|
||||
use Core\Mod\Tenant\Models\User;
|
||||
use Core\Mod\Tenant\Models\Workspace;
|
||||
use Core\Mod\Tenant\Models\WorkspacePackage;
|
||||
use Core\Core\Tenant\Models\Boost;
|
||||
use Core\Core\Tenant\Models\EntitlementLog;
|
||||
use Core\Core\Tenant\Models\Feature;
|
||||
use Core\Core\Tenant\Models\Namespace_;
|
||||
use Core\Core\Tenant\Models\NamespacePackage;
|
||||
use Core\Core\Tenant\Models\Package;
|
||||
use Core\Core\Tenant\Models\UsageRecord;
|
||||
use Core\Core\Tenant\Models\User;
|
||||
use Core\Core\Tenant\Models\Workspace;
|
||||
use Core\Core\Tenant\Models\WorkspacePackage;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,19 +2,19 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Services;
|
||||
namespace Core\Core\Tenant\Services;
|
||||
|
||||
use Core\Mod\Tenant\Contracts\EntitlementWebhookEvent;
|
||||
use Core\Mod\Tenant\Enums\WebhookDeliveryStatus;
|
||||
use Core\Mod\Tenant\Events\Webhook\BoostActivatedEvent;
|
||||
use Core\Mod\Tenant\Events\Webhook\BoostExpiredEvent;
|
||||
use Core\Mod\Tenant\Events\Webhook\LimitReachedEvent;
|
||||
use Core\Mod\Tenant\Events\Webhook\LimitWarningEvent;
|
||||
use Core\Mod\Tenant\Events\Webhook\PackageChangedEvent;
|
||||
use Core\Mod\Tenant\Jobs\DispatchEntitlementWebhook;
|
||||
use Core\Mod\Tenant\Models\EntitlementWebhook;
|
||||
use Core\Mod\Tenant\Models\EntitlementWebhookDelivery;
|
||||
use Core\Mod\Tenant\Models\Workspace;
|
||||
use Core\Core\Tenant\Contracts\EntitlementWebhookEvent;
|
||||
use Core\Core\Tenant\Enums\WebhookDeliveryStatus;
|
||||
use Core\Core\Tenant\Events\Webhook\BoostActivatedEvent;
|
||||
use Core\Core\Tenant\Events\Webhook\BoostExpiredEvent;
|
||||
use Core\Core\Tenant\Events\Webhook\LimitReachedEvent;
|
||||
use Core\Core\Tenant\Events\Webhook\LimitWarningEvent;
|
||||
use Core\Core\Tenant\Events\Webhook\PackageChangedEvent;
|
||||
use Core\Core\Tenant\Jobs\DispatchEntitlementWebhook;
|
||||
use Core\Core\Tenant\Models\EntitlementWebhook;
|
||||
use Core\Core\Tenant\Models\EntitlementWebhookDelivery;
|
||||
use Core\Core\Tenant\Models\Workspace;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Str;
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Services;
|
||||
namespace Core\Core\Tenant\Services;
|
||||
|
||||
use Core\Mod\Tenant\Models\Namespace_;
|
||||
use Core\Mod\Tenant\Models\User;
|
||||
use Core\Mod\Tenant\Models\Workspace;
|
||||
use Core\Core\Tenant\Models\Namespace_;
|
||||
use Core\Core\Tenant\Models\User;
|
||||
use Core\Core\Tenant\Models\Workspace;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Services;
|
||||
namespace Core\Core\Tenant\Services;
|
||||
|
||||
use Core\Mod\Tenant\Models\Namespace_;
|
||||
use Core\Mod\Tenant\Models\User;
|
||||
use Core\Mod\Tenant\Models\Workspace;
|
||||
use Core\Core\Tenant\Models\Namespace_;
|
||||
use Core\Core\Tenant\Models\User;
|
||||
use Core\Core\Tenant\Models\Workspace;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Services;
|
||||
namespace Core\Core\Tenant\Services;
|
||||
|
||||
use chillerlan\QRCode\QRCode;
|
||||
use chillerlan\QRCode\QROptions;
|
||||
use Core\Mod\Tenant\Contracts\TwoFactorAuthenticationProvider;
|
||||
use Core\Core\Tenant\Contracts\TwoFactorAuthenticationProvider;
|
||||
|
||||
/**
|
||||
* TOTP (Time-based One-Time Password) service.
|
||||
|
|
|
|||
|
|
@ -2,15 +2,15 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Services;
|
||||
namespace Core\Core\Tenant\Services;
|
||||
|
||||
use Core\Mod\Tenant\Events\Webhook\LimitReachedEvent;
|
||||
use Core\Mod\Tenant\Events\Webhook\LimitWarningEvent;
|
||||
use Core\Mod\Tenant\Models\Feature;
|
||||
use Core\Mod\Tenant\Models\UsageAlertHistory;
|
||||
use Core\Mod\Tenant\Models\User;
|
||||
use Core\Mod\Tenant\Models\Workspace;
|
||||
use Core\Mod\Tenant\Notifications\UsageAlertNotification;
|
||||
use Core\Core\Tenant\Events\Webhook\LimitReachedEvent;
|
||||
use Core\Core\Tenant\Events\Webhook\LimitWarningEvent;
|
||||
use Core\Core\Tenant\Models\Feature;
|
||||
use Core\Core\Tenant\Models\UsageAlertHistory;
|
||||
use Core\Core\Tenant\Models\User;
|
||||
use Core\Core\Tenant\Models\Workspace;
|
||||
use Core\Core\Tenant\Notifications\UsageAlertNotification;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Mod\Tenant\Services;
|
||||
namespace Core\Core\Tenant\Services;
|
||||
|
||||
use Core\Mod\Tenant\Enums\UserTier;
|
||||
use Core\Mod\Tenant\Models\User;
|
||||
use Core\Core\Tenant\Enums\UserTier;
|
||||
use Core\Core\Tenant\Models\User;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class UserStatsService
|
||||
|
|
@ -44,7 +44,7 @@ class UserStatsService
|
|||
// For page loads, return cached data immediately and queue refresh
|
||||
if ($user->cached_stats) {
|
||||
// Queue background refresh
|
||||
dispatch(new \Core\Mod\Tenant\Jobs\ComputeUserStats($user->id))->onQueue('stats');
|
||||
dispatch(new \Core\Core\Tenant\Jobs\ComputeUserStats($user->id))->onQueue('stats');
|
||||
|
||||
return $user->cached_stats;
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue