538 lines
16 KiB
Markdown
538 lines
16 KiB
Markdown
|
|
# MNG HTMX + API 패턴 가이드
|
||
|
|
|
||
|
|
**작성일:** 2025-01-24
|
||
|
|
**목적:** MNG 프로젝트의 표준 HTMX + API 패턴 문서화 (Tenant 패턴 기반)
|
||
|
|
|
||
|
|
**관련 문서:**
|
||
|
|
- [LAYOUT_PATTERN.md](./LAYOUT_PATTERN.md) - 페이지 레이아웃 및 Tenant Selector 패턴
|
||
|
|
- [99_TECHNICAL_STANDARDS.md](./99_TECHNICAL_STANDARDS.md) - SAM API Rules 기반 기술 표준
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 📋 목차
|
||
|
|
|
||
|
|
1. [패턴 개요](#1-패턴-개요)
|
||
|
|
2. [아키텍처 구조](#2-아키텍처-구조)
|
||
|
|
3. [구현 가이드](#3-구현-가이드)
|
||
|
|
4. [파일 구조](#4-파일-구조)
|
||
|
|
5. [체크리스트](#5-체크리스트)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 1. 패턴 개요
|
||
|
|
|
||
|
|
### 1.1 왜 HTMX + API 패턴인가?
|
||
|
|
|
||
|
|
**MNG 프로젝트의 표준 아키텍처 패턴입니다.**
|
||
|
|
|
||
|
|
- **일관성**: 모든 CRUD 기능이 동일한 패턴 사용
|
||
|
|
- **성능**: 페이지 전체 리로드 없이 동적 업데이트
|
||
|
|
- **유지보수성**: Blade 템플릿 + HTMX로 간단한 인터랙션
|
||
|
|
- **확장성**: API는 HTMX와 독립적으로 사용 가능
|
||
|
|
|
||
|
|
### 1.2 기본 원칙
|
||
|
|
|
||
|
|
1. **Blade View는 화면만 담당** - 데이터 처리 로직 없음
|
||
|
|
2. **API Controller는 HTMX와 JSON 모두 지원**
|
||
|
|
3. **HTMX 요청 시 HTML partial 반환**
|
||
|
|
4. **일반 요청 시 JSON 반환**
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 2. 아키텍처 구조
|
||
|
|
|
||
|
|
### 2.1 전체 흐름도
|
||
|
|
|
||
|
|
```
|
||
|
|
[Browser]
|
||
|
|
↓ (HTMX Request with HX-Request header)
|
||
|
|
[Route: web.php]
|
||
|
|
↓ (Blade View 반환)
|
||
|
|
[Controller: RoleController]
|
||
|
|
↓ (view('roles.index') - 화면만)
|
||
|
|
[Blade View: roles/index.blade.php]
|
||
|
|
↓ (hx-get="/api/admin/roles")
|
||
|
|
[API Route: api.php]
|
||
|
|
↓ (API 엔드포인트)
|
||
|
|
[Api\Admin\RoleController]
|
||
|
|
↓ (Service 호출)
|
||
|
|
[RoleService]
|
||
|
|
↓ (비즈니스 로직)
|
||
|
|
[Database]
|
||
|
|
↓
|
||
|
|
[RoleService]
|
||
|
|
↓ (데이터 반환)
|
||
|
|
[Api\Admin\RoleController]
|
||
|
|
↓ (HTMX 요청 감지: HX-Request header)
|
||
|
|
↓ (HTML partial 렌더링)
|
||
|
|
[Blade Partial: roles/partials/table.blade.php]
|
||
|
|
↓ (JSON with html)
|
||
|
|
[Browser - HTMX]
|
||
|
|
↓ (DOM 업데이트: #role-table)
|
||
|
|
[User sees updated table]
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2.2 컨트롤러 분리
|
||
|
|
|
||
|
|
#### Blade Controller (화면 전용)
|
||
|
|
```php
|
||
|
|
// app/Http/Controllers/RoleController.php
|
||
|
|
class RoleController extends Controller
|
||
|
|
{
|
||
|
|
public function index(): View
|
||
|
|
{
|
||
|
|
return view('roles.index'); // 화면만 반환
|
||
|
|
}
|
||
|
|
|
||
|
|
public function create(): View
|
||
|
|
{
|
||
|
|
return view('roles.create');
|
||
|
|
}
|
||
|
|
|
||
|
|
public function edit(int $id): View
|
||
|
|
{
|
||
|
|
$role = $this->roleService->getRoleById($id);
|
||
|
|
return view('roles.edit', compact('role'));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
#### API Controller (데이터 처리)
|
||
|
|
```php
|
||
|
|
// app/Http/Controllers/Api/Admin/RoleController.php
|
||
|
|
class RoleController extends Controller
|
||
|
|
{
|
||
|
|
public function index(Request $request): JsonResponse
|
||
|
|
{
|
||
|
|
$roles = $this->roleService->getRoles($request->all());
|
||
|
|
|
||
|
|
// HTMX 요청 감지
|
||
|
|
if ($request->header('HX-Request')) {
|
||
|
|
$html = view('roles.partials.table', compact('roles'))->render();
|
||
|
|
return response()->json(['html' => $html]);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 일반 API 요청
|
||
|
|
return response()->json([
|
||
|
|
'success' => true,
|
||
|
|
'data' => $roles->items(),
|
||
|
|
'meta' => [/*...*/],
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function store(StoreRoleRequest $request): JsonResponse
|
||
|
|
{
|
||
|
|
$role = $this->roleService->createRole($request->validated());
|
||
|
|
|
||
|
|
if ($request->header('HX-Request')) {
|
||
|
|
return response()->json([
|
||
|
|
'success' => true,
|
||
|
|
'message' => '역할이 생성되었습니다.',
|
||
|
|
'redirect' => route('roles.index'),
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
|
||
|
|
return response()->json([
|
||
|
|
'success' => true,
|
||
|
|
'data' => $role,
|
||
|
|
], 201);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function destroy(Request $request, int $id): JsonResponse
|
||
|
|
{
|
||
|
|
$this->roleService->deleteRole($id);
|
||
|
|
|
||
|
|
if ($request->header('HX-Request')) {
|
||
|
|
return response()->json([
|
||
|
|
'success' => true,
|
||
|
|
'message' => '역할이 삭제되었습니다.',
|
||
|
|
'action' => 'remove',
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
|
||
|
|
return response()->json([
|
||
|
|
'success' => true,
|
||
|
|
'message' => '역할이 삭제되었습니다.',
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 3. 구현 가이드
|
||
|
|
|
||
|
|
### 3.1 Blade View 구조
|
||
|
|
|
||
|
|
#### index.blade.php (메인 화면)
|
||
|
|
```blade
|
||
|
|
@extends('layouts.app')
|
||
|
|
|
||
|
|
@section('content')
|
||
|
|
<div class="container mx-auto">
|
||
|
|
<h1>🔑 역할 관리</h1>
|
||
|
|
|
||
|
|
<!-- 필터 폼 -->
|
||
|
|
<form id="filterForm">
|
||
|
|
<input type="text" name="search" placeholder="검색...">
|
||
|
|
<button type="submit">검색</button>
|
||
|
|
</form>
|
||
|
|
|
||
|
|
<!-- HTMX 동적 로딩 영역 -->
|
||
|
|
<div id="role-table"
|
||
|
|
hx-get="/api/admin/roles"
|
||
|
|
hx-trigger="load, filterSubmit from:body"
|
||
|
|
hx-include="#filterForm"
|
||
|
|
hx-headers='{"X-CSRF-TOKEN": "{{ csrf_token() }}"}'
|
||
|
|
class="bg-white rounded-lg shadow-sm">
|
||
|
|
<!-- 로딩 스피너 -->
|
||
|
|
<div class="flex justify-center items-center p-12">
|
||
|
|
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
@endsection
|
||
|
|
|
||
|
|
@push('scripts')
|
||
|
|
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
|
||
|
|
<script>
|
||
|
|
// 폼 제출 시 HTMX 트리거
|
||
|
|
document.getElementById('filterForm').addEventListener('submit', function(e) {
|
||
|
|
e.preventDefault();
|
||
|
|
htmx.trigger('#role-table', 'filterSubmit');
|
||
|
|
});
|
||
|
|
|
||
|
|
// HTMX 응답 처리
|
||
|
|
document.body.addEventListener('htmx:afterSwap', function(event) {
|
||
|
|
if (event.detail.target.id === 'role-table') {
|
||
|
|
const response = JSON.parse(event.detail.xhr.response);
|
||
|
|
if (response.html) {
|
||
|
|
event.detail.target.innerHTML = response.html;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// 삭제 확인
|
||
|
|
window.confirmDelete = function(id, name) {
|
||
|
|
if (confirm(`"${name}" 역할을 삭제하시겠습니까?`)) {
|
||
|
|
htmx.ajax('DELETE', `/api/admin/roles/${id}`, {
|
||
|
|
target: '#role-table',
|
||
|
|
swap: 'none',
|
||
|
|
headers: { 'X-CSRF-TOKEN': '{{ csrf_token() }}' }
|
||
|
|
}).then(() => {
|
||
|
|
htmx.trigger('#role-table', 'filterSubmit');
|
||
|
|
});
|
||
|
|
}
|
||
|
|
};
|
||
|
|
</script>
|
||
|
|
@endpush
|
||
|
|
```
|
||
|
|
|
||
|
|
#### partials/table.blade.php (HTMX 응답용 HTML partial)
|
||
|
|
```blade
|
||
|
|
<table>
|
||
|
|
<thead>
|
||
|
|
<tr>
|
||
|
|
<th>ID</th>
|
||
|
|
<th>이름</th>
|
||
|
|
<th>설명</th>
|
||
|
|
<th>권한 수</th>
|
||
|
|
<th>액션</th>
|
||
|
|
</tr>
|
||
|
|
</thead>
|
||
|
|
<tbody>
|
||
|
|
@forelse($roles as $role)
|
||
|
|
<tr>
|
||
|
|
<td>{{ $role->id }}</td>
|
||
|
|
<td>{{ $role->name }}</td>
|
||
|
|
<td>{{ $role->description }}</td>
|
||
|
|
<td>{{ $role->permissions_count }}</td>
|
||
|
|
<td>
|
||
|
|
<a href="{{ route('roles.edit', $role->id) }}">수정</a>
|
||
|
|
<button onclick="confirmDelete({{ $role->id }}, '{{ $role->name }}')">삭제</button>
|
||
|
|
</td>
|
||
|
|
</tr>
|
||
|
|
@empty
|
||
|
|
<tr>
|
||
|
|
<td colspan="5">등록된 역할이 없습니다.</td>
|
||
|
|
</tr>
|
||
|
|
@endforelse
|
||
|
|
</tbody>
|
||
|
|
</table>
|
||
|
|
|
||
|
|
<!-- 페이지네이션 -->
|
||
|
|
@include('partials.pagination', [
|
||
|
|
'paginator' => $roles,
|
||
|
|
'target' => '#role-table',
|
||
|
|
'includeForm' => '#filterForm'
|
||
|
|
])
|
||
|
|
```
|
||
|
|
|
||
|
|
### 3.2 라우트 설정
|
||
|
|
|
||
|
|
#### web.php (Blade 화면 라우트)
|
||
|
|
```php
|
||
|
|
Route::middleware('auth')->group(function () {
|
||
|
|
Route::prefix('roles')->name('roles.')->group(function () {
|
||
|
|
Route::get('/', [RoleController::class, 'index'])->name('index');
|
||
|
|
Route::get('/create', [RoleController::class, 'create'])->name('create');
|
||
|
|
Route::get('/{id}/edit', [RoleController::class, 'edit'])->name('edit');
|
||
|
|
});
|
||
|
|
});
|
||
|
|
```
|
||
|
|
|
||
|
|
#### api.php (API 엔드포인트)
|
||
|
|
```php
|
||
|
|
Route::middleware(['web', 'auth'])->prefix('admin')->name('api.admin.')->group(function () {
|
||
|
|
Route::prefix('roles')->name('roles.')->group(function () {
|
||
|
|
Route::get('/', [RoleController::class, 'index'])->name('index');
|
||
|
|
Route::post('/', [RoleController::class, 'store'])->name('store');
|
||
|
|
Route::get('/{id}', [RoleController::class, 'show'])->name('show');
|
||
|
|
Route::put('/{id}', [RoleController::class, 'update'])->name('update');
|
||
|
|
Route::delete('/{id}', [RoleController::class, 'destroy'])->name('destroy');
|
||
|
|
});
|
||
|
|
});
|
||
|
|
```
|
||
|
|
|
||
|
|
### 3.3 HTMX 핵심 개념
|
||
|
|
|
||
|
|
#### hx-get, hx-post, hx-put, hx-delete
|
||
|
|
```html
|
||
|
|
<!-- GET 요청 -->
|
||
|
|
<div hx-get="/api/admin/roles" hx-trigger="load">로딩 중...</div>
|
||
|
|
|
||
|
|
<!-- POST 요청 (폼 제출) -->
|
||
|
|
<form hx-post="/api/admin/roles" hx-target="#role-table">
|
||
|
|
<input name="name" required>
|
||
|
|
<button type="submit">생성</button>
|
||
|
|
</form>
|
||
|
|
|
||
|
|
<!-- DELETE 요청 (JavaScript) -->
|
||
|
|
<button onclick="htmx.ajax('DELETE', '/api/admin/roles/1', {target: '#role-table'})">삭제</button>
|
||
|
|
```
|
||
|
|
|
||
|
|
#### hx-trigger
|
||
|
|
```html
|
||
|
|
<!-- 페이지 로드 시 -->
|
||
|
|
<div hx-get="/api/admin/roles" hx-trigger="load"></div>
|
||
|
|
|
||
|
|
<!-- 커스텀 이벤트 -->
|
||
|
|
<div hx-get="/api/admin/roles" hx-trigger="filterSubmit from:body"></div>
|
||
|
|
|
||
|
|
<!-- 여러 트리거 조합 -->
|
||
|
|
<div hx-trigger="load, filterSubmit from:body"></div>
|
||
|
|
```
|
||
|
|
|
||
|
|
#### hx-include
|
||
|
|
```html
|
||
|
|
<!-- 폼 데이터 포함 -->
|
||
|
|
<div hx-get="/api/admin/roles" hx-include="#filterForm"></div>
|
||
|
|
```
|
||
|
|
|
||
|
|
#### hx-headers
|
||
|
|
```html
|
||
|
|
<!-- CSRF 토큰 포함 -->
|
||
|
|
<div hx-headers='{"X-CSRF-TOKEN": "{{ csrf_token() }}"}'></div>
|
||
|
|
```
|
||
|
|
|
||
|
|
#### hx-target, hx-swap
|
||
|
|
```html
|
||
|
|
<!-- 특정 엘리먼트 타겟 -->
|
||
|
|
<button hx-delete="/api/admin/roles/1" hx-target="#role-table">삭제</button>
|
||
|
|
|
||
|
|
<!-- swap 전략 -->
|
||
|
|
<div hx-swap="innerHTML">기본값</div>
|
||
|
|
<div hx-swap="outerHTML">엘리먼트 자체 교체</div>
|
||
|
|
<div hx-swap="none">응답 무시</div>
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 4. 파일 구조
|
||
|
|
|
||
|
|
### 4.1 표준 디렉토리 구조
|
||
|
|
|
||
|
|
```
|
||
|
|
mng/
|
||
|
|
├── app/
|
||
|
|
│ ├── Http/
|
||
|
|
│ │ ├── Controllers/
|
||
|
|
│ │ │ ├── RoleController.php # Blade 화면만
|
||
|
|
│ │ │ └── Api/
|
||
|
|
│ │ │ └── Admin/
|
||
|
|
│ │ │ └── RoleController.php # API 로직
|
||
|
|
│ │ └── Requests/
|
||
|
|
│ │ ├── StoreRoleRequest.php
|
||
|
|
│ │ └── UpdateRoleRequest.php
|
||
|
|
│ ├── Services/
|
||
|
|
│ │ └── RoleService.php # 비즈니스 로직
|
||
|
|
│ └── Models/
|
||
|
|
│ └── Role.php
|
||
|
|
├── resources/
|
||
|
|
│ └── views/
|
||
|
|
│ └── roles/
|
||
|
|
│ ├── index.blade.php # 메인 화면
|
||
|
|
│ ├── create.blade.php # 생성 화면
|
||
|
|
│ ├── edit.blade.php # 수정 화면
|
||
|
|
│ └── partials/
|
||
|
|
│ ├── table.blade.php # HTMX 응답 HTML
|
||
|
|
│ └── detail.blade.php # (선택사항)
|
||
|
|
└── routes/
|
||
|
|
├── web.php # Blade 화면 라우트
|
||
|
|
└── api.php # API 엔드포인트
|
||
|
|
```
|
||
|
|
|
||
|
|
### 4.2 Tenant 패턴 참고 파일
|
||
|
|
|
||
|
|
**학습 및 복사 기준:**
|
||
|
|
- `app/Http/Controllers/TenantController.php` → Blade 컨트롤러 패턴
|
||
|
|
- `app/Http/Controllers/Api/Admin/TenantController.php` → API 컨트롤러 패턴
|
||
|
|
- `resources/views/tenants/index.blade.php` → HTMX 메인 화면 패턴
|
||
|
|
- `resources/views/tenants/partials/table.blade.php` → HTML partial 패턴
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 5. 체크리스트
|
||
|
|
|
||
|
|
### 5.1 구현 전 확인사항
|
||
|
|
|
||
|
|
- [ ] **Tenant 패턴 파일 확인**: `tenants/` 디렉토리 구조 참고
|
||
|
|
- [ ] **Service 작성 완료**: `RoleService.php` 비즈니스 로직 구현
|
||
|
|
- [ ] **FormRequest 작성 완료**: `StoreRoleRequest`, `UpdateRoleRequest`
|
||
|
|
- [ ] **Model 확인**: `Role.php` 관계 설정 확인
|
||
|
|
|
||
|
|
### 5.2 컨트롤러 체크리스트
|
||
|
|
|
||
|
|
**Blade Controller (`app/Http/Controllers/RoleController.php`)**
|
||
|
|
- [ ] `index()` → `view('roles.index')` 반환만
|
||
|
|
- [ ] `create()` → `view('roles.create')` 반환만
|
||
|
|
- [ ] `edit($id)` → Service로 데이터 조회 → `view('roles.edit', compact('role'))`
|
||
|
|
|
||
|
|
**API Controller (`app/Http/Controllers/Api/Admin/RoleController.php`)**
|
||
|
|
- [ ] `index()` → HTMX 요청 감지 (`$request->header('HX-Request')`)
|
||
|
|
- [ ] HTMX 요청 시 → `view('roles.partials.table')->render()` → JSON 반환
|
||
|
|
- [ ] 일반 요청 시 → JSON 데이터 반환
|
||
|
|
- [ ] `store()`, `update()`, `destroy()` → HTMX 지원
|
||
|
|
- [ ] HTMX 응답 시 `redirect` 또는 `action` 포함
|
||
|
|
|
||
|
|
### 5.3 Blade View 체크리스트
|
||
|
|
|
||
|
|
**index.blade.php**
|
||
|
|
- [ ] `@extends('layouts.app')` 상속
|
||
|
|
- [ ] 필터 폼 `<form id="filterForm">` 생성
|
||
|
|
- [ ] HTMX 동적 영역 `<div id="role-table">` 생성
|
||
|
|
- [ ] `hx-get="/api/admin/roles"` 설정
|
||
|
|
- [ ] `hx-trigger="load, filterSubmit from:body"` 설정
|
||
|
|
- [ ] `hx-include="#filterForm"` 설정
|
||
|
|
- [ ] `hx-headers` CSRF 토큰 포함
|
||
|
|
- [ ] 로딩 스피너 추가
|
||
|
|
- [ ] `@push('scripts')` HTMX 스크립트 추가
|
||
|
|
- [ ] 폼 제출 이벤트 핸들러 (`filterSubmit` 트리거)
|
||
|
|
- [ ] HTMX 응답 처리 (`htmx:afterSwap`)
|
||
|
|
- [ ] 삭제 확인 함수 (`confirmDelete`)
|
||
|
|
|
||
|
|
**partials/table.blade.php**
|
||
|
|
- [ ] `<table>` 구조 생성
|
||
|
|
- [ ] `@forelse` 루프로 데이터 출력
|
||
|
|
- [ ] `@empty` 케이스 처리
|
||
|
|
- [ ] 액션 버튼 (수정, 삭제)
|
||
|
|
- [ ] 삭제 버튼 `onclick="confirmDelete()"` 연결
|
||
|
|
- [ ] 페이지네이션 `@include('partials.pagination')`
|
||
|
|
|
||
|
|
### 5.4 라우트 체크리스트
|
||
|
|
|
||
|
|
**web.php**
|
||
|
|
- [ ] `Route::middleware('auth')` 적용
|
||
|
|
- [ ] `Route::prefix('roles')->name('roles.')` 그룹
|
||
|
|
- [ ] `GET /roles` → `index()`
|
||
|
|
- [ ] `GET /roles/create` → `create()`
|
||
|
|
- [ ] `GET /roles/{id}/edit` → `edit()`
|
||
|
|
|
||
|
|
**api.php**
|
||
|
|
- [ ] `Route::middleware(['web', 'auth'])->prefix('admin')` 적용
|
||
|
|
- [ ] `Route::prefix('roles')->name('api.admin.roles.')` 그룹
|
||
|
|
- [ ] `GET /api/admin/roles` → `index()`
|
||
|
|
- [ ] `POST /api/admin/roles` → `store()`
|
||
|
|
- [ ] `GET /api/admin/roles/{id}` → `show()`
|
||
|
|
- [ ] `PUT /api/admin/roles/{id}` → `update()`
|
||
|
|
- [ ] `DELETE /api/admin/roles/{id}` → `destroy()`
|
||
|
|
|
||
|
|
### 5.5 테스트 체크리스트
|
||
|
|
|
||
|
|
- [ ] 브라우저에서 `/roles` 접근 → index 화면 로드
|
||
|
|
- [ ] HTMX 자동 로드 → 테이블 표시
|
||
|
|
- [ ] 검색 필터 동작 → 테이블 업데이트
|
||
|
|
- [ ] 삭제 버튼 → 확인 다이얼로그 → 테이블 업데이트
|
||
|
|
- [ ] 페이지네이션 동작
|
||
|
|
- [ ] 개발자 도구 Network 탭 → `HX-Request` 헤더 확인
|
||
|
|
- [ ] API 응답 JSON 구조 확인 (`{html: "..."}`)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 6. 참고사항
|
||
|
|
|
||
|
|
### 6.1 HTMX vs 전통적 방식 비교
|
||
|
|
|
||
|
|
| 항목 | 전통적 방식 | HTMX 방식 |
|
||
|
|
|------|------------|-----------|
|
||
|
|
| **폼 제출** | `<form method="GET">` → 전체 페이지 리로드 | `hx-get` → 부분 업데이트 |
|
||
|
|
| **데이터 로딩** | Controller에서 직접 데이터 전달 | API 호출 → HTML partial 반환 |
|
||
|
|
| **삭제 동작** | `<form method="POST">` + `@method('DELETE')` | `htmx.ajax('DELETE')` |
|
||
|
|
| **검색 필터** | 페이지 리로드 + 쿼리스트링 | HTMX 트리거 → 부분 업데이트 |
|
||
|
|
|
||
|
|
### 6.2 주의사항
|
||
|
|
|
||
|
|
1. **HTMX 요청 감지 필수**
|
||
|
|
```php
|
||
|
|
if ($request->header('HX-Request')) {
|
||
|
|
// HTMX 전용 로직
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
2. **CSRF 토큰 포함 필수**
|
||
|
|
```html
|
||
|
|
hx-headers='{"X-CSRF-TOKEN": "{{ csrf_token() }}"}'
|
||
|
|
```
|
||
|
|
|
||
|
|
3. **JSON 응답 구조 일관성**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"html": "<table>...</table>",
|
||
|
|
"success": true,
|
||
|
|
"message": "작업 완료"
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
4. **Blade와 API Controller 분리**
|
||
|
|
- Blade Controller: 화면만 반환
|
||
|
|
- API Controller: 데이터 처리 + HTMX/JSON 응답
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 7. 마이그레이션 가이드 (Admin → MNG)
|
||
|
|
|
||
|
|
### 7.1 작업 순서
|
||
|
|
|
||
|
|
1. **DB 확인** - 테이블이 이미 존재하는지 확인 (migrations 실행 불필요)
|
||
|
|
2. **Admin 파일 참고** - Controller, Service, Model 복사/참고
|
||
|
|
3. **패턴 적용** - HTMX + API 패턴으로 변환
|
||
|
|
4. **테스트** - 브라우저에서 동작 확인
|
||
|
|
|
||
|
|
### 7.2 마이그레이션 체크리스트
|
||
|
|
|
||
|
|
- [ ] DB 테이블 존재 확인 (`roles`, `permissions`, `role_has_permissions`)
|
||
|
|
- [ ] Admin Model 참고 (`admin/app/Models/Permissions/Role.php`)
|
||
|
|
- [ ] Admin Controller 참고 (비즈니스 로직 추출)
|
||
|
|
- [ ] Service 작성 (Admin 로직 → MNG Service)
|
||
|
|
- [ ] Blade Controller 작성 (화면 반환만)
|
||
|
|
- [ ] API Controller 작성 (HTMX 패턴)
|
||
|
|
- [ ] Blade View 작성 (Tenant 패턴 기반)
|
||
|
|
- [ ] 라우트 등록 (web.php, api.php)
|
||
|
|
- [ ] 브라우저 테스트
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
**작성자:** Claude
|
||
|
|
**최종 수정일:** 2025-01-24
|
||
|
|
**버전:** 1.0
|
||
|
|
**참고:** Tenant 관리 시스템 구현 패턴 기반
|