2025-12-04 18:47:19 +09:00
|
|
|
# SAM API 개발 규칙
|
|
|
|
|
|
2026-01-28 18:52:24 +09:00
|
|
|
**업데이트**: 2026-01-28
|
2025-12-04 18:47:19 +09:00
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 1. Architecture Philosophy
|
|
|
|
|
|
|
|
|
|
- **Service First**: All business logic must be written in Service classes (public functions)
|
|
|
|
|
- **Controller**: Only responsible for DI injection of Services and calling them. Use ApiResponse::handle() for responses
|
|
|
|
|
- **Exception Flow**: Errors are thrown → converted to JSON by global handler
|
|
|
|
|
- **Context Enforcement**: Base Service requires tenantId(), apiUserId(). Throws exception if not set
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 2. Multi-tenancy & Models
|
|
|
|
|
|
|
|
|
|
- BelongsToTenant global scope applied
|
|
|
|
|
- ModelTrait for is_active, date handling
|
|
|
|
|
- SoftDeletes by default
|
|
|
|
|
- Common columns: tenant_id, created_by, updated_by, deleted_by (COMMENT required)
|
|
|
|
|
- FK constraints: Created during design, minimal in production
|
|
|
|
|
|
2025-12-05 17:57:47 +09:00
|
|
|
### 2.1 ModelTrait 사용 가이드
|
|
|
|
|
|
|
|
|
|
`ModelTrait`는 모든 모델에서 공통으로 사용하는 기능을 제공합니다.
|
|
|
|
|
|
|
|
|
|
**위치**: `app/Traits/ModelTrait.php`
|
|
|
|
|
|
|
|
|
|
**제공 기능**:
|
|
|
|
|
```php
|
|
|
|
|
// 1. 날짜 직렬화 포맷 (Y-m-d H:i:s)
|
|
|
|
|
protected function serializeDate(DateTimeInterface $date)
|
|
|
|
|
|
|
|
|
|
// 2. Active 상태 조회 Scope
|
|
|
|
|
public function scopeActive($query)
|
|
|
|
|
// 사용: Model::active()->get()
|
|
|
|
|
// SQL: WHERE is_active = 1
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**⚠️ 필수 요구사항**:
|
|
|
|
|
|
|
|
|
|
`scopeActive()` 메서드 사용 시 테이블에 `is_active` 컬럼이 **반드시 존재해야 함**
|
|
|
|
|
|
|
|
|
|
```sql
|
|
|
|
|
-- 마이그레이션 예시
|
|
|
|
|
$table->boolean('is_active')
|
|
|
|
|
->default(true)
|
|
|
|
|
->comment('활성화 여부 (ModelTrait::scopeActive() 사용)');
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**모델 설정**:
|
|
|
|
|
```php
|
|
|
|
|
class YourModel extends Model
|
|
|
|
|
{
|
|
|
|
|
use BelongsToTenant, ModelTrait, SoftDeletes;
|
|
|
|
|
|
|
|
|
|
protected $fillable = [
|
|
|
|
|
// ...
|
|
|
|
|
'is_active', // 반드시 추가
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
protected $casts = [
|
|
|
|
|
'is_active' => 'boolean', // 반드시 추가
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**is_active 컬럼 적용 테이블** (2025-12-05 기준):
|
|
|
|
|
| 테이블 | is_active | 비고 |
|
|
|
|
|
|--------|-----------|------|
|
|
|
|
|
| materials | ✅ 있음 | |
|
|
|
|
|
| products | ✅ 있음 | |
|
|
|
|
|
| item_pages | ✅ 있음 | |
|
|
|
|
|
| item_fields | ✅ 있음 | 2025-12-05 추가 |
|
|
|
|
|
| item_sections | ❌ 없음 | 필요시 마이그레이션 추가 |
|
|
|
|
|
|
2025-12-04 18:47:19 +09:00
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 3. Middleware Stack
|
|
|
|
|
|
|
|
|
|
- ApiKeyMiddleware, CheckSwaggerAuth, CorsMiddleware, CheckPermission, PermMapper
|
2026-01-28 18:52:24 +09:00
|
|
|
- **ApiVersionMiddleware** - API 버전 선택 및 폴백 처리 (v2 없으면 v1 사용)
|
2025-12-04 18:47:19 +09:00
|
|
|
- Default route group: auth.apikey (some with auth:sanctum)
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
2026-01-28 18:52:24 +09:00
|
|
|
## 4. Routing
|
2025-12-04 18:47:19 +09:00
|
|
|
|
2026-01-28 18:52:24 +09:00
|
|
|
### 4.1 라우트 파일 구조
|
|
|
|
|
|
|
|
|
|
API 라우트는 도메인별로 분리되어 있습니다:
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
routes/api/
|
|
|
|
|
├── v1/ # v1 API 라우트
|
|
|
|
|
│ ├── auth.php # 인증 (login, logout, signup, token)
|
|
|
|
|
│ ├── admin.php # 관리자 (users, global-menus, FCM)
|
|
|
|
|
│ ├── users.php # 사용자 (me, profiles, invitations, roles)
|
|
|
|
|
│ ├── tenants.php # 테넌트 (CRUD, settings, stat-fields)
|
|
|
|
|
│ ├── hr.php # HR (departments, positions, employees, attendances)
|
|
|
|
|
│ ├── finance.php # 재무 (cards, deposits, withdrawals, payrolls)
|
|
|
|
|
│ ├── sales.php # 영업 (clients, quotes, orders, pricing)
|
|
|
|
|
│ ├── inventory.php # 재고 (items, BOM, stocks, shipments)
|
|
|
|
|
│ ├── production.php # 생산 (processes, work-orders, inspections)
|
|
|
|
|
│ ├── design.php # 설계 (models, versions, BOM templates)
|
|
|
|
|
│ ├── files.php # 파일 (upload, download, folders)
|
|
|
|
|
│ ├── boards.php # 게시판 (boards, posts, comments)
|
|
|
|
|
│ └── common.php # 공통 (menus, roles, permissions, settings)
|
|
|
|
|
├── v2/ # v2 API 라우트 (필요시 생성)
|
|
|
|
|
└── api.php # 라우트 로더
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 4.2 API 버전 폴백 시스템
|
|
|
|
|
|
|
|
|
|
**버전 선택 방법 (우선순위 순):**
|
|
|
|
|
1. `Accept-Version` 헤더: `Accept-Version: v2`
|
|
|
|
|
2. `X-API-Version` 헤더: `X-API-Version: v2`
|
|
|
|
|
3. `api_version` 쿼리 파라미터: `?api_version=v2`
|
|
|
|
|
4. 기본값: `v1`
|
|
|
|
|
|
|
|
|
|
**폴백 동작:**
|
|
|
|
|
- v2 요청 시 해당 라우트가 v2에 없으면 자동으로 v1 라우트 사용
|
|
|
|
|
- 응답 헤더 `X-API-Version`에 실제 사용된 버전 표시
|
|
|
|
|
|
|
|
|
|
**사용 예시:**
|
|
|
|
|
```bash
|
|
|
|
|
# v1 명시적 요청
|
|
|
|
|
curl -H "Accept-Version: v1" https://api.sam.kr/api/v1/users
|
|
|
|
|
|
|
|
|
|
# v2 요청 (v2 없으면 v1으로 폴백)
|
|
|
|
|
curl -H "Accept-Version: v2" https://api.sam.kr/api/v1/users
|
|
|
|
|
|
|
|
|
|
# 쿼리 파라미터로 버전 지정
|
|
|
|
|
curl "https://api.sam.kr/api/v1/users?api_version=v2"
|
|
|
|
|
|
|
|
|
|
# 버전 미지정 (기본 v1)
|
|
|
|
|
curl https://api.sam.kr/api/v1/users
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 4.3 REST 컨벤션
|
|
|
|
|
|
|
|
|
|
- 기본 CRUD: `index`, `show`, `store`, `update`, `destroy`
|
|
|
|
|
- 확장 메서드: `toggle`, `bulkUpsert`, `reorder`, `stats`, `options`
|
2025-12-04 18:47:19 +09:00
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 5. Controller/Service Rules
|
|
|
|
|
|
|
|
|
|
- **Controller**: FormRequest type-hint → only pass $request->validated() to Service
|
|
|
|
|
- **Service**: extends Service, tenantId()/apiUserId() required
|
|
|
|
|
- **Lists**: Pagination, explicit search parameters
|
|
|
|
|
- **Responses**: {success, message, data}, message must be i18n key only
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 6. i18n & Response Messages
|
|
|
|
|
|
|
|
|
|
- Messages: lang/{locale}/message.php
|
|
|
|
|
- Errors: lang/{locale}/error.php
|
|
|
|
|
- **Rule**: No direct strings, must use __('message.xxx'), __('error.xxx')
|
|
|
|
|
- Common keys: message.fetched/created/updated/deleted/bulk_upsert/reordered
|
|
|
|
|
- Resource-specific keys allowed: message.product.created, message.bom.bulk_upsert
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 7. Validation (FormRequest)
|
|
|
|
|
|
|
|
|
|
- **No direct validate() calls**. Separate all into FormRequest classes
|
|
|
|
|
- **Reuse common Requests**: PaginateRequest, BomItemsRequest, DateRangeRequest
|
|
|
|
|
- **Boundary validation** (effective_from ≤ effective_to) in Request
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 8. Audit Logging
|
|
|
|
|
|
|
|
|
|
- **Table**: audit_logs (tenant_id, target_type, target_id, action, before/after(json), actor_id, ip, ua, created_at)
|
|
|
|
|
- **Actions**: created, updated, deleted, released, cloned, items_replaced, diff_viewed
|
|
|
|
|
- **Retention**: 13 months default, audit:prune scheduler cleanup
|
|
|
|
|
- **Failure tolerance**: Log failures don't block business operations
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 9. Domain Rules
|
|
|
|
|
|
|
|
|
|
- **Status**: Strings (DRAFT/RELEASED/ARCHIVED)
|
|
|
|
|
- **Unique/Indexes**: models(code), model_versions(version_no), bom_items(parent, order)
|
|
|
|
|
- **BOM**: Supports summary/validate/bulkUpsert/reorder
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 개발 시 필수 체크
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
✓ Service-First 패턴 (Controller는 단순)
|
|
|
|
|
✓ BelongsToTenant scope 적용
|
|
|
|
|
✓ ModelTrait 사용
|
|
|
|
|
✓ SoftDeletes 적용
|
|
|
|
|
✓ FormRequest 검증
|
|
|
|
|
✓ i18n 키 사용 (__('message.xxx'))
|
|
|
|
|
✓ 감사 로그 고려
|
|
|
|
|
✓ tenant_id 필터링 확인
|
|
|
|
|
```
|