v7.0.0-beta.2 release
This commit is contained in:
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace App\Http\Controllers\Api\Admin;
|
||||
|
||||
use App\Helpers\PermissionHelper;
|
||||
use App\Http\Controllers\Api\Base\AdminBaseController;
|
||||
use App\Http\Requests\ActivityLog\ActivityLogBulkDeleteRequest;
|
||||
use App\Http\Requests\ActivityLog\ActivityLogDeleteRequest;
|
||||
@@ -42,14 +41,7 @@ class ActivityLogController extends AdminBaseController
|
||||
|
||||
$collection = new ActivityLogCollection($logs);
|
||||
|
||||
$responseData = $collection->toArray($request);
|
||||
|
||||
// 컬렉션 레벨 abilities (페이지 레벨 버튼 제어용)
|
||||
$responseData['abilities'] = [
|
||||
'can_delete' => PermissionHelper::check('core.activities.delete', $request->user()),
|
||||
];
|
||||
|
||||
return $this->success('activity_log.fetch_success', $responseData);
|
||||
return $this->success('activity_log.fetch_success', $collection->toArray($request));
|
||||
} catch (Exception $e) {
|
||||
return $this->error('activity_log.fetch_failed', 500, $e->getMessage());
|
||||
}
|
||||
|
||||
@@ -2,14 +2,12 @@
|
||||
|
||||
namespace App\Http\Controllers\Api\Admin;
|
||||
|
||||
use App\Helpers\ResponseHelper;
|
||||
use App\Http\Controllers\Api\Base\AdminBaseController;
|
||||
use App\Http\Requests\Auth\LoginRequest;
|
||||
use App\Http\Resources\UserResource;
|
||||
use App\Services\AuthService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class AuthController extends AdminBaseController
|
||||
@@ -38,7 +36,7 @@ class AuthController extends AdminBaseController
|
||||
|
||||
// 관리자 권한 확인
|
||||
if (!$user->isAdmin()) {
|
||||
return ResponseHelper::forbidden('auth.admin_required');
|
||||
return $this->forbidden('auth.admin_required');
|
||||
}
|
||||
|
||||
// 사용자 정보는 Resource로, 토큰은 그대로
|
||||
@@ -46,7 +44,7 @@ class AuthController extends AdminBaseController
|
||||
|
||||
return $this->success('auth.admin_login_success', $data);
|
||||
} catch (ValidationException $e) {
|
||||
return ResponseHelper::unauthorized('auth.login_failed', [], 'core');
|
||||
return $this->unauthorized('auth.login_failed');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,7 +97,7 @@ class AuthController extends AdminBaseController
|
||||
}
|
||||
return $this->success('common.success', $data);
|
||||
} catch (ValidationException $e) {
|
||||
return ResponseHelper::unauthorized('auth.unauthenticated', [], 'core');
|
||||
return $this->unauthorized('auth.unauthenticated');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Admin;
|
||||
|
||||
use App\Http\Controllers\Api\Base\AdminBaseController;
|
||||
use App\Services\GeoIpDatabaseService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
/**
|
||||
* GeoIP DB 관리 컨트롤러.
|
||||
*
|
||||
* 수동 DB 업데이트 트리거를 제공합니다. 정기 갱신은
|
||||
* routes/console.php의 스케줄(geoip:update)이 담당합니다.
|
||||
* 비즈니스 로직은 모두 GeoIpDatabaseService에 위임합니다.
|
||||
*/
|
||||
class GeoIpController extends AdminBaseController
|
||||
{
|
||||
public function __construct(
|
||||
private GeoIpDatabaseService $geoIpDatabaseService
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* MaxMind GeoLite2-City DB를 즉시 재다운로드합니다.
|
||||
*
|
||||
* 내부적으로 GeoIpDatabaseService::updateDatabase(true)를 호출합니다.
|
||||
* 다운로드는 동기 실행이므로 PHP-FPM/웹서버 타임아웃(90초 이상) 필요.
|
||||
*
|
||||
* @return JsonResponse 업데이트 결과 JSON 응답
|
||||
*/
|
||||
public function update(): JsonResponse
|
||||
{
|
||||
$result = $this->geoIpDatabaseService->updateDatabase(true);
|
||||
|
||||
if ($result['success']) {
|
||||
return $this->success(
|
||||
'settings.geoip.update_success',
|
||||
$result['data'] ?? null
|
||||
);
|
||||
}
|
||||
|
||||
return $this->error(
|
||||
$this->mapStatusToMessageKey((string) $result['status']),
|
||||
$this->mapStatusToHttpCode((string) $result['status']),
|
||||
$result['message'] ?? null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Service 상태 코드를 i18n 메시지 키로 매핑합니다.
|
||||
*
|
||||
* @param string $status Service 상태 코드
|
||||
* @return string i18n 키
|
||||
*/
|
||||
private function mapStatusToMessageKey(string $status): string
|
||||
{
|
||||
return match ($status) {
|
||||
'missing_license_key' => 'settings.geoip.license_key_missing',
|
||||
'unauthorized' => 'settings.geoip.license_key_invalid',
|
||||
'connection_failed' => 'settings.geoip.connection_failed',
|
||||
default => 'settings.geoip.update_failed',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Service 상태 코드를 HTTP 상태 코드로 매핑합니다.
|
||||
*
|
||||
* @param string $status Service 상태 코드
|
||||
* @return int HTTP 상태 코드
|
||||
*/
|
||||
private function mapStatusToHttpCode(string $status): int
|
||||
{
|
||||
return match ($status) {
|
||||
'missing_license_key' => 400,
|
||||
'unauthorized' => 401,
|
||||
default => 500,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Admin;
|
||||
|
||||
use App\Helpers\PermissionHelper;
|
||||
use App\Http\Controllers\Api\Base\AdminBaseController;
|
||||
use App\Http\Requests\MailSendLog\MailSendLogBulkDeleteRequest;
|
||||
use App\Http\Requests\MailSendLog\MailSendLogDeleteRequest;
|
||||
use App\Http\Requests\MailSendLog\MailSendLogIndexRequest;
|
||||
use App\Http\Resources\MailSendLogCollection;
|
||||
use App\Models\MailSendLog;
|
||||
use App\Services\MailSendLogService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* 메일 발송 이력 관리 컨트롤러
|
||||
*/
|
||||
class MailSendLogController extends AdminBaseController
|
||||
{
|
||||
/**
|
||||
* MailSendLogController 생성자.
|
||||
*
|
||||
* @param MailSendLogService $mailSendLogService 메일 발송 이력 서비스
|
||||
*/
|
||||
public function __construct(
|
||||
private MailSendLogService $mailSendLogService
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* 메일 발송 이력 목록을 조회합니다.
|
||||
*
|
||||
* @param MailSendLogIndexRequest $request 검증된 조회 요청
|
||||
* @return JsonResponse 발송 이력 목록
|
||||
*/
|
||||
public function index(MailSendLogIndexRequest $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$filters = array_filter([
|
||||
'extension_type' => $request->validated('extension_type'),
|
||||
'extension_identifier' => $request->validated('extension_identifier'),
|
||||
'template_type' => $request->validated('template_type'),
|
||||
'status' => $request->validated('status'),
|
||||
'search' => $request->validated('search'),
|
||||
'search_type' => $request->validated('search_type'),
|
||||
'date_from' => $request->validated('date_from'),
|
||||
'date_to' => $request->validated('date_to'),
|
||||
'sort_by' => $request->validated('sort_by'),
|
||||
'sort_order' => $request->validated('sort_order'),
|
||||
], fn ($value) => $value !== null);
|
||||
|
||||
$perPage = (int) ($request->validated('per_page') ?? 20);
|
||||
$logs = $this->mailSendLogService->getLogs($filters, $perPage);
|
||||
|
||||
$collection = new MailSendLogCollection($logs);
|
||||
|
||||
$responseData = $collection->toArray($request);
|
||||
|
||||
// 컬렉션 레벨 abilities (페이지 레벨 버튼 제어용)
|
||||
$responseData['abilities'] = [
|
||||
'can_delete' => PermissionHelper::check('core.mail-send-logs.delete', $request->user()),
|
||||
];
|
||||
|
||||
return $this->success('mail_send_log.fetch_success', $responseData);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('메일 발송 이력 조회 실패', ['error' => $e->getMessage()]);
|
||||
|
||||
return $this->error('mail_send_log.fetch_failed', 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 메일 발송 이력을 삭제합니다.
|
||||
*
|
||||
* @param MailSendLogDeleteRequest $request 검증된 삭제 요청
|
||||
* @param MailSendLog $mailSendLog 삭제할 발송 이력 모델
|
||||
* @return JsonResponse 삭제 결과
|
||||
*/
|
||||
public function destroy(MailSendLogDeleteRequest $request, MailSendLog $mailSendLog): JsonResponse
|
||||
{
|
||||
try {
|
||||
$this->mailSendLogService->delete($mailSendLog->id);
|
||||
|
||||
return $this->success('mail_send_log.delete_success');
|
||||
} catch (\Exception $e) {
|
||||
Log::error('메일 발송 이력 삭제 실패', ['id' => $mailSendLog->id, 'error' => $e->getMessage()]);
|
||||
|
||||
return $this->error('mail_send_log.delete_failed', 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 메일 발송 이력을 일괄 삭제합니다.
|
||||
*
|
||||
* @param MailSendLogBulkDeleteRequest $request 검증된 일괄 삭제 요청
|
||||
* @return JsonResponse 삭제 결과
|
||||
*/
|
||||
public function bulkDestroy(MailSendLogBulkDeleteRequest $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$count = $this->mailSendLogService->deleteMany($request->validated('ids'));
|
||||
|
||||
return $this->success('mail_send_log.bulk_delete_success', ['deleted_count' => $count]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('메일 발송 이력 일괄 삭제 실패', ['error' => $e->getMessage()]);
|
||||
|
||||
return $this->error('mail_send_log.delete_failed', 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Admin;
|
||||
|
||||
use App\Http\Controllers\Api\Base\AdminBaseController;
|
||||
use App\Http\Requests\MailTemplate\MailTemplateIndexRequest;
|
||||
use App\Http\Requests\MailTemplate\MailTemplatePreviewRequest;
|
||||
use App\Http\Requests\MailTemplate\UpdateMailTemplateRequest;
|
||||
use App\Http\Resources\MailTemplateCollection;
|
||||
use App\Http\Resources\MailTemplateResource;
|
||||
use App\Models\MailTemplate;
|
||||
use App\Services\MailTemplateService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* 메일 템플릿 관리 컨트롤러
|
||||
*
|
||||
* 메일 템플릿의 조회, 수정, 미리보기, 초기화 기능을 제공합니다.
|
||||
*/
|
||||
class MailTemplateController extends AdminBaseController
|
||||
{
|
||||
/**
|
||||
* MailTemplateController 생성자.
|
||||
*
|
||||
* @param MailTemplateService $mailTemplateService 메일 템플릿 서비스
|
||||
*/
|
||||
public function __construct(
|
||||
private MailTemplateService $mailTemplateService
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* 메일 템플릿 목록을 페이지네이션하여 조회합니다.
|
||||
*
|
||||
* @param MailTemplateIndexRequest $request 검증된 목록 조회 요청
|
||||
* @return JsonResponse 페이지네이션된 템플릿 목록
|
||||
*/
|
||||
public function index(MailTemplateIndexRequest $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$filters = array_filter($request->validated(), fn ($value) => $value !== null);
|
||||
$perPage = (int) ($request->validated('per_page') ?? 20);
|
||||
$templates = $this->mailTemplateService->getTemplates($filters, $perPage);
|
||||
|
||||
$collection = new MailTemplateCollection($templates);
|
||||
|
||||
return $this->success('mail_template.fetch_success', $collection->toArray($request));
|
||||
} catch (\Exception $e) {
|
||||
Log::error('메일 템플릿 목록 조회 실패', ['error' => $e->getMessage()]);
|
||||
|
||||
return $this->error('mail_template.fetch_failed', 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 메일 템플릿을 수정합니다.
|
||||
*
|
||||
* @param UpdateMailTemplateRequest $request 수정 요청
|
||||
* @param MailTemplate $mailTemplate 수정 대상
|
||||
* @return JsonResponse 수정 결과
|
||||
*/
|
||||
public function update(UpdateMailTemplateRequest $request, MailTemplate $mailTemplate): JsonResponse
|
||||
{
|
||||
try {
|
||||
$userId = $request->user()?->id;
|
||||
$template = $this->mailTemplateService->updateTemplate(
|
||||
$mailTemplate,
|
||||
$request->validated(),
|
||||
$userId
|
||||
);
|
||||
|
||||
return $this->success(
|
||||
'mail_template.save_success',
|
||||
new MailTemplateResource($template)
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('메일 템플릿 수정 실패', ['error' => $e->getMessage()]);
|
||||
|
||||
return $this->error('mail_template.save_error', 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 메일 템플릿의 활성 상태를 토글합니다.
|
||||
*
|
||||
* @param MailTemplate $mailTemplate 토글 대상
|
||||
* @return JsonResponse 토글 결과
|
||||
*/
|
||||
public function toggleActive(MailTemplate $mailTemplate): JsonResponse
|
||||
{
|
||||
try {
|
||||
$template = $this->mailTemplateService->toggleActive($mailTemplate);
|
||||
|
||||
return $this->success(
|
||||
'mail_template.toggle_success',
|
||||
new MailTemplateResource($template)
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('메일 템플릿 활성 상태 토글 실패', ['error' => $e->getMessage()]);
|
||||
|
||||
return $this->error('mail_template.toggle_failed', 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 메일 템플릿 미리보기를 반환합니다.
|
||||
*
|
||||
* @param MailTemplatePreviewRequest $request 검증된 미리보기 요청
|
||||
* @return JsonResponse 미리보기 결과
|
||||
*/
|
||||
public function preview(MailTemplatePreviewRequest $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$result = $this->mailTemplateService->getPreview($request->validated());
|
||||
|
||||
return $this->success('mail_template.preview_success', $result);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('메일 템플릿 미리보기 실패', ['error' => $e->getMessage()]);
|
||||
|
||||
return $this->error('mail_template.preview_failed', 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 메일 템플릿을 시더 기본값으로 복원합니다.
|
||||
*
|
||||
* @param MailTemplate $mailTemplate 복원 대상
|
||||
* @return JsonResponse 복원 결과
|
||||
*/
|
||||
public function reset(MailTemplate $mailTemplate): JsonResponse
|
||||
{
|
||||
try {
|
||||
$defaultData = $this->mailTemplateService->getDefaultTemplateData($mailTemplate->type);
|
||||
|
||||
if (! $defaultData) {
|
||||
return $this->error('mail_template.reset_no_default', 404);
|
||||
}
|
||||
|
||||
$template = $this->mailTemplateService->resetToDefault($mailTemplate, $defaultData);
|
||||
|
||||
return $this->success(
|
||||
'mail_template.reset_success',
|
||||
new MailTemplateResource($template)
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('메일 템플릿 기본값 복원 실패', ['error' => $e->getMessage()]);
|
||||
|
||||
return $this->error('mail_template.reset_failed', 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
namespace App\Http\Controllers\Api\Admin;
|
||||
|
||||
use App\Helpers\PermissionHelper;
|
||||
use App\Helpers\ResponseHelper;
|
||||
use App\Http\Controllers\Api\Base\AdminBaseController;
|
||||
use App\Http\Requests\Module\ActivateModuleRequest;
|
||||
use App\Http\Requests\Module\DeactivateModuleRequest;
|
||||
@@ -11,6 +9,7 @@ use App\Http\Requests\Module\IndexModuleRequest;
|
||||
use App\Http\Requests\Module\InstallModuleFromFileRequest;
|
||||
use App\Http\Requests\Module\InstallModuleFromGithubRequest;
|
||||
use App\Http\Requests\Module\InstallModuleRequest;
|
||||
use App\Http\Requests\Module\PerformModuleUpdateRequest;
|
||||
use App\Http\Requests\Module\RefreshModuleLayoutsRequest;
|
||||
use App\Http\Requests\Module\UninstallModuleRequest;
|
||||
use App\Http\Requests\Extension\ChangelogRequest;
|
||||
@@ -62,15 +61,9 @@ class ModuleController extends AdminBaseController
|
||||
$collection = new ModuleCollection(collect($responseData['data']));
|
||||
$responseData['data'] = $collection->toArray($request)['data'];
|
||||
$responseData['meta'] = $collection->with($request)['meta'];
|
||||
$responseData['abilities'] = $collection->resolveCollectionAbilities($request);
|
||||
}
|
||||
|
||||
// 컬렉션 레벨 abilities (페이지 레벨 버튼 제어용)
|
||||
$responseData['abilities'] = [
|
||||
'can_install' => PermissionHelper::check('core.modules.install', $request->user()),
|
||||
'can_activate' => PermissionHelper::check('core.modules.activate', $request->user()),
|
||||
'can_uninstall' => PermissionHelper::check('core.modules.uninstall', $request->user()),
|
||||
];
|
||||
|
||||
return $this->success('module.fetch_success', $responseData);
|
||||
} catch (\Exception $e) {
|
||||
return $this->error('module.fetch_failed', 500, $e->getMessage());
|
||||
@@ -154,8 +147,12 @@ class ModuleController extends AdminBaseController
|
||||
public function install(InstallModuleRequest $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$moduleName = $request->validated()['module_name'];
|
||||
$module = $this->moduleService->installModule($moduleName);
|
||||
$validated = $request->validated();
|
||||
$moduleName = $validated['module_name'];
|
||||
$vendorMode = \App\Extension\Vendor\VendorMode::fromStringOrAuto(
|
||||
$validated['vendor_mode'] ?? null
|
||||
);
|
||||
$module = $this->moduleService->installModule($moduleName, $vendorMode);
|
||||
|
||||
if ($module) {
|
||||
return $this->successWithResource(
|
||||
@@ -172,7 +169,7 @@ class ModuleController extends AdminBaseController
|
||||
$firstError = collect($e->errors())->flatten()->first()
|
||||
?? __('module.install_failed');
|
||||
|
||||
return ResponseHelper::error($firstError, 422, $e->errors());
|
||||
return $this->validationError($e->errors(), $firstError);
|
||||
} catch (\Exception $e) {
|
||||
return $this->error('modules.installation_failed', 500, $e->getMessage(), [
|
||||
'error' => $e->getMessage(),
|
||||
@@ -411,16 +408,48 @@ class ModuleController extends AdminBaseController
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 특정 모듈의 수정된 레이아웃을 확인합니다.
|
||||
*
|
||||
* 업데이트 전 사용자가 수정한 레이아웃이 있는지 확인하여
|
||||
* 레이아웃 전략(overwrite/keep) 선택에 참고할 수 있도록 합니다.
|
||||
*
|
||||
* @param string $moduleName 모듈 식별자
|
||||
* @return JsonResponse 수정된 레이아웃 정보를 포함한 JSON 응답
|
||||
*/
|
||||
public function checkModifiedLayouts(string $moduleName): JsonResponse
|
||||
{
|
||||
try {
|
||||
$result = $this->moduleService->checkModifiedLayouts($moduleName);
|
||||
|
||||
return $this->success('modules.check_modified_layouts_success', $result);
|
||||
} catch (ValidationException $e) {
|
||||
return $this->error('modules.check_modified_layouts_failed', 422, $e->errors());
|
||||
} catch (\Exception $e) {
|
||||
return $this->error('modules.check_modified_layouts_failed', 500, $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 특정 모듈을 업데이트합니다.
|
||||
*
|
||||
* layout_strategy 파라미터로 레이아웃 처리 방식을 결정합니다:
|
||||
* - overwrite: 모든 레이아웃을 새 버전으로 교체
|
||||
* - keep: 사용자가 수정한 레이아웃을 유지
|
||||
*
|
||||
* @param PerformModuleUpdateRequest $request 업데이트 요청 데이터
|
||||
* @param string $moduleName 업데이트할 모듈 identifier
|
||||
* @return JsonResponse 업데이트 결과 JSON 응답
|
||||
*/
|
||||
public function performUpdate(string $moduleName): JsonResponse
|
||||
public function performUpdate(PerformModuleUpdateRequest $request, string $moduleName): JsonResponse
|
||||
{
|
||||
try {
|
||||
$result = $this->moduleService->updateModule($moduleName);
|
||||
$validated = $request->validated();
|
||||
$vendorMode = \App\Extension\Vendor\VendorMode::fromStringOrAuto(
|
||||
$validated['vendor_mode'] ?? null
|
||||
);
|
||||
$layoutStrategy = $validated['layout_strategy'] ?? 'overwrite';
|
||||
$result = $this->moduleService->updateModule($moduleName, $vendorMode, $layoutStrategy);
|
||||
|
||||
$moduleInfo = $result['module_info'] ?? null;
|
||||
|
||||
@@ -438,7 +467,7 @@ class ModuleController extends AdminBaseController
|
||||
$firstError = collect($e->errors())->flatten()->first()
|
||||
?? __('modules.errors.update_failed', ['module' => $moduleName, 'error' => '']);
|
||||
|
||||
return ResponseHelper::error($firstError, 422, $e->errors());
|
||||
return $this->validationError($e->errors(), $firstError);
|
||||
} catch (\Exception $e) {
|
||||
return $this->error('modules.errors.update_failed', 500, $e->getMessage(), [
|
||||
'module' => $moduleName,
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Admin;
|
||||
|
||||
use App\Contracts\Notifications\ChannelReadinessCheckerInterface;
|
||||
use App\Http\Controllers\Api\Base\AdminBaseController;
|
||||
use App\Services\NotificationChannelService;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* 알림 채널 관리 컨트롤러
|
||||
*
|
||||
* 시스템에서 사용 가능한 알림 채널 목록을 제공합니다.
|
||||
* 플러그인이 Filter 훅으로 채널을 확장할 수 있습니다.
|
||||
*/
|
||||
class NotificationChannelController extends AdminBaseController
|
||||
{
|
||||
/**
|
||||
* @param NotificationChannelService $channelService
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly NotificationChannelService $channelService,
|
||||
private readonly ChannelReadinessCheckerInterface $readinessChecker,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* 사용 가능한 알림 채널 목록을 반환합니다.
|
||||
*
|
||||
* 각 채널에 readiness (설정 완료 여부) 정보를 포함합니다.
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
try {
|
||||
$channels = $this->channelService->getAvailableChannels();
|
||||
|
||||
// 각 채널에 readiness 정보 추가
|
||||
$channels = array_map(function (array $channel) {
|
||||
$channelId = $channel['id'] ?? '';
|
||||
$channel['readiness'] = $this->readinessChecker->check($channelId);
|
||||
|
||||
return $channel;
|
||||
}, $channels);
|
||||
|
||||
return $this->success(
|
||||
__('notification.channels_success'),
|
||||
['channels' => $channels]
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('알림 채널 목록 조회 실패', ['error' => $e->getMessage()]);
|
||||
|
||||
return $this->error(__('notification.channels_failed'), 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,29 +3,170 @@
|
||||
namespace App\Http\Controllers\Api\Admin;
|
||||
|
||||
use App\Http\Controllers\Api\Base\AdminBaseController;
|
||||
use App\Http\Requests\Notification\NotificationBatchReadRequest;
|
||||
use App\Http\Requests\Notification\NotificationIndexRequest;
|
||||
use App\Http\Resources\UserNotificationCollection;
|
||||
use App\Http\Resources\UserNotificationResource;
|
||||
use App\Services\NotificationService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* 관리자 알림 컨트롤러 (더미)
|
||||
* 관리자 알림 컨트롤러
|
||||
*
|
||||
* 추후 알림 기능 구현 예정. 현재는 빈 목록을 반환합니다.
|
||||
* 관리자의 사이트내 알림을 관리합니다.
|
||||
*/
|
||||
class NotificationController extends AdminBaseController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly NotificationService $notificationService,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* 알림 목록을 조회합니다.
|
||||
*
|
||||
* @return JsonResponse 알림 목록 (현재 빈 배열)
|
||||
*/
|
||||
public function index(): JsonResponse
|
||||
public function index(NotificationIndexRequest $request): JsonResponse
|
||||
{
|
||||
// TODO: 실제 알림 기능 구현 시 교체 예정
|
||||
return $this->success('messages.success', [
|
||||
'data' => [],
|
||||
'meta' => [
|
||||
'total' => 0,
|
||||
'unread_count' => 0,
|
||||
],
|
||||
]);
|
||||
try {
|
||||
$validated = $request->validated();
|
||||
$user = $request->user();
|
||||
$perPage = (int) ($validated['per_page'] ?? 15);
|
||||
$filters = collect($validated)->only(['read'])->filter(fn ($v) => $v !== null)->toArray();
|
||||
|
||||
$notifications = $this->notificationService->getNotifications($user, $filters, $perPage);
|
||||
|
||||
return $this->success(
|
||||
__('notification.user.list_success'),
|
||||
new UserNotificationCollection($notifications)
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('관리자 알림 목록 조회 실패', ['error' => $e->getMessage()]);
|
||||
|
||||
return $this->error(__('notification.user.list_failed'), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 미읽음 알림 수를 반환합니다.
|
||||
*/
|
||||
public function unreadCount(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$count = $this->notificationService->getUnreadCount($request->user());
|
||||
|
||||
return $this->success(
|
||||
__('notification.user.unread_count_success'),
|
||||
['unread_count' => $count]
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('관리자 미읽음 알림 수 조회 실패', ['error' => $e->getMessage()]);
|
||||
|
||||
return $this->error(__('notification.user.unread_count_failed'), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 알림을 읽음 처리합니다.
|
||||
*/
|
||||
public function markAsRead(Request $request, string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$notification = $this->notificationService->markAsRead($request->user(), $id);
|
||||
|
||||
if (! $notification) {
|
||||
return $this->error(__('notification.user.not_found'), 404);
|
||||
}
|
||||
|
||||
return $this->success(
|
||||
__('notification.user.read_success'),
|
||||
new UserNotificationResource($notification)
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('관리자 알림 읽음 처리 실패', ['id' => $id, 'error' => $e->getMessage()]);
|
||||
|
||||
return $this->error(__('notification.user.read_failed'), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 지정된 알림들을 일괄 읽음 처리합니다.
|
||||
*/
|
||||
public function markBatchAsRead(NotificationBatchReadRequest $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$ids = $request->validated()['ids'];
|
||||
|
||||
$count = $this->notificationService->markBatchAsRead($request->user(), $ids);
|
||||
|
||||
return $this->success(
|
||||
__('notification.user.read_success'),
|
||||
['marked_count' => $count]
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('관리자 알림 일괄 읽음 처리 실패', ['error' => $e->getMessage()]);
|
||||
|
||||
return $this->error(__('notification.user.read_failed'), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 모든 알림을 읽음 처리합니다.
|
||||
*/
|
||||
public function markAllAsRead(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$count = $this->notificationService->markAllAsRead($request->user());
|
||||
|
||||
return $this->success(
|
||||
__('notification.user.read_all_success'),
|
||||
['marked_count' => $count]
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('관리자 전체 읽음 처리 실패', ['error' => $e->getMessage()]);
|
||||
|
||||
return $this->error(__('notification.user.read_all_failed'), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 사용자의 모든 알림을 삭제합니다.
|
||||
*/
|
||||
public function destroyAll(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$count = $this->notificationService->deleteAllNotifications($request->user());
|
||||
|
||||
return $this->success(
|
||||
__('notification.user.delete_all_success'),
|
||||
['deleted_count' => $count]
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('관리자 전체 알림 삭제 실패', ['error' => $e->getMessage()]);
|
||||
|
||||
return $this->error(__('notification.user.delete_all_failed'), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 알림을 삭제합니다.
|
||||
*/
|
||||
public function destroy(Request $request, string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$deleted = $this->notificationService->deleteNotification($request->user(), $id);
|
||||
|
||||
if (! $deleted) {
|
||||
return $this->error(__('notification.user.not_found'), 404);
|
||||
}
|
||||
|
||||
return $this->success(__('notification.user.delete_success'));
|
||||
} catch (\Exception $e) {
|
||||
Log::error('관리자 알림 삭제 실패', ['id' => $id, 'error' => $e->getMessage()]);
|
||||
|
||||
return $this->error(__('notification.user.delete_failed'), 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Admin;
|
||||
|
||||
use App\Http\Controllers\Api\Base\AdminBaseController;
|
||||
use App\Http\Requests\NotificationDefinition\NotificationDefinitionIndexRequest;
|
||||
use App\Http\Requests\NotificationDefinition\UpdateNotificationDefinitionRequest;
|
||||
use App\Http\Resources\NotificationDefinitionCollection;
|
||||
use App\Http\Resources\NotificationDefinitionResource;
|
||||
use App\Models\NotificationDefinition;
|
||||
use App\Services\NotificationDefinitionService;
|
||||
use App\Services\NotificationTemplateService;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* 알림 정의 관리 컨트롤러
|
||||
*/
|
||||
class NotificationDefinitionController extends AdminBaseController
|
||||
{
|
||||
/**
|
||||
* @param NotificationDefinitionService $definitionService
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly NotificationDefinitionService $definitionService,
|
||||
private readonly NotificationTemplateService $templateService,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* 알림 정의 목록을 조회합니다.
|
||||
*
|
||||
* @param NotificationDefinitionIndexRequest $request
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function index(NotificationDefinitionIndexRequest $request)
|
||||
{
|
||||
try {
|
||||
$filters = $request->validated();
|
||||
$perPage = $filters['per_page'] ?? 20;
|
||||
|
||||
$definitions = $this->definitionService->getDefinitions($filters, $perPage);
|
||||
$collection = new NotificationDefinitionCollection($definitions);
|
||||
|
||||
return $this->success(
|
||||
__('notification.definition_list_success'),
|
||||
$collection->toArray($request)
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('알림 정의 목록 조회 실패', ['error' => $e->getMessage()]);
|
||||
|
||||
return $this->error(__('notification.definition_list_failed'), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 알림 정의 상세를 조회합니다.
|
||||
*
|
||||
* @param NotificationDefinition $definition
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function show(NotificationDefinition $definition)
|
||||
{
|
||||
try {
|
||||
$definition->load('templates');
|
||||
|
||||
return $this->success(
|
||||
__('notification.definition_show_success'),
|
||||
new NotificationDefinitionResource($definition)
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('알림 정의 상세 조회 실패', [
|
||||
'definition_id' => $definition->id,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
return $this->error(__('notification.definition_show_failed'), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 알림 정의를 수정합니다.
|
||||
*
|
||||
* @param UpdateNotificationDefinitionRequest $request
|
||||
* @param NotificationDefinition $definition
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function update(UpdateNotificationDefinitionRequest $request, NotificationDefinition $definition)
|
||||
{
|
||||
try {
|
||||
$updated = $this->definitionService->updateDefinition(
|
||||
$definition,
|
||||
$request->validated(),
|
||||
$this->getCurrentAdmin()?->id
|
||||
);
|
||||
|
||||
return $this->success(
|
||||
__('notification.definition_updated'),
|
||||
new NotificationDefinitionResource($updated->load('templates'))
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('알림 정의 수정 실패', [
|
||||
'definition_id' => $definition->id,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
return $this->error(__('notification.definition_update_failed'), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 알림 정의 활성/비활성을 토글합니다.
|
||||
*
|
||||
* @param NotificationDefinition $definition
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function toggleActive(NotificationDefinition $definition)
|
||||
{
|
||||
try {
|
||||
$updated = $this->definitionService->toggleActive($definition);
|
||||
|
||||
return $this->success(
|
||||
__('notification.definition_toggled'),
|
||||
new NotificationDefinitionResource($updated)
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('알림 정의 활성 토글 실패', [
|
||||
'definition_id' => $definition->id,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
return $this->error(__('notification.definition_toggle_failed'), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 알림 정의의 모든 템플릿을 기본값으로 일괄 복원합니다.
|
||||
*
|
||||
* @param NotificationDefinition $definition
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function reset(NotificationDefinition $definition)
|
||||
{
|
||||
try {
|
||||
$definition->load('templates');
|
||||
$resetCount = 0;
|
||||
|
||||
foreach ($definition->templates as $template) {
|
||||
$defaultData = $this->templateService->getDefaultTemplateData(
|
||||
$definition->type,
|
||||
$template->channel
|
||||
);
|
||||
|
||||
if (! empty($defaultData)) {
|
||||
$this->templateService->resetToDefault($template, $defaultData);
|
||||
$resetCount++;
|
||||
}
|
||||
}
|
||||
|
||||
$this->definitionService->markAsDefault($definition);
|
||||
$definition->load('templates');
|
||||
|
||||
return $this->success(
|
||||
__('notification.definition_reset'),
|
||||
new NotificationDefinitionResource($definition)
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('알림 정의 초기화 실패', [
|
||||
'definition_id' => $definition->id,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
return $this->error(__('notification.definition_reset_failed'), 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Admin;
|
||||
|
||||
use App\Http\Controllers\Api\Base\AdminBaseController;
|
||||
use App\Http\Requests\NotificationLog\NotificationLogBulkDeleteRequest;
|
||||
use App\Http\Requests\NotificationLog\NotificationLogDeleteRequest;
|
||||
use App\Http\Requests\NotificationLog\NotificationLogIndexRequest;
|
||||
use App\Http\Resources\NotificationLogCollection;
|
||||
use App\Models\NotificationLog;
|
||||
use App\Services\NotificationLogService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* 알림 발송 이력 관리 컨트롤러
|
||||
*/
|
||||
class NotificationLogController extends AdminBaseController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly NotificationLogService $logService,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* 알림 발송 이력 목록을 조회합니다.
|
||||
*/
|
||||
public function index(NotificationLogIndexRequest $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$filters = $request->validated();
|
||||
$perPage = $filters['per_page'] ?? 20;
|
||||
|
||||
$logs = $this->logService->getLogs($filters, $perPage, $request->user());
|
||||
$collection = new NotificationLogCollection($logs);
|
||||
|
||||
return $this->success(
|
||||
__('notification_log.list_success'),
|
||||
$collection->toArray($request)
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('알림 발송 이력 목록 조회 실패', ['error' => $e->getMessage()]);
|
||||
|
||||
return $this->error(__('notification_log.list_failed'), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 알림 발송 이력을 삭제합니다.
|
||||
*/
|
||||
public function destroy(NotificationLogDeleteRequest $request, NotificationLog $notificationLog): JsonResponse
|
||||
{
|
||||
try {
|
||||
$this->logService->deleteLog($notificationLog);
|
||||
|
||||
return $this->success(__('notification_log.delete_success'));
|
||||
} catch (\Exception $e) {
|
||||
Log::error('알림 발송 이력 삭제 실패', [
|
||||
'id' => $notificationLog->id,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
return $this->error(__('notification_log.delete_failed'), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 알림 발송 이력을 다건 삭제합니다.
|
||||
*/
|
||||
public function bulkDestroy(NotificationLogBulkDeleteRequest $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$ids = $request->validated()['ids'];
|
||||
$count = $this->logService->bulkDelete($ids);
|
||||
|
||||
return $this->success(
|
||||
__('notification_log.bulk_delete_success'),
|
||||
['deleted_count' => $count]
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('알림 발송 이력 다건 삭제 실패', ['error' => $e->getMessage()]);
|
||||
|
||||
return $this->error(__('notification_log.bulk_delete_failed'), 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Admin;
|
||||
|
||||
use App\Http\Controllers\Api\Base\AdminBaseController;
|
||||
use App\Http\Requests\NotificationTemplate\PreviewNotificationTemplateRequest;
|
||||
use App\Http\Requests\NotificationTemplate\UpdateNotificationTemplateRequest;
|
||||
use App\Http\Resources\NotificationTemplateResource;
|
||||
use App\Models\NotificationTemplate;
|
||||
use App\Services\NotificationTemplateService;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* 알림 템플릿 관리 컨트롤러
|
||||
*/
|
||||
class NotificationTemplateController extends AdminBaseController
|
||||
{
|
||||
/**
|
||||
* @param NotificationTemplateService $templateService
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly NotificationTemplateService $templateService,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* 알림 템플릿을 수정합니다.
|
||||
*
|
||||
* @param UpdateNotificationTemplateRequest $request
|
||||
* @param NotificationTemplate $template
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function update(UpdateNotificationTemplateRequest $request, NotificationTemplate $template)
|
||||
{
|
||||
try {
|
||||
$updated = $this->templateService->updateTemplate(
|
||||
$template,
|
||||
$request->validated(),
|
||||
$this->getCurrentAdmin()?->id
|
||||
);
|
||||
|
||||
return $this->success(
|
||||
__('notification.template_updated'),
|
||||
new NotificationTemplateResource($updated)
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('알림 템플릿 수정 실패', [
|
||||
'template_id' => $template->id,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
return $this->error(__('notification.template_update_failed'), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 알림 템플릿 활성/비활성을 토글합니다.
|
||||
*
|
||||
* @param NotificationTemplate $template
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function toggleActive(NotificationTemplate $template)
|
||||
{
|
||||
try {
|
||||
$updated = $this->templateService->toggleActive($template);
|
||||
|
||||
return $this->success(
|
||||
__('notification.template_toggled'),
|
||||
new NotificationTemplateResource($updated)
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('알림 템플릿 활성 토글 실패', [
|
||||
'template_id' => $template->id,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
return $this->error(__('notification.template_toggle_failed'), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 알림 템플릿 미리보기를 반환합니다.
|
||||
*
|
||||
* @param PreviewNotificationTemplateRequest $request
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function preview(PreviewNotificationTemplateRequest $request)
|
||||
{
|
||||
try {
|
||||
$result = $this->templateService->getPreview($request->validated());
|
||||
|
||||
return $this->success(__('notification.preview_success'), $result);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('알림 템플릿 미리보기 실패', ['error' => $e->getMessage()]);
|
||||
|
||||
return $this->error(__('notification.preview_failed'), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 알림 템플릿을 기본값으로 복원합니다.
|
||||
*
|
||||
* @param NotificationTemplate $template
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function reset(NotificationTemplate $template)
|
||||
{
|
||||
try {
|
||||
$definition = $template->definition;
|
||||
if (! $definition) {
|
||||
return $this->error(__('notification.definition_not_found'), 404);
|
||||
}
|
||||
|
||||
$defaultData = $this->templateService->getDefaultTemplateData($definition->type, $template->channel);
|
||||
if (empty($defaultData)) {
|
||||
return $this->error(__('notification.default_data_not_found'), 404);
|
||||
}
|
||||
|
||||
$updated = $this->templateService->resetToDefault($template, $defaultData);
|
||||
|
||||
return $this->success(
|
||||
__('notification.template_reset'),
|
||||
new NotificationTemplateResource($updated)
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('알림 템플릿 초기화 실패', [
|
||||
'template_id' => $template->id,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
return $this->error(__('notification.template_reset_failed'), 500);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace App\Http\Controllers\Api\Admin;
|
||||
|
||||
use App\Helpers\PermissionHelper;
|
||||
use App\Helpers\ResponseHelper;
|
||||
use App\Http\Controllers\Api\Base\AdminBaseController;
|
||||
use App\Http\Requests\Plugin\ActivatePluginRequest;
|
||||
use App\Http\Requests\Plugin\DeactivatePluginRequest;
|
||||
@@ -11,6 +10,7 @@ use App\Http\Requests\Plugin\IndexPluginRequest;
|
||||
use App\Http\Requests\Plugin\InstallPluginFromFileRequest;
|
||||
use App\Http\Requests\Plugin\InstallPluginFromGithubRequest;
|
||||
use App\Http\Requests\Plugin\InstallPluginRequest;
|
||||
use App\Http\Requests\Plugin\PerformPluginUpdateRequest;
|
||||
use App\Http\Requests\Plugin\RefreshPluginLayoutsRequest;
|
||||
use App\Http\Requests\Plugin\UninstallPluginRequest;
|
||||
use App\Http\Requests\Extension\ChangelogRequest;
|
||||
@@ -139,8 +139,12 @@ class PluginController extends AdminBaseController
|
||||
public function install(InstallPluginRequest $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$pluginName = $request->validated()['plugin_name'];
|
||||
$pluginInfo = $this->pluginService->installPlugin($pluginName);
|
||||
$validated = $request->validated();
|
||||
$pluginName = $validated['plugin_name'];
|
||||
$vendorMode = \App\Extension\Vendor\VendorMode::fromStringOrAuto(
|
||||
$validated['vendor_mode'] ?? null
|
||||
);
|
||||
$pluginInfo = $this->pluginService->installPlugin($pluginName, $vendorMode);
|
||||
|
||||
if ($pluginInfo) {
|
||||
return $this->successWithResource(
|
||||
@@ -156,7 +160,7 @@ class PluginController extends AdminBaseController
|
||||
$firstError = collect($e->errors())->flatten()->first()
|
||||
?? __('plugins.install_failed');
|
||||
|
||||
return ResponseHelper::error($firstError, 422, $e->errors());
|
||||
return $this->validationError($e->errors(), $firstError);
|
||||
} catch (\Exception $e) {
|
||||
return $this->error('plugins.installation_failed', 500, $e->getMessage(), [
|
||||
'error' => $e->getMessage(),
|
||||
@@ -419,16 +423,45 @@ class PluginController extends AdminBaseController
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 특정 플러그인의 수정된 레이아웃을 확인합니다.
|
||||
*
|
||||
* @param string $pluginName 플러그인 식별자
|
||||
* @return JsonResponse 수정된 레이아웃 정보를 포함한 JSON 응답
|
||||
*/
|
||||
public function checkModifiedLayouts(string $pluginName): JsonResponse
|
||||
{
|
||||
try {
|
||||
$result = $this->pluginService->checkModifiedLayouts($pluginName);
|
||||
|
||||
return $this->success('plugins.check_modified_layouts_success', $result);
|
||||
} catch (ValidationException $e) {
|
||||
return $this->error('plugins.check_modified_layouts_failed', 422, $e->errors());
|
||||
} catch (\Exception $e) {
|
||||
return $this->error('plugins.check_modified_layouts_failed', 500, $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 특정 플러그인을 업데이트합니다.
|
||||
*
|
||||
* layout_strategy 파라미터로 레이아웃 처리 방식을 결정합니다:
|
||||
* - overwrite: 모든 레이아웃을 새 버전으로 교체
|
||||
* - keep: 사용자가 수정한 레이아웃을 유지
|
||||
*
|
||||
* @param PerformPluginUpdateRequest $request 업데이트 요청 데이터
|
||||
* @param string $pluginName 업데이트할 플러그인 identifier
|
||||
* @return JsonResponse 업데이트 결과 JSON 응답
|
||||
*/
|
||||
public function performUpdate(string $pluginName): JsonResponse
|
||||
public function performUpdate(PerformPluginUpdateRequest $request, string $pluginName): JsonResponse
|
||||
{
|
||||
try {
|
||||
$result = $this->pluginService->updatePlugin($pluginName);
|
||||
$validated = $request->validated();
|
||||
$vendorMode = \App\Extension\Vendor\VendorMode::fromStringOrAuto(
|
||||
$validated['vendor_mode'] ?? null
|
||||
);
|
||||
$layoutStrategy = $validated['layout_strategy'] ?? 'overwrite';
|
||||
$result = $this->pluginService->updatePlugin($pluginName, $vendorMode, $layoutStrategy);
|
||||
|
||||
$pluginInfo = $result['plugin_info'] ?? null;
|
||||
|
||||
@@ -446,7 +479,7 @@ class PluginController extends AdminBaseController
|
||||
$firstError = collect($e->errors())->flatten()->first()
|
||||
?? __('plugins.errors.update_failed', ['plugin' => $pluginName, 'error' => '']);
|
||||
|
||||
return ResponseHelper::error($firstError, 422, $e->errors());
|
||||
return $this->validationError($e->errors(), $firstError);
|
||||
} catch (\Exception $e) {
|
||||
return $this->error('plugins.errors.update_failed', 500, $e->getMessage(), [
|
||||
'plugin' => $pluginName,
|
||||
|
||||
@@ -10,6 +10,7 @@ use App\Http\Requests\Settings\TestMailRequest;
|
||||
use App\Http\Requests\Settings\UpdateSettingRequest;
|
||||
use App\Http\Resources\SettingsResource;
|
||||
use App\Services\DriverConnectionTester;
|
||||
use App\Services\DriverRegistryService;
|
||||
use App\Services\SettingsService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -24,7 +25,8 @@ class SettingsController extends AdminBaseController
|
||||
{
|
||||
public function __construct(
|
||||
private SettingsService $settingsService,
|
||||
private DriverConnectionTester $driverConnectionTester
|
||||
private DriverConnectionTester $driverConnectionTester,
|
||||
private DriverRegistryService $driverRegistryService
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
@@ -38,6 +40,7 @@ class SettingsController extends AdminBaseController
|
||||
{
|
||||
try {
|
||||
$settings = $this->settingsService->getAllSettings();
|
||||
$settings['available_drivers'] = $this->driverRegistryService->getAllAvailableDrivers();
|
||||
|
||||
return $this->success('settings.fetch_success',
|
||||
(new SettingsResource($settings))->toArray(request())
|
||||
@@ -64,6 +67,7 @@ class SettingsController extends AdminBaseController
|
||||
if ($result) {
|
||||
// 저장 후 전체 설정 반환 (관리자 UI 상태 업데이트용)
|
||||
$allSettings = $this->settingsService->getAllSettings();
|
||||
$allSettings['available_drivers'] = $this->driverRegistryService->getAllAvailableDrivers();
|
||||
|
||||
return $this->success('settings.save_success', [
|
||||
'settings' => $allSettings,
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace App\Http\Controllers\Api\Admin;
|
||||
|
||||
use App\Helpers\PermissionHelper;
|
||||
use App\Helpers\ResponseHelper;
|
||||
use App\Http\Controllers\Api\Base\AdminBaseController;
|
||||
use App\Http\Requests\Template\ActivateTemplateRequest;
|
||||
use App\Http\Requests\Template\DeactivateTemplateRequest;
|
||||
@@ -135,7 +134,7 @@ class TemplateController extends AdminBaseController
|
||||
$firstError = collect($e->errors())->flatten()->first()
|
||||
?? __('templates.install_failed');
|
||||
|
||||
return ResponseHelper::error($firstError, 422, $e->errors());
|
||||
return $this->validationError($e->errors(), $firstError);
|
||||
} catch (\Exception $e) {
|
||||
return $this->error('templates.errors.installation_failed', 500, $e->getMessage(), [
|
||||
'error' => $e->getMessage(),
|
||||
@@ -414,7 +413,7 @@ class TemplateController extends AdminBaseController
|
||||
$firstError = collect($e->errors())->flatten()->first()
|
||||
?? __('templates.errors.update_failed', ['template' => $templateName, 'error' => '']);
|
||||
|
||||
return ResponseHelper::error($firstError, 422, $e->errors());
|
||||
return $this->validationError($e->errors(), $firstError);
|
||||
} catch (\Exception $e) {
|
||||
return $this->error('templates.errors.update_failed', 500, $e->getMessage(), [
|
||||
'template' => $templateName,
|
||||
|
||||
@@ -23,7 +23,7 @@ use Illuminate\Validation\ValidationException;
|
||||
|
||||
/**
|
||||
* 관리자용 사용자 관리 컨트롤러
|
||||
*
|
||||
*
|
||||
* 관리자가 시스템 사용자들을 관리할 수 있는 기능을 제공합니다.
|
||||
*/
|
||||
class UserController extends AdminBaseController
|
||||
@@ -78,7 +78,7 @@ class UserController extends AdminBaseController
|
||||
} catch (ValidationException $e) {
|
||||
return $this->error('user.create_failed', 422, $e->errors());
|
||||
} catch (Exception $e) {
|
||||
return $this->error('user.create_failed', 500, $e->getMessage());
|
||||
return $this->error('user.create_failed', 500, $e, ['error' => $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,7 +126,7 @@ class UserController extends AdminBaseController
|
||||
} catch (ValidationException $e) {
|
||||
return $this->error('user.update_failed', 422, $e->errors());
|
||||
} catch (Exception $e) {
|
||||
return $this->error('user.update_failed', 500, $e->getMessage());
|
||||
return $this->error('user.update_failed', 500, $e, ['error' => $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,7 +154,7 @@ class UserController extends AdminBaseController
|
||||
} catch (ValidationException $e) {
|
||||
return $this->error('user.delete_failed', 422, $e->errors());
|
||||
} catch (Exception $e) {
|
||||
return $this->error('user.delete_failed', 500);
|
||||
return $this->error('user.delete_failed', 500, $e, ['error' => $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Auth;
|
||||
|
||||
use App\Http\Controllers\Api\Base\AuthBaseController;
|
||||
use App\Http\Requests\Notification\NotificationBatchReadRequest;
|
||||
use App\Http\Requests\Notification\NotificationIndexRequest;
|
||||
use App\Http\Resources\UserNotificationCollection;
|
||||
use App\Http\Resources\UserNotificationResource;
|
||||
use App\Services\NotificationService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* 사용자 알림 컨트롤러
|
||||
*
|
||||
* 인증된 사용자의 사이트내 알림을 관리합니다.
|
||||
*/
|
||||
class NotificationController extends AuthBaseController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly NotificationService $notificationService,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* 알림 목록을 조회합니다.
|
||||
*/
|
||||
public function index(NotificationIndexRequest $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$validated = $request->validated();
|
||||
$user = $request->user();
|
||||
$perPage = (int) ($validated['per_page'] ?? 20);
|
||||
$filters = collect($validated)->only(['read'])->filter(fn ($v) => $v !== null)->toArray();
|
||||
|
||||
$notifications = $this->notificationService->getNotifications($user, $filters, $perPage);
|
||||
|
||||
return $this->success(
|
||||
__('notification.user.list_success'),
|
||||
new UserNotificationCollection($notifications)
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('알림 목록 조회 실패', ['error' => $e->getMessage()]);
|
||||
|
||||
return $this->error(__('notification.user.list_failed'), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 미읽음 알림 수를 반환합니다.
|
||||
*/
|
||||
public function unreadCount(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$count = $this->notificationService->getUnreadCount($request->user());
|
||||
|
||||
return $this->success(
|
||||
__('notification.user.unread_count_success'),
|
||||
['unread_count' => $count]
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('미읽음 알림 수 조회 실패', ['error' => $e->getMessage()]);
|
||||
|
||||
return $this->error(__('notification.user.unread_count_failed'), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 알림을 읽음 처리합니다.
|
||||
*/
|
||||
public function markAsRead(Request $request, string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$notification = $this->notificationService->markAsRead($request->user(), $id);
|
||||
|
||||
if (! $notification) {
|
||||
return $this->error(__('notification.user.not_found'), 404);
|
||||
}
|
||||
|
||||
return $this->success(
|
||||
__('notification.user.read_success'),
|
||||
new UserNotificationResource($notification)
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('알림 읽음 처리 실패', ['id' => $id, 'error' => $e->getMessage()]);
|
||||
|
||||
return $this->error(__('notification.user.read_failed'), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 지정된 알림들을 일괄 읽음 처리합니다.
|
||||
*/
|
||||
public function markBatchAsRead(NotificationBatchReadRequest $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$ids = $request->validated()['ids'];
|
||||
|
||||
$count = $this->notificationService->markBatchAsRead($request->user(), $ids);
|
||||
|
||||
return $this->success(
|
||||
__('notification.user.read_success'),
|
||||
['marked_count' => $count]
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('알림 일괄 읽음 처리 실패', ['error' => $e->getMessage()]);
|
||||
|
||||
return $this->error(__('notification.user.read_failed'), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 모든 알림을 읽음 처리합니다.
|
||||
*/
|
||||
public function markAllAsRead(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$count = $this->notificationService->markAllAsRead($request->user());
|
||||
|
||||
return $this->success(
|
||||
__('notification.user.read_all_success'),
|
||||
['marked_count' => $count]
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('전체 읽음 처리 실패', ['error' => $e->getMessage()]);
|
||||
|
||||
return $this->error(__('notification.user.read_all_failed'), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 사용자의 모든 알림을 삭제합니다.
|
||||
*/
|
||||
public function destroyAll(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$count = $this->notificationService->deleteAllNotifications($request->user());
|
||||
|
||||
return $this->success(
|
||||
__('notification.user.delete_all_success'),
|
||||
['deleted_count' => $count]
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('전체 알림 삭제 실패', ['error' => $e->getMessage()]);
|
||||
|
||||
return $this->error(__('notification.user.delete_all_failed'), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 알림을 삭제합니다.
|
||||
*/
|
||||
public function destroy(Request $request, string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$deleted = $this->notificationService->deleteNotification($request->user(), $id);
|
||||
|
||||
if (! $deleted) {
|
||||
return $this->error(__('notification.user.not_found'), 404);
|
||||
}
|
||||
|
||||
return $this->success(__('notification.user.delete_success'));
|
||||
} catch (\Exception $e) {
|
||||
Log::error('알림 삭제 실패', ['id' => $id, 'error' => $e->getMessage()]);
|
||||
|
||||
return $this->error(__('notification.user.delete_failed'), 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -130,8 +130,7 @@ class ProfileController extends AuthBaseController
|
||||
return $this->unauthorized('auth.unauthenticated');
|
||||
}
|
||||
|
||||
// TODO: 사용자 활동 로그 서비스 구현
|
||||
$activities = []; // $this->userService->getUserActivities($user->id);
|
||||
$activities = $this->userService->getUserActivityLogs($user->id);
|
||||
|
||||
$this->logUserActivity('profile.activity_log');
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Http\Controllers\Api\Base;
|
||||
|
||||
use App\Contracts\Extension\CacheInterface;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
@@ -22,6 +23,10 @@ abstract class PublicBaseController extends BaseApiController
|
||||
/**
|
||||
* 캐시된 응답을 반환하거나 새로 생성합니다.
|
||||
*
|
||||
* 주의: 콜백 반환값이 배열이고 `error` 키가 있으면 캐시하지 않는다.
|
||||
* 이는 확장 설치 직후·활성화 전 등의 일시적 상태에서 얻은 "not found" 같은
|
||||
* 에러 응답이 영구 캐시되어 복구 후에도 잘못된 응답을 반환하는 문제를 방지한다.
|
||||
*
|
||||
* @param string $key 캐시 키
|
||||
* @param callable $callback 데이터 생성 콜백
|
||||
* @param int $ttl 캐시 유지 시간 (초)
|
||||
@@ -29,7 +34,21 @@ abstract class PublicBaseController extends BaseApiController
|
||||
*/
|
||||
protected function cached(string $key, callable $callback, int $ttl = 3600)
|
||||
{
|
||||
return cache()->remember($key, $ttl, $callback);
|
||||
$cache = app(CacheInterface::class);
|
||||
|
||||
$cached = $cache->get($key);
|
||||
if ($cached !== null) {
|
||||
return $cached;
|
||||
}
|
||||
|
||||
$result = $callback();
|
||||
|
||||
// 에러 응답은 캐시하지 않음 (일시적 상태 회복 가능성 보장)
|
||||
if (! (is_array($result) && isset($result['error']))) {
|
||||
$cache->put($key, $result, $ttl);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -57,7 +57,7 @@ class PublicSearchController extends PublicBaseController
|
||||
$response = $this->buildResponse($q, $results, $context);
|
||||
|
||||
return $this->success(
|
||||
__('search.results_found', ['count' => $response['total']]),
|
||||
__('search.results_found', ['count' => number_format($response['total'])]),
|
||||
$response
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
namespace App\Http\Controllers\Api\Public;
|
||||
|
||||
use App\Contracts\Extension\CacheInterface;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Seo\SitemapGenerator;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
/**
|
||||
* Sitemap XML 컨트롤러
|
||||
@@ -21,20 +21,21 @@ class SitemapController extends Controller
|
||||
* 캐시가 있으면 캐시된 XML, 없으면 실시간 생성 후 캐시 저장.
|
||||
*
|
||||
* @param SitemapGenerator $generator Sitemap 생성기
|
||||
* @param CacheInterface $cache 코어 캐시 드라이버
|
||||
*/
|
||||
public function index(SitemapGenerator $generator): Response
|
||||
public function index(SitemapGenerator $generator, CacheInterface $cache): Response
|
||||
{
|
||||
$enabled = (bool) g7_core_settings('seo.sitemap_enabled', true);
|
||||
if (! $enabled) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
$xml = Cache::get('seo:sitemap');
|
||||
$xml = $cache->get('seo.sitemap');
|
||||
|
||||
if (! $xml) {
|
||||
$xml = $generator->generate();
|
||||
$ttl = (int) g7_core_settings('seo.sitemap_cache_ttl', 86400);
|
||||
Cache::put('seo:sitemap', $xml, $ttl);
|
||||
$ttl = (int) g7_core_settings('cache.seo_sitemap_ttl', g7_core_settings('seo.sitemap_cache_ttl', 86400));
|
||||
$cache->put('seo.sitemap', $xml, $ttl);
|
||||
}
|
||||
|
||||
return response($xml, 200, [
|
||||
|
||||
Reference in New Issue
Block a user