fix(page): 일괄 발행/미발행 시 활동 이력이 기록되도록 after_publish 훅 발화

페이지 목록 일괄 발행/미발행이 after_publish 훅을 발화하지 않아
활동로그에 발행/미발행이 기록되지 않던 문제를 수정한다.

- bulkChangePublishStatus 를 DB::transaction 내부에서 페이지별로
 before_publish -> update -> after_publish 발화하도록 재작성 (per-item 로그)
- 존재하지 않는 id 가 섞이면 전체 롤백 (all-or-nothing)
- 단일 mass UPDATE 전용 bulkUpdatePublished 제거 (dead code, 의존 확장 영향 없음)
- 일괄 발행 성공 응답 메시지의 :count 미치환 정정
 (success 의 4번째 인자 messageParams 로 치환 파라미터 전달)
- 회귀 테스트 추가 (훅 발화 횟수/published 인자/롤백/메시지 치환)
This commit is contained in:
chym1217 committed 2026-07-06 09:37:02 +09:00
1 parent 0de58baf46
commit bb654fb56c
7 files changed
+144 -32

No files matched your search

@@ -30,6 +30,7 @@
- 새 페이지 등록 화면에서 슬러그를 입력하기 전 주소 미리보기가 "/page/{{slug}}" 처럼 표시되던 문제를 수정했습니다. 이제 입력 전에는 "/page/example" 로 보이고, 입력하면 입력한 주소로 즉시 바뀝니다. (#424 @gnuboard 님께서 제보해주셨습니다.)
- 관리자 화면에서 페이지 첨부파일의 다운로드 / 미리보기가 관리자 전용 주소로 열리도록 수정했습니다. 이전에는 관리자 화면에서도 공개용 주소가 사용되어 비공개 페이지의 첨부파일을 관리자가 열지 못하는 경우가 있었습니다. (#424 @gnuboard 님께서 제보해주셨습니다.)
- 페이지를 수정하거나 삭제한 직후 해당 페이지 상세 화면의 검색엔진용 캐시가 즉시 갱신되도록 수정했습니다. 이전에는 일부 캐시가 갱신되지 않아 변경 전 내용이 검색엔진에 남는 경우가 있었습니다. (#424 @gnuboard 님께서 제보해주셨습니다.)
- 페이지 목록에서 여러 페이지를 한 번에 발행 / 미발행할 때 활동 이력에 발행 여부가 기록되지 않던 문제를 수정했습니다. 이제 일괄 처리에서도 페이지별로 발행 / 미발행 기록이 남습니다. (#424 @gnuboard 님께서 제보해주셨습니다.)
## [1.0.0-beta.3] - 2026-05-11
@@ -212,9 +212,12 @@ class PageController extends AdminBaseController
$validated = $request->validated();
$count = $this->pageService->bulkChangePublishStatus($validated['ids'], $validated['published']);
return $this->success('sirsoft-page::messages.page.bulk_publish_success', [
'count' => $count,
]);
return $this->success(
'sirsoft-page::messages.page.bulk_publish_success',
['count' => $count],
200,
['count' => $count],
);
} catch (\Exception $e) {
return $this->error('sirsoft-page::messages.page.bulk_publish_failed', 500, $e->getMessage());
}
@@ -97,13 +97,4 @@ interface PageRepositoryInterface
* @return int 일치하는 페이지 수
*/
public function countByKeyword(string $keyword): int;
/**
* 여러 페이지의 발행 상태를 일괄 변경합니다.
*
* @param array $ids 페이지 ID 목록
* @param array $data 업데이트할 데이터 (published, updated_by 등)
* @return int 변경된 페이지 수
*/
public function bulkUpdatePublished(array $ids, array $data): int;
}
@@ -289,18 +289,6 @@ class PageRepository implements PageRepositoryInterface
}
}
/**
* 여러 페이지의 발행 상태를 일괄 변경합니다.
*
* @param array $ids 페이지 ID 목록
* @param array $data 업데이트할 데이터 (published, updated_by 등)
* @return int 변경된 페이지 수
*/
public function bulkUpdatePublished(array $ids, array $data): int
{
return Page::whereIn('id', $ids)->update($data);
}
/**
* filters 배열을 search/search_field로 정규화합니다.
*
@@ -211,16 +211,40 @@ class PageService
*/
public function bulkChangePublishStatus(array $ids, bool $published): int
{
$updateData = [
'published' => $published,
'updated_by' => Auth::id(),
];
if ($published) {
$updateData['published_at'] = now();
if (empty($ids)) {
return 0;
}
return $this->pageRepository->bulkUpdatePublished($ids, $updateData);
return DB::transaction(function () use ($ids, $published) {
$userId = Auth::id();
$count = 0;
foreach ($ids as $id) {
// 존재하지 않는 페이지가 섞이면 예외 → 트랜잭션 전체 롤백 (all-or-nothing)
$page = $this->pageRepository->findOrFail((int) $id);
HookManager::doAction('sirsoft-page.page.before_publish', $page, $published);
$updateData = [
'published' => $published,
'updated_by' => $userId,
];
// 발행 시 published_at 갱신
if ($published) {
$updateData['published_at'] = now();
}
$page = $this->pageRepository->update($page, $updateData);
// 페이지별 after_publish 발화 → 활동로그 per-item 기록 (이슈 #424-19)
HookManager::doAction('sirsoft-page.page.after_publish', $page, $published);
$count++;
}
return $count;
});
}
/**
@@ -882,6 +882,11 @@ class PageControllerTest extends FeatureTestCase
->assertJsonPath('success', true)
->assertJsonPath('data.count', 3);
// 성공 메시지의 :count 플레이스홀더가 실제 건수로 치환되는지 확인 (이슈 #424-19 회귀)
$message = $response->json('message');
$this->assertStringNotContainsString(':count', $message, '메시지에 :count 플레이스홀더가 치환되지 않고 남았습니다.');
$this->assertStringContainsString('3', $message, '메시지에 변경 건수(3)가 포함되어야 합니다.');
// DB 확인
foreach ($ids as $id) {
$this->assertDatabaseHas('pages', ['id' => $id, 'published' => true]);
@@ -4,6 +4,7 @@ namespace Modules\Sirsoft\Page\Tests\Unit\Services;
use App\Enums\PermissionType;
use App\Enums\ScopeType;
use App\Extension\HookManager;
use App\Helpers\PermissionHelper;
use App\Models\Permission;
use App\Models\Role;
@@ -206,6 +207,105 @@ class PageServiceTest extends ModuleTestCase
);
$this->assertEquals(3, $count);
// 일괄 발행 후 published/published_at 가 실제 갱신되는지 확인
foreach ($pages as $page) {
$this->assertDatabaseHas('pages', [
'id' => $page->id,
'published' => true,
]);
}
}
/**
* 일괄 발행 시 after_publish 훅이 페이지 수만큼 발화되는지 확인 (이슈 #424-19 회귀)
*
* 일괄변경 경로가 after_publish 훅을 발화하지 않으면 활동로그가 기록되지 않는다.
* 단일 발행(changePublishStatus)과 동일하게 페이지별(per-item) 발화를 보장한다.
*/
public function test_bulk_change_publish_status_fires_after_publish_hook_per_page(): void
{
$pages = Page::factory()->count(3)->create([
'published' => false,
'created_by' => $this->adminUser->id,
'updated_by' => $this->adminUser->id,
]);
$firedPageIds = [];
$firedPublishedFlags = [];
HookManager::addAction(
'sirsoft-page.page.after_publish',
function (Page $page, bool $published) use (&$firedPageIds, &$firedPublishedFlags) {
$firedPageIds[] = $page->id;
$firedPublishedFlags[] = $published;
}
);
$this->service->bulkChangePublishStatus($pages->pluck('id')->toArray(), true);
// 페이지 수만큼 발화 + 각 페이지 id 가 1회씩
$this->assertCount(3, $firedPageIds);
$this->assertEqualsCanonicalizing(
$pages->pluck('id')->toArray(),
$firedPageIds
);
// 발행 인자가 모두 true
$this->assertSame([true, true, true], $firedPublishedFlags);
}
/**
* 일괄 미발행 시 after_publish 훅이 published=false 로 발화되는지 확인 (이슈 #424-19 회귀)
*/
public function test_bulk_change_publish_status_fires_unpublish_hook_per_page(): void
{
$pages = Page::factory()->published()->count(3)->create([
'created_by' => $this->adminUser->id,
'updated_by' => $this->adminUser->id,
]);
$firedPublishedFlags = [];
HookManager::addAction(
'sirsoft-page.page.after_publish',
function (Page $page, bool $published) use (&$firedPublishedFlags) {
$firedPublishedFlags[] = $published;
}
);
$this->service->bulkChangePublishStatus($pages->pluck('id')->toArray(), false);
$this->assertSame([false, false, false], $firedPublishedFlags);
}
/**
* 일괄 처리 도중 한 건이라도 실패하면 전체 롤백되는지 확인 (이슈 #424-19 회귀)
*
* all-or-nothing: 존재하지 않는 id 가 섞이면 정상 페이지도 발행되지 않아야 한다.
*/
public function test_bulk_change_publish_status_rolls_back_on_failure(): void
{
$pages = Page::factory()->count(2)->create([
'published' => false,
'created_by' => $this->adminUser->id,
'updated_by' => $this->adminUser->id,
]);
$ids = $pages->pluck('id')->toArray();
$ids[] = 999999; // 존재하지 않는 페이지 id
try {
$this->service->bulkChangePublishStatus($ids, true);
$this->fail('존재하지 않는 페이지 id 포함 시 예외가 발생해야 한다.');
} catch (\Throwable $e) {
// 예외 발생 기대
}
// 정상 페이지 2건도 발행되지 않아야 함 (전체 롤백)
foreach ($pages as $page) {
$this->assertDatabaseHas('pages', [
'id' => $page->id,
'published' => false,
]);
}
}
// ─── restoreVersion ────────────────────────────────