feat: assign many users to many tenants + tenant creation

This commit is contained in:
Noah Lancien 2025-05-21 12:56:59 +02:00
parent 0eed72ed89
commit ee8106e679
21 changed files with 567 additions and 82 deletions

View file

@ -3,7 +3,6 @@
namespace App\Console\Commands;
use App\Enums\Users\RoleEnum;
use App\Models\Tenant;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Console\Command;
@ -80,7 +79,6 @@ class UserCreate extends Command
'role' => $this->role,
'published' => true,
'published_at' => Carbon::now(),
'tenant_id' => Tenant::query()->first()->id,
]);
$user->saveOrFail();

View file

@ -0,0 +1,119 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Tenants\StoreTenantRequest;
use App\Models\Settings;
use App\Models\Tenant;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class TenantController extends Controller
{
/**
* Create the controller instance.
*/
public function __construct()
{
$this->authorizeResource(Tenant::class);
}
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View
*/
public function index(Request $request): View
{
/** @var \Illuminate\Database\Eloquent\Builder $query */
$query = Tenant::query();
/** @var string $search Search field */
$search = $request->input('search');
if ($search) {
$this->searchQuery(
$query,
$search,
null,
'name',
);
}
$searchFields = \implode(', ', [
trans('validation.attributes.name'),
]);
/** Sort columns with a query */
$this->sortQuery($query);
/** Custom pagination */
$tenantModelList = $this->paginate($query);
return view('bo.pages.tenants.index', compact('tenantModelList', 'search', 'searchFields'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Contracts\View\View
*/
public function create(): \Illuminate\Contracts\View\View
{
return view('bo.pages.tenants.create');
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\Tenants\StoreTenantRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(StoreTenantRequest $request): RedirectResponse
{
return DB::transaction(function () use ($request) {
$tenant = new Tenant();
$tenant->fill($request->validated());
$settings = new Settings();
if ($tenant->saveOrFail()) {
return redirect()->route('bo.tenants.index', ['tenant' => \tenant()])
->with('success', __('crud.messages.has_been_created', [
'model' => Str::ucfirst(\trans_choice('models.classes.tenant', 1))
]));
}
return redirect()->back()
->with('error', __('crud.messages.cannot_be_created', [
'model' => Str::ucfirst(\trans_choice('models.classes.tenant', 1))
]));
});
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\Tenant $tenant
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(Tenant $tenant): RedirectResponse
{
// FIXME: c pt
/** @var array<string,string> */
$previousQueries = [];
\parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries);
if ($tenant->deleteOrFail()) {
return redirect()->route('bo.tenants.index', array_merge(['tenant' => \tenant('id')], $previousQueries))
->with('success', __('crud.messages.has_been_deleted', [
'model' => Str::ucfirst(\trans_choice('models.classes.tenant', 1))
]));
}
return redirect()->back()
->with('error', __('crud.messages.cannot_be_deleted', [
'model' => Str::ucfirst(\trans_choice('models.classes.tenant', 1))
]));
}
}

View file

@ -18,7 +18,7 @@ class Authenticate extends Middleware
protected function redirectTo(Request $request): ?string
{
$logins = [
'backend' => route('bo.login', ['tenant' => Tenant::query()->first()->id])
'backend' => route('bo.login')
];
if (!$request->expectsJson()) {
// * Notify must login

View file

@ -3,6 +3,7 @@
namespace App\Http\Middleware;
use App\Enums\Users\RoleEnum;
use App\Models\Tenant;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
@ -20,8 +21,18 @@ class RestrictTenantAccess
public function handle(Request $request, Closure $next): Response
{
$user = \auth('backend')->user();
if ($user->tenant_id !== $request->tenant && !(RoleEnum::conceptor->equals($user->role))) {
abort(403);
if ($user) {
if ($request->routeIs('bo.tenants.*')) {
tenancy()->initialize(Tenant::firstOrFail());
return $next($request);
}
if (
!\in_array($request->tenant, $user->tenants->pluck('id')->toArray()) &&
!(RoleEnum::conceptor->equals($user->role))
) {
abort(403);
}
}
return $next($request);
}

View file

@ -0,0 +1,32 @@
<?php
namespace App\Http\Requests\Bo\Tenants;
use App\Models\Tenant;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
class StoreTenantRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return Gate::allows('create', Tenant::class);
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
*/
public function rules(): array
{
return [
'name' => 'required|min:2|max:255',
];
}
}

View file

@ -3,6 +3,7 @@
namespace App\Models;
use App\Lib\Helpers\ToolboxHelper;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Stancl\Tenancy\Database\Models\Tenant as BaseTenant;
use Stancl\Tenancy\Contracts\Tenant as TenantContract;
use Illuminate\Support\Str;
@ -14,7 +15,10 @@ class Tenant extends BaseTenant implements TenantContract
*
* @var list<string>
*/
protected $fillable = ['id'];
protected $fillable = [
'id',
'name',
];
/**
* Define the custom columns (that won't be stored in the data JSON column)
@ -71,4 +75,16 @@ class Tenant extends BaseTenant implements TenantContract
ToolboxHelper::assertFieldIsUnique($table, 'name', $name, $id);
ToolboxHelper::assertFieldIsUnique($table, 'id', Str::slug($name), $id);
}
// * RELATIONSHIPS
/**
* The tenants that belong to the user.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function users(): BelongsToMany
{
return $this->belongsToMany(User::class);
}
}

View file

@ -6,9 +6,9 @@ use App\Enums\Users\RoleEnum;
use App\Traits\Models\ActivityLog;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Stancl\Tenancy\Database\Concerns\BelongsToTenant;
/**
* @property string $first_name Firstname.
@ -27,7 +27,6 @@ class User extends Authenticatable
use ActivityLog;
use HasFactory;
use Notifiable;
use BelongsToTenant;
/**
* The attributes that are mass assignable.
@ -94,4 +93,16 @@ class User extends Authenticatable
);
});
}
// * RELATIONSHIPS
/**
* The tenants that belong to the user.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function tenants(): BelongsToMany
{
return $this->belongsToMany(Tenant::class);
}
}

View file

@ -0,0 +1,59 @@
<?php
namespace App\Policies;
use App\Enums\Users\RoleEnum;
use App\Models\User;
use App\Policies\Rules\UserStaticRules;
class TenantPolicy
{
/**
* Determine whether the user can view any models.
*
* @param \App\Models\User $authUser
* @return boolean
*/
public function viewAny(User $authUser): bool
{
return UserStaticRules::atLeastRole($authUser, RoleEnum::conceptor);
}
/**
* Determine whether the user can create models.
*
* @param \App\Models\User $authUser
* @return boolean
*/
public function create(User $authUser): bool
{
return UserStaticRules::atLeastRole($authUser, RoleEnum::conceptor);
}
/**
* Determine whether the user can delete the model.
*
* @param \App\Models\User $authUser
* @param \App\Models\Tenant $tenant
* @return boolean
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
*/
public function delete(User $authUser, \App\Models\Tenant $tenant): bool
{
// phpcs:enable
return UserStaticRules::atLeastRole($authUser, RoleEnum::conceptor);
}
/**
* Determine whether the user can permanently delete the model.
*
* @param \App\Models\User $authUser
* @param \App\Models\Tenant $tenant
* @return boolean
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
*/
public function forceDelete(User $authUser, \App\Models\Tenant $tenant): bool
{
// phpcs:enable
return false;
}
}

View file

@ -7,7 +7,6 @@ use App\Models\Font;
use App\Models\Product;
use App\Models\Range;
use App\Models\Settings;
use App\Models\User;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
use Illuminate\Http\Request;
@ -53,7 +52,6 @@ class RouteServiceProvider extends ServiceProvider
'product' => Product::class,
'range' => Range::class,
'settings' => Settings::class,
'user' => User::class,
];
foreach ($tenantRelatedModels as $key => $tenantRelatedModel) {

View file

@ -3,6 +3,7 @@
namespace App\Providers;
use App\Models\Settings;
use App\Models\Tenant;
use Illuminate\Pagination\Paginator;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\File;
@ -39,6 +40,8 @@ class ViewServiceProvider extends ServiceProvider
View::composer('*', function () {
View::share('settingsGlobal', Settings::getCached()->where('tenant_id', tenant('id'))->first());
});
View::share('tenantGlobal', Tenant::all());
}
}
}

View file

@ -13,35 +13,31 @@ return new class extends Migration
*/
public function up(): void
{
Schema::table('activity_logs', function (Blueprint $table) {
$table->string('tenant_id')->index()->after('data');
});
$tables = [
['name' => 'activity_logs', 'after' => 'data'],
['name' => 'brands', 'after' => 'status'],
['name' => 'fonts', 'after' => 'font'],
['name' => 'products', 'after' => 'range_id'],
['name' => 'ranges', 'after' => 'font_id'],
['name' => 'settings', 'after' => 'home_timeout'],
];
Schema::table('brands', function (Blueprint $table) {
$table->string('tenant_id')->index()->after('status');
});
foreach ($tables as $entry) {
$tableName = $entry['name'];
$after = $entry['after'];
Schema::table('fonts', function (Blueprint $table) {
$table->string('tenant_id')->index()->after('font');
});
Schema::table($tableName, function (Blueprint $table) use ($after) {
$table->string('tenant_id')->index()->after($after)->comment('Tenant reference.');
});
Schema::table('products', function (Blueprint $table) {
$table->string('tenant_id')->index()->after('range_id');
});
Schema::table('ranges', function (Blueprint $table) {
$table->string('tenant_id')->index()->after('font_id');
});
Schema::table('settings', function (Blueprint $table) {
$table->string('tenant_id')->index()->after('home_timeout');
});
Schema::table('users', function (Blueprint $table) {
$table->string('tenant_id')->index()->after('published');
$table->dropUnique(['email']);
$table->unique(['tenant_id', 'email']);
});
Schema::table($tableName, function (Blueprint $table) use ($tableName) {
$table->foreign('tenant_id', $tableName . '_tenant_id_foreign')
->references('id')
->on('tenants')
->onUpdate('cascade')
->onDelete('cascade');
});
} //end foreach
}
/**
@ -51,29 +47,20 @@ return new class extends Migration
*/
public function down(): void
{
Schema::table('activity_logs', function (Blueprint $table) {
$table->dropColumn('tenant_id');
});
$tables = [
'activity_logs',
'brands',
'fonts',
'products',
'ranges',
'settings',
];
Schema::table('brands', function (Blueprint $table) {
$table->dropColumn('tenant_id');
});
Schema::table('fonts', function (Blueprint $table) {
$table->dropColumn('tenant_id');
});
Schema::table('products', function (Blueprint $table) {
$table->dropColumn('tenant_id');
});
Schema::table('ranges', function (Blueprint $table) {
$table->dropColumn('tenant_id');
});
Schema::table('settings', function (Blueprint $table) {
$table->dropColumn('tenant_id');
});
foreach ($tables as $entry) {
Schema::table($entry, function (Blueprint $table) {
$table->dropColumn('tenant_id');
});
}
Schema::table('users', function (Blueprint $table) {
$table->dropUnique(['tenant_id', 'email']);

View file

@ -0,0 +1,36 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('tenant_user', function (Blueprint $table) {
$table->unsignedBigInteger('user_id');
$table->string('tenant_id', 255);
$table->primary(['user_id', 'tenant_id']);
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->foreign('tenant_id')->references('id')->on('tenants')->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('tenant_user');
}
};

View file

@ -12,6 +12,7 @@ import {
faArrowsAltV,
faBackspace,
faBan,
faBuilding,
faCheck,
faChevronLeft,
faChevronRight,
@ -90,6 +91,7 @@ library.add(faEarthAmericas);
library.add(faG);
library.add(faP);
library.add(faTriangleExclamation);
library.add(faBuilding);
// Front
library.add(faChevronLeft);

View file

@ -72,6 +72,24 @@
</li>
<!-- Authentication Links -->
@auth
<!-- Tenant switchs -->
<li class="nav-item dropdown @auth border-end @endauth">
<button class="btn nav-link dropdown-toggle d-flex align-items-center h-100 flex-row border-0 px-3"
data-bs-toggle="dropdown" type="button" aria-expanded="false">
{{ tenant('name') }}&nbsp;<i class="fa-solid fa-building"></i></i>
</button>
<div class="dropdown-menu dropdown-menu-end text-center">
@foreach ($tenantGlobal as $tenant)
<div class="dropdown-item">
<a class="btn" href="{{ route('bo.dashboard', ['tenant' => $tenant]) }}">{{ __('switch_tenant') }}
->
{{ $tenant->name }}
</a>
</div>
@endforeach
</div>
</li>
<!-- Profile -->
<li class="nav-item dropdown">
<button class="btn nav-link dropdown-toggle d-flex align-items-center h-100 flex-row border-0 ps-3" id="navbarDropdown"
data-bs-toggle="dropdown" type="button">

View file

@ -0,0 +1,36 @@
@extends('bo.layout', ['brParam' => ''])
@section('title', trans_choice('crud.meta.title.create', 1, ['model' => trans_choice('models.classes.tenant', 1)]))
@section('description', trans_choice('crud.meta.description.create', 1, ['model' => trans_choice('models.classes.tenant', 1)]))
@section('breadcrumb', request()->route()->getName())
@section('content')
<div class="d-flex justify-content-between flex-md-nowrap align-items-center flex-wrap pb-3">
<div class="d-flex align-items-start flex-row">
<a class="btn btn-primary text-decoration-none m-0" data-bs-toggle="tooltip"
href="{{ route('bo.tenants.index', ['tenant' => \tenant()]) }}"
title="{{ __('crud.helpers.list_all', ['model' => trans_choice('models.classes.tenant', '*')]) }}">
<i class="fa-solid fa-arrow-left"></i>
</a>
@include('modules.breadcrumbs.breadcrumb-body', ['brParam' => ''])
</div>
<div class="mb-md-0 mb-2">
<button class="btn btn-primary" id="formSubmitClone" data-bs-toggle="tooltip" type="submit"
title="{{ __('crud.helpers.save_model', ['model' => trans_choice('models.classes.tenant', 1)]) }}">
{{ Str::ucfirst(__('crud.actions.save')) }}
</button>
</div>
</div>
<form action="{{ route('bo.tenants.store', ['tenant' => \tenant()]) }}" method="POST" enctype="multipart/form-data">
@csrf
@include('bo.pages.tenants.form')
<div class="row mt-3">
<div class="col text-center">
<button class="btn btn-primary" id="formSubmit" data-bs-toggle="tooltip" type="submit"
title="{{ __('crud.helpers.save_model', ['model' => trans_choice('models.classes.tenant', 1)]) }}">
{{ Str::ucfirst(__('crud.actions.save')) }}
</button>
</div>
</div>
</form>
@endsection

View file

@ -0,0 +1,52 @@
<div class="row">
{{-- NAME --}}
<div class="col-12 col-md-6 mb-3">
<fieldset class="h-100 border p-3">
<legend>{{ Str::ucfirst(__('validation.attributes.name')) }}</legend>
<div class="row">
<div class="col-12">
@include('bo.partials.inputs.varchar', [
'id' => 'tenantName',
'label' => __(':field :inter:model', [
'field' => Str::ucfirst(__('validation.attributes.name')),
'inter' => __('validation.custom.of_the.male'),
'model' => trans_choice('models.classes.tenant', 1),
]),
'hasWordCounter' => true,
'name' => 'name',
'required' => false,
'model' => $fontModel ?? null,
'helper' => __('validation.string', [
'attribute' => __(':field :inter:model', [
'field' => Str::ucfirst(__('validation.attributes.name')),
'inter' => __('validation.custom.of_the.male'),
'model' => trans_choice('models.classes.tenant', 1),
]),
]),
'placeholder' => '2025 - Salon - Marque',
])
</div>
</div>
</fieldset>
</div>
</div>
<div class="row">
<div class="col text-center">
<p><b>{{ __('* Champs obligatoires') }}</b></p>
</div>
</div>
@push('scripts')
<script nonce="{{ $nonce }}">
document.addEventListener("DOMContentLoaded", () => {
// Submit button clone.
const submit = document.getElementById('formSubmit')
submitClone = document.getElementById('formSubmitClone');
submitClone.addEventListener('click', (event) => {
event.preventDefault();
submit.click('');
})
});
</script>
@endpush

View file

@ -0,0 +1,48 @@
<table class="table-hover table-fix-action m-0 table">
@if (count($tenantModelList) > 0)
<thead>
@include('bo.modules.table-col-sorter', [
'cols' => [
'name' => Str::ucfirst(__('validation.attributes.name')),
'updated_at' => Str::ucfirst(__('validation.attributes.updated_at')),
],
])
</thead>
<tbody>
@foreach ($tenantModelList as $tenantModel)
<tr class="border-bottom list-item">
<td class="text-center align-middle">
<span>{{ $tenantModel->name }}</span>
</td>
<td class="text-center align-middle">
<span class="badge bg-secondary">{{ $tenantModel->updated_at->isoFormat('LLLL') }}</span>
</td>
<td class="text-end align-middle">
@can('delete', $tenantModel)
<form class="btn-group confirmDeleteTS"
action="{{ route('bo.tenants.destroy', ['tenant' => tenant('id'), 'tenant_model' => $tenantModel]) }}"
method="POST" novalidate>
@csrf
@method('DELETE')
<button class="btn btn-sm btn-danger" data-bs-toggle="tooltip" type="submit"
title="{{ Str::ucfirst(__('crud.actions.delete')) }}">
<i class="fa-solid fa-trash"></i>
</button>
</form>
@else
<span class="text-danger" data-bs-toggle="tooltip" title="{{ __('list.right') }}">
<i class="fa-solid fa-ban"></i>
</span>
@endcan
</td>
</tr>
@endforeach
</tbody>
@else
<tr>
<td class="border-0">
{{ trans_choice('crud.other.no_model_found', 1, ['model' => trans_choice('models.classes.tenant', '*')]) }}
</td>
</tr>
@endif
</table>

View file

@ -0,0 +1,21 @@
@extends('bo.layout')
@section('title', Str::ucfirst(trans_choice('models.classes.tenant', '*')))
@section('description', __('Liste de tous les :model', ['model' => trans_choice('models.classes.tenant', '*')]))
@section('content')
<div class="d-flex justify-content-between flex-md-nowrap align-items-center flex-wrap">
@include('modules.breadcrumbs.breadcrumb-body')
@can('create', \App\Models\Tenant::class)
<a class="btn btn-primary float-right" data-bs-toggle="tooltip" href="{{ route('bo.tenants.create', ['tenant' => tenant()]) }}"
title="{{ __('crud.helpers.create_model', ['model' => trans_choice('models.classes.tenant', 1)]) }}">
<i class="fa-solid fa-plus"></i>
</a>
@endcan
</div>
@include('bo.modules.search-bar')
<div class="table-responsive mb-3">
@include('bo.pages.tenants.index-list')
</div>
{{ $tenantModelList->links() }}
@endsection

View file

@ -130,3 +130,15 @@ Breadcrumbs::for('bo.settings.edit', function (Generator $trail, Settings $setti
route('bo.settings.edit', ['tenant' => \tenant(), 'settings' => $settings])
);
});
// * TENANTS
Breadcrumbs::for('bo.tenants.index', function (Generator $trail) {
$trail->push(
Str::of(trans_choice('models.classes.tenant', 2))->ucfirst(),
route('bo.tenants.index', ['tenant' => tenant(), 'sort_col' => 'updated_at', 'sort_way' => 'desc'])
);
});
Breadcrumbs::for('bo.tenants.create', function (Generator $trail) {
$trail->parent('bo.tenants.index');
$trail->push(Str::ucfirst(trans('crud.actions.create')), route('bo.tenants.create', ['tenant' => tenant()]));
});

View file

@ -1,5 +1,6 @@
<?php
use App\Enums\Users\RoleEnum;
use App\Http\Controllers\Bo\ActivityLogController;
use App\Http\Controllers\Bo\BrandController;
use App\Http\Controllers\Bo\DashboardController;
@ -8,11 +9,59 @@ use App\Http\Controllers\Bo\FontController;
use App\Http\Controllers\Bo\ProductController;
use App\Http\Controllers\Bo\RangeController;
use App\Http\Controllers\Bo\SettingsController;
use App\Http\Controllers\Bo\TenantController;
use App\Http\Controllers\Bo\UserController;
use App\Http\Controllers\Controller;
use App\Models\Tenant;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Route;
Route::prefix('/bo')
->name('bo.')
->group(function () {
// * Authenticable routes
Route::namespace('\App\Http\Controllers\Bo')
->group(function () {
Auth::routes(
[
'confirm' => false,
'login' => true,
'logout' => true,
'register' => false,
'reset' => false,
'verify' => false,
]
);
Route::get('/login-error', function () {
return view('bo.pages.login-error');
})->name('login-error');
});
// * Others routes
Route::post('theme/set', [Controller::class, 'setTheme'])->name('theme.set');
Route::get('/lang/{lang}', [DashboardController::class, 'changelang'])->name('lang');
Route::middleware([
'admin-secure',
'auth:backend'
])
->group(function () {
Route::get('/', function () {
$user = auth('backend')->user();
$tenant = RoleEnum::conceptor->equals($user->role) ?
Tenant::first()->getKey() :
$user->tenants->first()->getKey();
return redirect()->route('bo.dashboard', [
'tenant' => $tenant
]);
});
// * TENANTS MANAGEMENT
Route::resource('tenants', TenantController::class)
->only(['index', 'create', 'store', 'destroy'])
->parameters([
'tenants' => 'tenant_model'
]);
});
});
Route::middleware([
'web',
Stancl\Tenancy\Middleware\InitializeTenancyByPath::class,
@ -20,23 +69,6 @@ Route::middleware([
Route::prefix('/bo')
->name('bo.')
->group(function () {
// * Authenticable routes
Route::namespace('\App\Http\Controllers\Bo')
->group(function () {
Auth::routes(
[
'confirm' => false,
'login' => true,
'logout' => true,
'register' => false,
'reset' => false,
'verify' => false,
]
);
Route::get('/login-error', function () {
return view('bo.pages.login-error');
})->name('login-error');
});
Route::middleware(
[
'admin-secure',
@ -45,7 +77,6 @@ Route::middleware([
)->group(function () {
Route::get('/', [DashboardController::class, 'index'])->name('dashboard');
Route::get('/lang/{lang}', [DashboardController::class, 'changelang'])->name('lang');
// * EXPORT.
Route::get('export', [ExportController::class, 'exportToZip'])->name('export');
@ -88,7 +119,5 @@ Route::middleware([
Route::resource('activity_logs', ActivityLogController::class)->only(['index', 'show']);
});
});
// * Others routes
Route::post('theme/set', [Controller::class, 'setTheme'])->name('theme.set');
});
});

View file

@ -1,5 +1,6 @@
<?php
use App\Enums\Users\RoleEnum;
use App\Lib\Helpers\ToolboxHelper;
use App\Models\Tenant;
use Illuminate\Support\Facades\Route;
@ -24,8 +25,4 @@ Route::middleware(['throttle:credentials'])
})->middleware('throttle:30,1')->where('schema', '[a-z]{1,}\.schema\.json')->name('schema');
Route::redirect('/', '/bo')->name('homepage');
Route::fallback(function () {
response()->redirectToRoute('bo.dashboard', ['tenant' => auth('backend')->user()->tenant_id ?? Tenant::first()->id])->throwResponse();
});
});