Files
sam-manage/app/Http/Controllers/FcmController.php
2026-02-11 15:43:07 +09:00

294 lines
8.4 KiB
PHP

<?php
namespace App\Http\Controllers;
use App\Models\FcmSendLog;
use App\Models\PushDeviceToken;
use App\Models\Tenants\Tenant;
use App\Services\FcmApiService;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\View\View;
// Note: Response is still used by deleteToken() method
class FcmController extends Controller
{
public function __construct(
private readonly FcmApiService $fcmApiService
) {}
/**
* FCM 토큰 관리 페이지
*/
public function tokens(Request $request): View
{
$tenants = Tenant::orderBy('company_name')->get();
$tokens = $this->getTokensQuery($request)->paginate(20);
$stats = $this->getTokenStats($request->get('tenant_id'));
return view('fcm.tokens', compact('tenants', 'tokens', 'stats'));
}
/**
* FCM 토큰 목록 (HTMX partial)
*/
public function tokenList(Request $request): View
{
$tokens = $this->getTokensQuery($request)->paginate(20);
return view('fcm.partials.token-table', compact('tokens'));
}
/**
* 토큰 통계 (HTMX partial)
*/
public function tokenStats(Request $request): View
{
$stats = $this->getTokenStats($request->get('tenant_id'));
return view('fcm.partials.token-stats', compact('stats'));
}
/**
* 토큰 상태 변경 (활성/비활성)
*/
public function toggleToken(Request $request, int $id): View
{
$token = PushDeviceToken::withoutGlobalScopes()->findOrFail($id);
$token->update([
'is_active' => ! $token->is_active,
]);
return view('fcm.partials.token-row', compact('token'));
}
/**
* 토큰 삭제
*/
public function deleteToken(int $id): Response
{
$token = PushDeviceToken::withoutGlobalScopes()->findOrFail($id);
$token->delete();
return response('', 200)
->header('HX-Trigger', 'tokenDeleted');
}
/**
* 에러 토큰 전체 삭제
*/
public function deleteErrorTokens(Request $request): Response
{
$query = PushDeviceToken::withoutGlobalScopes()->whereNotNull('last_error');
if ($tenantId = $request->get('tenant_id')) {
$query->where('tenant_id', $tenantId);
}
$count = $query->count();
$query->toBase()->delete();
\Log::info("에러 토큰 전체삭제: {$count}건 영구 삭제");
return response('', 200)
->header('HX-Refresh', 'true');
}
/**
* FCM 테스트 발송 페이지
*/
public function send(): View
{
$tenants = Tenant::orderBy('company_name')->get();
return view('fcm.send', compact('tenants'));
}
/**
* FCM 발송 실행 (HTMX)
*
* API 서버를 통해 FCM을 발송합니다.
*/
public function sendPush(Request $request): View
{
$request->validate([
'title' => 'required|string|max:255',
'body' => 'required|string|max:1000',
'tenant_id' => 'nullable|integer|exists:tenants,id',
'user_id' => 'nullable|integer',
'platform' => 'nullable|string|in:android,ios,web',
'channel_id' => 'nullable|string|in:push_default,push_vendor_register,push_approval_request,push_income,push_sales_order,push_purchase_order,push_contract',
'type' => 'nullable|string|max:50',
'url' => 'nullable|string|max:500',
]);
// API 서버로 발송 요청
$result = $this->fcmApiService->send(
array_filter([
'title' => $request->get('title'),
'body' => $request->get('body'),
'tenant_id' => $request->get('tenant_id'),
'user_id' => $request->get('user_id'),
'platform' => $request->get('platform'),
'channel_id' => $request->get('channel_id', 'push_default'),
'type' => $request->get('type'),
'url' => $request->get('url'),
]),
auth()->id()
);
if (! $result['success']) {
return view('fcm.partials.send-result', [
'success' => false,
'message' => $result['message'] ?? 'FCM 발송에 실패했습니다.',
]);
}
return view('fcm.partials.send-result', [
'success' => true,
'message' => $result['message'] ?? '발송 완료',
'data' => $result['data'] ?? [],
]);
}
/**
* FCM 발송 이력 페이지
*/
public function history(Request $request): View
{
$logs = $this->getHistoryQuery($request)->paginate(20);
return view('fcm.history', compact('logs'));
}
/**
* FCM 발송 이력 목록 (HTMX partial)
*/
public function historyList(Request $request): View
{
$logs = $this->getHistoryQuery($request)->paginate(20);
return view('fcm.partials.history-table', compact('logs'));
}
/**
* 대상 토큰 수 미리보기 (HTMX)
*/
public function previewCount(Request $request): View
{
$query = PushDeviceToken::withoutGlobalScopes()->active();
if ($tenantId = $request->get('tenant_id')) {
$query->forTenant($tenantId);
}
if ($userId = $request->get('user_id')) {
$query->forUser($userId);
}
if ($platform = $request->get('platform')) {
$query->platform($platform);
}
$count = $query->count();
return view('fcm.partials.preview-count', compact('count'));
}
/**
* 토큰 쿼리 빌더
*/
private function getTokensQuery(Request $request)
{
$query = PushDeviceToken::withoutGlobalScopes()
->with(['user:id,name,email', 'tenant:id,company_name']);
if ($tenantId = $request->get('tenant_id')) {
$query->where('tenant_id', $tenantId);
}
if ($platform = $request->get('platform')) {
$query->where('platform', $platform);
}
if ($request->has('is_active') && $request->get('is_active') !== '') {
$query->where('is_active', $request->boolean('is_active'));
}
if ($request->boolean('has_error')) {
$query->hasError();
}
if ($search = $request->get('search')) {
$query->where(function ($q) use ($search) {
$q->where('token', 'like', "%{$search}%")
->orWhere('device_name', 'like', "%{$search}%")
->orWhereHas('user', function ($q) use ($search) {
$q->where('name', 'like', "%{$search}%")
->orWhere('email', 'like', "%{$search}%");
});
});
}
return $query->orderBy('created_at', 'desc');
}
/**
* 토큰 통계 가져오기
*/
private function getTokenStats(?int $tenantId = null): array
{
$query = PushDeviceToken::withoutGlobalScopes();
if ($tenantId) {
$query->where('tenant_id', $tenantId);
}
$total = (clone $query)->count();
$active = (clone $query)->where('is_active', true)->count();
$inactive = (clone $query)->where('is_active', false)->count();
$hasError = (clone $query)->whereNotNull('last_error')->count();
$byPlatform = (clone $query)->selectRaw('platform, count(*) as count')
->groupBy('platform')
->pluck('count', 'platform')
->toArray();
return [
'total' => $total,
'active' => $active,
'inactive' => $inactive,
'has_error' => $hasError,
'by_platform' => $byPlatform,
];
}
/**
* 발송 이력 쿼리 빌더
*/
private function getHistoryQuery(Request $request)
{
$query = FcmSendLog::with(['sender:id,name', 'tenant:id,company_name']);
if ($tenantId = $request->get('tenant_id')) {
$query->where('tenant_id', $tenantId);
}
if ($status = $request->get('status')) {
$query->where('status', $status);
}
if ($from = $request->get('from')) {
$query->whereDate('created_at', '>=', $from);
}
if ($to = $request->get('to')) {
$query->whereDate('created_at', '<=', $to);
}
return $query->orderBy('created_at', 'desc');
}
}