294 lines
8.4 KiB
PHP
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');
|
|
}
|
|
}
|