221 lines
8.3 KiB
PHP
221 lines
8.3 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Arr;
|
|
use Illuminate\Validation\ValidationException;
|
|
use Illuminate\Support\Facades\Validator;
|
|
|
|
class MenuService
|
|
{
|
|
protected static function tenantId(array $params): ?int
|
|
{
|
|
return $params['tenant_id'] ?? request()->attributes->get('tenant_id');
|
|
}
|
|
|
|
protected static function actorId(array $params): ?int
|
|
{
|
|
return $params['user_id'] ?? (request()->user()->id ?? null);
|
|
}
|
|
|
|
public static function index(array $params)
|
|
{
|
|
$tenantId = self::tenantId($params);
|
|
|
|
$q = DB::table('menus')->whereNull('deleted_at');
|
|
if (!is_null($tenantId)) {
|
|
$q->where(function ($w) use ($tenantId) {
|
|
$w->whereNull('tenant_id')->orWhere('tenant_id', $tenantId);
|
|
});
|
|
}
|
|
// 옵션: parent_id / is_active / hidden 필터
|
|
if (isset($params['parent_id'])) $q->where('parent_id', $params['parent_id']);
|
|
if (isset($params['is_active'])) $q->where('is_active', (int)$params['is_active']);
|
|
if (isset($params['hidden'])) $q->where('hidden', (int)$params['hidden']);
|
|
|
|
return $q->orderBy('parent_id')->orderBy('sort_order')->get();
|
|
}
|
|
|
|
public static function show(array $params)
|
|
{
|
|
$id = (int)($params['id'] ?? 0);
|
|
$tenantId = self::tenantId($params);
|
|
|
|
$q = DB::table('menus')->where('id', $id)->whereNull('deleted_at');
|
|
if (!is_null($tenantId)) {
|
|
$q->where(function ($w) use ($tenantId) {
|
|
$w->whereNull('tenant_id')->orWhere('tenant_id', $tenantId);
|
|
});
|
|
}
|
|
|
|
$row = $q->first();
|
|
throw_if(!$row, ValidationException::withMessages(['id' => 'Menu not found']));
|
|
return $row;
|
|
}
|
|
|
|
public static function store(array $params)
|
|
{
|
|
$tenantId = self::tenantId($params);
|
|
$userId = self::actorId($params);
|
|
|
|
$v = Validator::make($params, [
|
|
'parent_id' => ['nullable','integer'],
|
|
'name' => ['required','string','max:100'],
|
|
'slug' => ['nullable','string','max:150'],
|
|
'url' => ['nullable','string','max:255'],
|
|
'is_active' => ['nullable','boolean'],
|
|
'sort_order' => ['nullable','integer'],
|
|
'hidden' => ['nullable','boolean'],
|
|
'is_external' => ['nullable','boolean'],
|
|
'external_url' => ['nullable','string','max:255'],
|
|
'icon' => ['nullable','string','max:50'],
|
|
]);
|
|
$data = $v->validated();
|
|
|
|
// slug 유니크(테넌트 범위) 체크
|
|
if (!empty($data['slug'])) {
|
|
$exists = DB::table('menus')
|
|
->whereNull('deleted_at')
|
|
->when(!is_null($tenantId), fn($q)=>$q->where('tenant_id',$tenantId), fn($q)=>$q->whereNull('tenant_id'))
|
|
->where('slug',$data['slug'])
|
|
->exists();
|
|
if ($exists) {
|
|
throw ValidationException::withMessages(['slug'=>'이미 사용 중인 슬러그입니다.']);
|
|
}
|
|
}
|
|
|
|
$now = now();
|
|
$id = DB::table('menus')->insertGetId([
|
|
'tenant_id' => $tenantId,
|
|
'parent_id' => $data['parent_id'] ?? null,
|
|
'name' => $data['name'],
|
|
'slug' => $data['slug'] ?? null,
|
|
'url' => $data['url'] ?? null,
|
|
'is_active' => (int)($data['is_active'] ?? 1),
|
|
'sort_order' => (int)($data['sort_order'] ?? 0),
|
|
'hidden' => (int)($data['hidden'] ?? 0),
|
|
'is_external' => (int)($data['is_external'] ?? 0),
|
|
'external_url' => $data['external_url'] ?? null,
|
|
'icon' => $data['icon'] ?? null,
|
|
'created_at' => $now,
|
|
'updated_at' => $now,
|
|
'created_by' => $userId,
|
|
'updated_by' => $userId,
|
|
]);
|
|
|
|
return ['id' => $id];
|
|
}
|
|
|
|
public static function update(array $params)
|
|
{
|
|
$id = (int)($params['id'] ?? 0);
|
|
$tenantId = self::tenantId($params);
|
|
$userId = self::actorId($params);
|
|
|
|
$v = Validator::make($params, [
|
|
'parent_id' => ['nullable','integer'],
|
|
'name' => ['nullable','string','max:100'],
|
|
'slug' => ['nullable','string','max:150'],
|
|
'url' => ['nullable','string','max:255'],
|
|
'is_active' => ['nullable','boolean'],
|
|
'sort_order' => ['nullable','integer'],
|
|
'hidden' => ['nullable','boolean'],
|
|
'is_external' => ['nullable','boolean'],
|
|
'external_url' => ['nullable','string','max:255'],
|
|
'icon' => ['nullable','string','max:50'],
|
|
]);
|
|
$data = $v->validated();
|
|
|
|
// 대상 존재 확인 & 테넌트 범위
|
|
$exists = DB::table('menus')->where('id',$id)->whereNull('deleted_at')
|
|
->when(!is_null($tenantId), fn($q)=>$q->where('tenant_id',$tenantId), fn($q)=>$q->whereNull('tenant_id'))
|
|
->exists();
|
|
if (!$exists) throw ValidationException::withMessages(['id'=>'Menu not found']);
|
|
|
|
// slug 유니크(테넌트 범위) 체크
|
|
if (!empty($data['slug'])) {
|
|
$dup = DB::table('menus')->whereNull('deleted_at')
|
|
->when(!is_null($tenantId), fn($q)=>$q->where('tenant_id',$tenantId), fn($q)=>$q->whereNull('tenant_id'))
|
|
->where('slug',$data['slug'])->where('id','<>',$id)->exists();
|
|
if ($dup) throw ValidationException::withMessages(['slug'=>'이미 사용 중인 슬러그입니다.']);
|
|
}
|
|
|
|
$update = Arr::only($data, ['parent_id','name','slug','url','is_active','sort_order','hidden','is_external','external_url','icon']);
|
|
$update = array_filter($update, fn($v)=>!is_null($v));
|
|
$update['updated_at'] = now();
|
|
$update['updated_by'] = $userId;
|
|
|
|
DB::table('menus')->where('id',$id)->update($update);
|
|
return ['id' => $id];
|
|
}
|
|
|
|
public static function destroy(array $params)
|
|
{
|
|
$id = (int)($params['id'] ?? 0);
|
|
$tenantId = self::tenantId($params);
|
|
$userId = self::actorId($params);
|
|
|
|
$q = DB::table('menus')->where('id',$id)->whereNull('deleted_at');
|
|
$q = !is_null($tenantId)
|
|
? $q->where('tenant_id',$tenantId)
|
|
: $q->whereNull('tenant_id');
|
|
|
|
$row = $q->first();
|
|
if (!$row) throw ValidationException::withMessages(['id'=>'Menu not found']);
|
|
|
|
DB::table('menus')->where('id',$id)->update([
|
|
'deleted_at' => now(),
|
|
'deleted_by' => $userId,
|
|
]);
|
|
return ['id' => $id, 'deleted' => true];
|
|
}
|
|
|
|
/**
|
|
* 정렬 일괄 변경
|
|
* $params = [ ['id'=>10, 'sort_order'=>1], ... ]
|
|
*/
|
|
public static function reorder(array $params)
|
|
{
|
|
if (!is_array($params) || empty($params)) {
|
|
throw ValidationException::withMessages(['items'=>'유효한 정렬 목록이 필요합니다.']);
|
|
}
|
|
DB::transaction(function () use ($params) {
|
|
foreach ($params as $it) {
|
|
if (!isset($it['id'], $it['sort_order'])) continue;
|
|
DB::table('menus')->where('id',(int)$it['id'])->update([
|
|
'sort_order' => (int)$it['sort_order'],
|
|
'updated_at' => now(),
|
|
]);
|
|
}
|
|
});
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* 상태 토글
|
|
* 허용 필드: is_active / hidden / is_external
|
|
*/
|
|
public static function toggle(array $params)
|
|
{
|
|
$id = (int)($params['id'] ?? 0);
|
|
$userId = self::actorId($params);
|
|
|
|
$payload = array_filter([
|
|
'is_active' => isset($params['is_active']) ? (int)$params['is_active'] : null,
|
|
'hidden' => isset($params['hidden']) ? (int)$params['hidden'] : null,
|
|
'is_external' => isset($params['is_external']) ? (int)$params['is_external'] : null,
|
|
], fn($v)=>!is_null($v));
|
|
|
|
if (empty($payload)) {
|
|
throw ValidationException::withMessages(['toggle'=>'변경할 필드가 없습니다.']);
|
|
}
|
|
|
|
$payload['updated_at'] = now();
|
|
$payload['updated_by'] = $userId;
|
|
|
|
DB::table('menus')->where('id',$id)->update($payload);
|
|
return ['id' => $id];
|
|
}
|
|
}
|