본문으로 건너뛰기
가이드

보호된 API 추가하기

Note의 작업 권한과 레코드 범위를 유지하며 API를 확장합니다.

보호된 API 추가하기

Note 수정 요청을 보호합니다. Resource 만들기로 생성했다면 권한·Policy·Route·Controller가 이미 있습니다. App의 src/Policies/NotePolicy.php를 열면 수정 권한과 레코드 범위를 함께 확인합니다.

코드 예시
PHP
public function update(Actor $user, Note $note): bool
{
    return $this->allowsPermission($user, 'workshop.note.update')
        && $this->matchesAuthorizationContract($note);
}

필요한 import와 private helper는 생성된 파일에 있습니다. src/Http/Controllers/NoteController.php에서는 공개 UUID로 조회 → 저장된 소유자 복원 → update 인가 → 입력 검증 → 저장 순서를 유지합니다. 새 입력 항목은 데이터 모델과 마이그레이션를 따라 연결하세요.

보관 동작 추가

초안·활성 노트를 보관하고 이미 보관된 노트의 반복 요청에는 409를 반환하는 예제입니다. 아래 코드는 App의 생성된 클래스에 멤버로 추가하고 Route는 기존 보호 그룹 안에 넣습니다. 파일 전체를 교체하지 않습니다.

코드 예시
PHP
// NoteModule::permissionResources(): keep the existing actions and add archive.
return [
    'workshop.note' => ['read', 'create', 'update', 'delete', 'archive'],
];
코드 예시
PHP
// Add to NotePolicy; reuse its existing Actor, Note and authorization helpers.
public function archive(Actor $user, Note $note): bool
{
    return $this->allowsPermission($user, 'workshop.note.archive')
        && $this->matchesAuthorizationContract($note);
}
코드 예시
PHP
// routes/routes.php: inside the existing api/workshop prefix and guarded App group.
Route::post('/notes/{note}/archive', [NoteController::class, 'archive'])
    ->whereUuid('note');
코드 예시
PHP
// NoteController: add the DB import; the other imports already exist.
use Illuminate\Support\Facades\DB;

public function archive(Request $request, string $note): JsonResponse
{
    $record = $this->resolveNote($request, $note, 'workshop.note.archive');

    return DB::transaction(function () use ($request, $record): JsonResponse {
        $locked = Note::query()->whereKey($record->getKey())
            ->lockForUpdate()->firstOrFail();
        $this->authorize('archive', $locked);
        abort_if($locked->status === NoteStatus::Archived, 409);
        $locked->update(['status' => NoteStatus::Archived]);

        return response()->json([
            'note' => $this->serialize(
                $request,
                $locked,
                app(ResourceActionDecisions::class),
            ),
        ]);
    });
}

NoteController::store()와 update()의 기존 status 검증 규칙은 'status' => ['missing']로 교체합니다. 새 노트는 DB 기본값 draft를 사용하고 일반 수정으로 보관 권한을 우회할 수 없게 됩니다. missing은 null이나 빈 값으로 전달한 status도 거부합니다. 다른 필드 규칙은 유지하세요. 각 언어에 permissions.keys.workshop.note.archive.label과 .description을 추가합니다. 영어는 Archive Notes / Allows archiving Notes., 한국어는 노트 보관 / 노트를 보관할 수 있습니다., 중국어는 归档笔记 / 允许归档笔记。입니다.

이 동작은 요청의 상태·소유자를 받지 않습니다. 기존 resolver가 소유 범위를 복원하고 행 잠금이 동시 보관의 중복 성공을 막습니다. 버튼은 App 화면 만들기의 흐름에 인가된 action decision으로 연결합니다. 상태만으로 권한을 추정하지 않습니다.

선언을 바꾼 뒤 Core 루트에서 실행합니다.

코드 예시
Shell
task artisan -- nexia-access:sync-manifest
task artisan -- nexia-runtime:refresh-runtime-caches

권한 정의와 캐시된 연결이 갱신됩니다. 사용자에게 권한이 배정되지는 않습니다.

목록 범위 보호

단건 Policy만으로 목록은 보호되지 않습니다. 생성 모델은 ResourceAuthorization::scopeVisible()을 사용하고 Legal Entity 목록 Controller는 페이지의 허용 대상을 먼저 해석합니다. 목록·내보내기·일괄 작업에서도 이 조건을 유지하세요. 페이지 처리 전 SQL에서 제한해야 금지된 행과 집계가 새지 않습니다.

생성은 creationAttributes()와 인가된 단일 대상을 사용합니다. 요청의 숫자 소유자 ID를 신뢰하거나 저장된 소유자를 덮어쓰지 않습니다. 일반 목록의 선택기는 useOrganizationListScope와 NxOrganizationTargetSelector로 NxPageFrame.organizationScope에 배치합니다. 테넌트 공통 기준정보에는 선택기가 없습니다.

HTTP 경계 확인

권한이 있는 노트에 POST /api/workshop/notes/{public_id}/archive를 호출하면 200과 status: "archived"를 받습니다. 같은 요청을 반복하면 409, 일반 생성·수정에 status를 넣어 전환을 우회하면 422여야 합니다.

테넌트에 App이 설치된 fixture로 실제 endpoint를 호출합니다. 허용 사용자, 권한 없는 사용자, 다른 조직, 다른 테넌트를 확인하세요. 범위 밖 레코드는 의도적으로 404가 될 수 있습니다. 생성 Controller가 일부 소유자 해석 실패를 숨기므로 모든 거부를 403으로 단정하지 않습니다.

App 테스트하기에서 선언 검사와 동작 검사를 구분합니다. 메뉴·버튼·응답의 actions는 화면을 위한 정보이며 서버 검사를 대신하지 않습니다.

원본 위치: docs/developers/content/ko/building-apps/add-api-and-authorization.md