feat: 實作 Multi-tenancy 多租戶架構 (stancl/tenancy)
- 安裝並設定 stancl/tenancy 套件 - 分離 Central / Tenant migrations - 建立 Tenant Model 與資料遷移指令 - 建立房東後台 CRUD (Landlord Dashboard) - 新增租戶管理頁面 (列表、新增、編輯、詳情) - 新增域名管理功能 - 更新部署手冊
This commit is contained in:
29
app/Http/Controllers/Landlord/DashboardController.php
Normal file
29
app/Http/Controllers/Landlord/DashboardController.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Landlord;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Tenant;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class DashboardController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$stats = [
|
||||
'totalTenants' => Tenant::count(),
|
||||
'activeTenants' => Tenant::whereJsonContains('data->is_active', true)->count(),
|
||||
'recentTenants' => Tenant::latest()->take(5)->get()->map(function ($tenant) {
|
||||
return [
|
||||
'id' => $tenant->id,
|
||||
'name' => $tenant->name ?? $tenant->id,
|
||||
'is_active' => $tenant->is_active ?? true,
|
||||
'created_at' => $tenant->created_at->format('Y-m-d H:i'),
|
||||
'domains' => $tenant->domains->pluck('domain')->toArray(),
|
||||
];
|
||||
}),
|
||||
];
|
||||
|
||||
return Inertia::render('Landlord/Dashboard', $stats);
|
||||
}
|
||||
}
|
||||
172
app/Http/Controllers/Landlord/TenantController.php
Normal file
172
app/Http/Controllers/Landlord/TenantController.php
Normal file
@@ -0,0 +1,172 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Landlord;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Tenant;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class TenantController extends Controller
|
||||
{
|
||||
/**
|
||||
* 顯示租戶列表
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$tenants = Tenant::with('domains')->get()->map(function ($tenant) {
|
||||
return [
|
||||
'id' => $tenant->id,
|
||||
'name' => $tenant->name ?? $tenant->id,
|
||||
'email' => $tenant->email ?? null,
|
||||
'is_active' => $tenant->is_active ?? true,
|
||||
'created_at' => $tenant->created_at->format('Y-m-d H:i'),
|
||||
'domains' => $tenant->domains->pluck('domain')->toArray(),
|
||||
];
|
||||
});
|
||||
|
||||
return Inertia::render('Landlord/Tenant/Index', [
|
||||
'tenants' => $tenants,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 顯示新增租戶表單
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
return Inertia::render('Landlord/Tenant/Create');
|
||||
}
|
||||
|
||||
/**
|
||||
* 儲存新租戶
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'id' => ['required', 'string', 'max:50', 'alpha_dash', Rule::unique('tenants', 'id')],
|
||||
'name' => ['required', 'string', 'max:100'],
|
||||
'email' => ['nullable', 'email', 'max:100'],
|
||||
'domain' => ['nullable', 'string', 'max:100'],
|
||||
]);
|
||||
|
||||
$tenant = Tenant::create([
|
||||
'id' => $validated['id'],
|
||||
'name' => $validated['name'],
|
||||
'email' => $validated['email'] ?? null,
|
||||
'is_active' => true,
|
||||
]);
|
||||
|
||||
// 如果有指定域名,則綁定
|
||||
if (!empty($validated['domain'])) {
|
||||
$tenant->domains()->create(['domain' => $validated['domain']]);
|
||||
}
|
||||
|
||||
return redirect()->route('landlord.tenants.index')
|
||||
->with('success', "租戶 {$validated['name']} 建立成功!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 顯示單一租戶詳情
|
||||
*/
|
||||
public function show(string $id)
|
||||
{
|
||||
$tenant = Tenant::with('domains')->findOrFail($id);
|
||||
|
||||
return Inertia::render('Landlord/Tenant/Show', [
|
||||
'tenant' => [
|
||||
'id' => $tenant->id,
|
||||
'name' => $tenant->name ?? $tenant->id,
|
||||
'email' => $tenant->email ?? null,
|
||||
'is_active' => $tenant->is_active ?? true,
|
||||
'created_at' => $tenant->created_at->format('Y-m-d H:i'),
|
||||
'updated_at' => $tenant->updated_at->format('Y-m-d H:i'),
|
||||
'domains' => $tenant->domains->map(fn($d) => [
|
||||
'id' => $d->id,
|
||||
'domain' => $d->domain,
|
||||
])->toArray(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 顯示編輯租戶表單
|
||||
*/
|
||||
public function edit(string $id)
|
||||
{
|
||||
$tenant = Tenant::findOrFail($id);
|
||||
|
||||
return Inertia::render('Landlord/Tenant/Edit', [
|
||||
'tenant' => [
|
||||
'id' => $tenant->id,
|
||||
'name' => $tenant->name ?? $tenant->id,
|
||||
'email' => $tenant->email ?? null,
|
||||
'is_active' => $tenant->is_active ?? true,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新租戶
|
||||
*/
|
||||
public function update(Request $request, string $id)
|
||||
{
|
||||
$tenant = Tenant::findOrFail($id);
|
||||
|
||||
$validated = $request->validate([
|
||||
'name' => ['required', 'string', 'max:100'],
|
||||
'email' => ['nullable', 'email', 'max:100'],
|
||||
'is_active' => ['boolean'],
|
||||
]);
|
||||
|
||||
$tenant->update($validated);
|
||||
|
||||
return redirect()->route('landlord.tenants.index')
|
||||
->with('success', "租戶 {$validated['name']} 更新成功!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 刪除租戶
|
||||
*/
|
||||
public function destroy(string $id)
|
||||
{
|
||||
$tenant = Tenant::findOrFail($id);
|
||||
$name = $tenant->name ?? $id;
|
||||
|
||||
$tenant->delete();
|
||||
|
||||
return redirect()->route('landlord.tenants.index')
|
||||
->with('success', "租戶 {$name} 已刪除!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增域名到租戶
|
||||
*/
|
||||
public function addDomain(Request $request, string $id)
|
||||
{
|
||||
$tenant = Tenant::findOrFail($id);
|
||||
|
||||
$validated = $request->validate([
|
||||
'domain' => ['required', 'string', 'max:100', Rule::unique('domains', 'domain')],
|
||||
]);
|
||||
|
||||
$tenant->domains()->create(['domain' => $validated['domain']]);
|
||||
|
||||
return back()->with('success', "域名 {$validated['domain']} 已綁定!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除租戶的域名
|
||||
*/
|
||||
public function removeDomain(string $id, int $domainId)
|
||||
{
|
||||
$tenant = Tenant::findOrFail($id);
|
||||
$domain = $tenant->domains()->findOrFail($domainId);
|
||||
$domainName = $domain->domain;
|
||||
|
||||
$domain->delete();
|
||||
|
||||
return back()->with('success', "域名 {$domainName} 已移除!");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user