fix : 테넌트 관리자 사용자 API 추가
- 사용자 목록 - 사용자 생성 - 사용자 단건 조회 - 사용자 수정 - 사용자 삭제(소프트 삭제) - 활성/비활성 전환 - 삭제 복구 - 비밀번호 초기화 - 수정필요 : 역할부여, 역할 해재
This commit is contained in:
469
app/Services/AdminService.php
Normal file
469
app/Services/AdminService.php
Normal file
@@ -0,0 +1,469 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Helpers\ApiResponse;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
use App\Models\Members\User;
|
||||
use App\Models\Members\UserTenant;
|
||||
use Spatie\Permission\Models\Role;
|
||||
|
||||
|
||||
class AdminService
|
||||
{
|
||||
/**
|
||||
* [GET] 테넌트 사용자 목록
|
||||
* - 컨트롤러 index()에서 호출
|
||||
* - 검색/정렬/페이징 최소 항목 포함
|
||||
*/
|
||||
public static function getTenants(array $params = [])
|
||||
{
|
||||
$tenantId = app('tenant_id');
|
||||
if (!$tenantId) {
|
||||
return ApiResponse::error('활성 테넌트가 없습니다.', 400);
|
||||
}
|
||||
|
||||
$page = isset($params['page']) ? (int)$params['page'] : 1;
|
||||
$size = isset($params['size']) ? (int)$params['size'] : 10;
|
||||
$keyword = $params['q'] ?? null;
|
||||
$active = $params['is_active'] ?? null; // 0/1
|
||||
$sortBy = $params['sort_by'] ?? 'users.id';
|
||||
$sortDir = strtolower($params['sort_dir'] ?? 'desc') === 'asc' ? 'asc' : 'desc';
|
||||
|
||||
$q = UserTenant::query()
|
||||
->with(['user:id,name,email,phone'])
|
||||
->where('tenant_id', $tenantId);
|
||||
|
||||
if ($keyword) {
|
||||
$q->whereHas('user', function($sub) use ($keyword) {
|
||||
$sub->where(function($w) use ($keyword) {
|
||||
$w->where('name', 'like', "%{$keyword}%")
|
||||
->orWhere('email', 'like', "%{$keyword}%")
|
||||
->orWhere('phone', 'like', "%{$keyword}%");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if ($active !== null && $active !== '') {
|
||||
$q->where('is_active', (int)$active);
|
||||
}
|
||||
|
||||
// 조인 정렬용
|
||||
$q->leftJoin('users', 'users.id', '=', 'user_tenants.user_id')
|
||||
->select(
|
||||
'users.id',
|
||||
'users.user_id',
|
||||
'users.name',
|
||||
'users.email',
|
||||
'users.phone',
|
||||
'user_tenants.is_active',
|
||||
'user_tenants.joined_at',
|
||||
'user_tenants.left_at',
|
||||
'user_tenants.tenant_id'
|
||||
);
|
||||
|
||||
$q->orderBy($sortBy, $sortDir);
|
||||
|
||||
$data = $q->paginate($size, ['*'], 'page', $page);
|
||||
|
||||
return ApiResponse::response('result', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* [POST] 테넌트 사용자 추가 (기존 사용자 연결)
|
||||
* - 컨트롤러 store()에서 호출
|
||||
* - 유저 등록 역할 부여
|
||||
*/
|
||||
public static function store(array $params = [])
|
||||
{
|
||||
$tenantId = app('tenant_id');
|
||||
if (!$tenantId) {
|
||||
return ApiResponse::error('활성 테넌트가 없습니다.', 400);
|
||||
}
|
||||
|
||||
// 신규 회원 생성 + 역할 부여 지원
|
||||
$v = Validator::make($params, [
|
||||
'user_id' => 'required|string|max:255|unique:users,user_id',
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|email|max:100|unique:users,email',
|
||||
'phone' => 'nullable|string|max:30',
|
||||
'password' => 'required|string|min:8|max:64',
|
||||
'roles' => 'nullable|array',
|
||||
'roles.*' => 'string|max:100', // 각각의 역할 이름
|
||||
]);
|
||||
|
||||
if ($v->fails()) {
|
||||
return ApiResponse::error($v->errors()->first(), 422);
|
||||
}
|
||||
|
||||
$payload = $v->validated();
|
||||
|
||||
return DB::transaction(function () use ($payload, $tenantId) {
|
||||
// 신규 사용자 생성
|
||||
$user = User::create([
|
||||
'user_id' => $payload['user_id'],
|
||||
'name' => $payload['name'],
|
||||
'email' => $payload['email'],
|
||||
'phone' => $payload['phone'] ?? null,
|
||||
'password' => $payload['password'], // 캐스트가 알아서 해싱
|
||||
]);
|
||||
|
||||
// 현재 테넌트에 활성 연결
|
||||
UserTenant::create([
|
||||
'user_id' => $user->id,
|
||||
'tenant_id' => $tenantId,
|
||||
'is_active' => 1,
|
||||
'is_default' => 0,
|
||||
'joined_at' => now(),
|
||||
]);
|
||||
|
||||
// 역할 부여 (Spatie Permission teams 모드 가정)
|
||||
if (!empty($payload['roles']) && method_exists($user, 'assignRole')) {
|
||||
$previousTeam = app()->has('permission.team_id') ? app('permission.team_id') : null;
|
||||
app()->instance('permission.team_id', $tenantId);
|
||||
|
||||
try {
|
||||
foreach ($payload['roles'] as $roleName) {
|
||||
$user->assignRole($roleName);
|
||||
}
|
||||
} finally {
|
||||
app()->instance('permission.team_id', $previousTeam);
|
||||
}
|
||||
}
|
||||
|
||||
return ApiResponse::response('result', [
|
||||
'user' => $user->only(['id','user_id','name','email','phone']),
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* [GET] 테넌트 사용자 단건 조회
|
||||
* - 컨트롤러 show()에서 호출
|
||||
*/
|
||||
public static function show(int $userNo)
|
||||
{
|
||||
$tenantId = app('tenant_id');
|
||||
if (!$tenantId) {
|
||||
return ApiResponse::error('활성 테넌트가 없습니다.', 400);
|
||||
}
|
||||
|
||||
if (!$userNo) {
|
||||
return ApiResponse::error('회원 정보가 없습니다.', 422);
|
||||
}
|
||||
|
||||
$user = User::whereHas('userTenants')->find($userNo);
|
||||
|
||||
if (!$user) {
|
||||
return ApiResponse::error('해당 사용자를 찾을 수 없습니다.', 404);
|
||||
}
|
||||
|
||||
return ApiResponse::response('result', $user);
|
||||
}
|
||||
|
||||
/**
|
||||
* [PUT/PATCH] 테넌트 사용자 정보 수정
|
||||
* - 회원 기본정보(user_id, name, email, phone, password) 변경
|
||||
* - 역할(roles) 변경 및 삭제 처리
|
||||
*/
|
||||
public static function update(array $params = [], int $userNo)
|
||||
{
|
||||
$tenantId = app('tenant_id');
|
||||
if (!$tenantId) {
|
||||
return ApiResponse::error('활성 테넌트가 없습니다.', 400);
|
||||
}
|
||||
if (!$userNo) {
|
||||
return ApiResponse::error('회원 정보가 없습니다.', 422);
|
||||
}
|
||||
|
||||
// 1) 유저 존재/테넌트 소속 확인
|
||||
$user = User::find($userNo);
|
||||
if (!$user) {
|
||||
return ApiResponse::error('해당 회원을 찾을 수 없습니다.', 404);
|
||||
}
|
||||
$linked = UserTenant::where('tenant_id', $tenantId)
|
||||
->where('user_id', $userNo)
|
||||
->exists();
|
||||
if (!$linked) {
|
||||
return ApiResponse::error('이 테넌트에 소속된 회원이 아닙니다.', 403);
|
||||
}
|
||||
|
||||
// 2) 프로필 + roles만 수정
|
||||
$v = Validator::make($params, [
|
||||
'user_id' => ['nullable','string','max:255', Rule::unique('users','user_id')->ignore($userNo)],
|
||||
'name' => 'nullable|string|max:255',
|
||||
'email' => ['nullable','email','max:100', Rule::unique('users','email')->ignore($userNo)],
|
||||
'phone' => 'nullable|string|max:30',
|
||||
'password' => 'nullable|string|min:8|max:64',
|
||||
|
||||
'roles' => 'nullable|array',
|
||||
'roles.*' => 'string|max:100',
|
||||
]);
|
||||
if ($v->fails()) {
|
||||
return ApiResponse::error($v->errors()->first(), 422);
|
||||
}
|
||||
$payload = $v->validated();
|
||||
|
||||
// 아무 필드도 없으면 방어
|
||||
$updatableKeys = ['user_id','name','email','phone','password'];
|
||||
$hasProfileInput = (bool) array_intersect(array_keys($payload), $updatableKeys);
|
||||
$hasRolesInput = array_key_exists('roles', $payload);
|
||||
if (!$hasProfileInput && !$hasRolesInput) {
|
||||
return ApiResponse::error('수정할 항목이 없습니다.', 422);
|
||||
}
|
||||
|
||||
return DB::transaction(function () use ($user, $payload, $tenantId, $updatableKeys) {
|
||||
|
||||
// 3) 프로필 업데이트 (제공된 키만 반영)
|
||||
$updateData = [];
|
||||
foreach ($updatableKeys as $k) {
|
||||
if (array_key_exists($k, $payload)) {
|
||||
$updateData[$k] = $payload[$k];
|
||||
}
|
||||
}
|
||||
|
||||
// 비밀번호 처리
|
||||
if (array_key_exists('password', $updateData)) {
|
||||
if ($updateData['password'] === null || $updateData['password'] === '') {
|
||||
unset($updateData['password']); // 빈 값 들어오면 무시
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($updateData)) {
|
||||
$user->fill($updateData);
|
||||
$user->save();
|
||||
}
|
||||
|
||||
// 4) 역할 수정 (teams 모드: 테넌트 컨텍스트로 sync)
|
||||
if (array_key_exists('roles', $payload) && method_exists($user, 'syncRoles')) {
|
||||
$previousTeam = app()->has('permission.team_id') ? app('permission.team_id') : null;
|
||||
app()->instance('permission.team_id', $tenantId);
|
||||
try {
|
||||
// roles 키가 있으면 그 값으로 덮어쓰기 (빈 배열이면 모두 제거)
|
||||
$roles = $payload['roles'] ?? [];
|
||||
$user->syncRoles($roles);
|
||||
} finally {
|
||||
app()->instance('permission.team_id', $previousTeam);
|
||||
}
|
||||
}
|
||||
|
||||
return ApiResponse::response('result', [
|
||||
'user' => $user->only(['id','user_id','name','email','phone']),
|
||||
'roles' => method_exists($user, 'getRoleNames') ? $user->getRoleNames() : [],
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* [DELETE] 테넌트 사용자 삭제(연결 해제)
|
||||
* - soft delete + left_at 기록
|
||||
*/
|
||||
public static function destroy(int $userNo)
|
||||
{
|
||||
$tenantId = app('tenant_id');
|
||||
if (!$tenantId) {
|
||||
return ApiResponse::error('활성 테넌트가 없습니다.', 400);
|
||||
}
|
||||
if (!$userNo) {
|
||||
return ApiResponse::error('회원 정보가 없습니다.', 422);
|
||||
}
|
||||
|
||||
$ut = UserTenant::where('user_id',$userNo)
|
||||
->where('tenant_id', $tenantId)
|
||||
->first();
|
||||
|
||||
if (!$ut) {
|
||||
return ApiResponse::error('해당 사용자를 찾을 수 없습니다.', 404);
|
||||
}
|
||||
|
||||
$ut->left_at = now();
|
||||
$ut->save();
|
||||
$ut->delete(); // SoftDeletes 가정
|
||||
|
||||
return ApiResponse::response('success');
|
||||
}
|
||||
|
||||
/**
|
||||
* [POST] 삭제 복구
|
||||
*/
|
||||
public static function restore(int $userNo)
|
||||
{
|
||||
$tenantId = app('tenant_id');
|
||||
if (!$tenantId) {
|
||||
return ApiResponse::error('활성 테넌트가 없습니다.', 400);
|
||||
}
|
||||
if (!$userNo) {
|
||||
return ApiResponse::error('회원 정보가 없습니다.', 422);
|
||||
}
|
||||
|
||||
$ut = UserTenant::withTrashed()
|
||||
->where('tenant_id', $tenantId)
|
||||
->where('user_id', $userNo)
|
||||
->first();
|
||||
|
||||
if (!$ut) {
|
||||
return ApiResponse::error('해당 사용자를 찾을 수 없습니다.', 404);
|
||||
}
|
||||
|
||||
if ($ut->trashed()) {
|
||||
$ut->restore();
|
||||
$ut->left_at = null;
|
||||
$ut->save();
|
||||
}
|
||||
|
||||
return ApiResponse::response('success');
|
||||
}
|
||||
|
||||
/**
|
||||
* [PATCH] 활성/비활성 토글
|
||||
*/
|
||||
public static function toggle(int $userNo)
|
||||
{
|
||||
$tenantId = app('tenant_id');
|
||||
if (!$tenantId) {
|
||||
return ApiResponse::error('활성 테넌트가 없습니다.', 400);
|
||||
}
|
||||
if (!$userNo) {
|
||||
return ApiResponse::error('회원 정보가 없습니다.', 422);
|
||||
}
|
||||
|
||||
$ut = UserTenant::where('tenant_id', $tenantId)
|
||||
->where('user_id', $userNo)
|
||||
->first();
|
||||
|
||||
if (!$ut) {
|
||||
return ApiResponse::error('해당 사용자를 찾을 수 없습니다.', 404);
|
||||
}
|
||||
|
||||
$ut->is_active = $ut->is_active ? 0 : 1;
|
||||
$ut->save();
|
||||
|
||||
return ApiResponse::response('result',['is_active' => $ut->is_active]);
|
||||
}
|
||||
|
||||
/**
|
||||
* [POST] 역할 부여 (Spatie Permission - teams 사용 가정)
|
||||
* - params: user_id, role_name
|
||||
*/
|
||||
public static function attach(array $params = [])
|
||||
{
|
||||
$tenantId = app('tenant_id');
|
||||
if (!$tenantId) {
|
||||
return ApiResponse::error('활성 테넌트가 없습니다.', 400);
|
||||
}
|
||||
|
||||
$v = Validator::make($params, [
|
||||
'user_id' => 'required|integer|exists:users,id',
|
||||
'role_name' => 'required|string|max:100',
|
||||
]);
|
||||
if ($v->fails()) {
|
||||
return ApiResponse::error($v->errors()->first(), 422);
|
||||
}
|
||||
|
||||
$user = User::find($params['user_id']);
|
||||
if (!method_exists($user, 'assignRole')) {
|
||||
// Spatie 미사용 환경 방어
|
||||
return ApiResponse::error('역할 시스템이 활성화되어 있지 않습니다.', 501);
|
||||
}
|
||||
|
||||
// teams(tenant) 스코프
|
||||
$previousTeam = app()->has('permission.team_id') ? app('permission.team_id') : null;
|
||||
app()->instance('permission.team_id', $tenantId);
|
||||
|
||||
try {
|
||||
$user->assignRole($params['role_name']);
|
||||
} finally {
|
||||
// 원복
|
||||
app()->instance('permission.team_id', $previousTeam);
|
||||
}
|
||||
|
||||
return ApiResponse::response('success');
|
||||
}
|
||||
|
||||
/**
|
||||
* [POST] 역할 해제 (Spatie Permission - teams 사용 가정)
|
||||
* - params: user_id, role_name
|
||||
*/
|
||||
public static function detach(array $params = [])
|
||||
{
|
||||
$tenantId = app('tenant_id');
|
||||
if (!$tenantId) {
|
||||
return ApiResponse::error('활성 테넌트가 없습니다.', 400);
|
||||
}
|
||||
|
||||
$v = Validator::make($params, [
|
||||
'user_id' => 'required|integer|exists:users,id',
|
||||
'role_name' => 'required|string|max:100',
|
||||
]);
|
||||
if ($v->fails()) {
|
||||
return ApiResponse::error($v->errors()->first(), 422);
|
||||
}
|
||||
|
||||
$user = User::find($params['user_id']);
|
||||
if (!method_exists($user, 'removeRole')) {
|
||||
return ApiResponse::error('역할 시스템이 활성화되어 있지 않습니다.', 501);
|
||||
}
|
||||
|
||||
$previousTeam = app()->has('permission.team_id') ? app('permission.team_id') : null;
|
||||
app()->instance('permission.team_id', $tenantId);
|
||||
|
||||
try {
|
||||
$user->removeRole($params['role_name']);
|
||||
} finally {
|
||||
app()->instance('permission.team_id', $previousTeam);
|
||||
}
|
||||
|
||||
return ApiResponse::response('success');
|
||||
}
|
||||
|
||||
/**
|
||||
* [POST] 테넌트 사용자 비밀번호 초기화
|
||||
* - (보안) 관리자 권한 확인은 미들웨어/가드에서 처리 가정
|
||||
* - 새 임시 비밀번호를 설정(응답으로 직접 노출 X 권장)
|
||||
* - 여기서는 옵션에 따라 노출/미노출 선택 가능하도록 구현
|
||||
*/
|
||||
public static function reset(array $params = [],int $userNo)
|
||||
{
|
||||
$tenantId = app('tenant_id');
|
||||
if (!$tenantId) {
|
||||
return ApiResponse::error('활성 테넌트가 없습니다.', 400);
|
||||
}
|
||||
if (!$userNo) {
|
||||
return ApiResponse::error('회원 정보가 없습니다.', 422);
|
||||
}
|
||||
|
||||
$v = Validator::make($params, [
|
||||
'new_password' => 'nullable|string|min:8|max:64',
|
||||
'return_password' => 'nullable|in:0,1', // 1이면 응답에 임시 비번 포함(개발용)
|
||||
]);
|
||||
if ($v->fails()) {
|
||||
return ApiResponse::error($v->errors()->first(), 422);
|
||||
}
|
||||
$payload = $v->validated();
|
||||
|
||||
$user = User::find($userNo);
|
||||
if (!$user) {
|
||||
return ApiResponse::error('유저를 찾을 수 없습니다.', 404);
|
||||
}
|
||||
|
||||
$new = $payload['new_password'] ?? Str::random(12);
|
||||
$user->password = $new;
|
||||
$user->save();
|
||||
|
||||
// (선택) 기존 토큰 무효화
|
||||
// if (method_exists($user, 'tokens')) { $user->tokens()->delete(); }
|
||||
|
||||
$resp = ['status' => 'ok'];
|
||||
if (!empty($payload['return_password'])) {
|
||||
// 운영에선 반환하지 말고 메일/문자 발송을 권장
|
||||
$resp['temp_password'] = $new;
|
||||
}
|
||||
|
||||
return ApiResponse::response('result', $resp);
|
||||
}
|
||||
}
|
||||
@@ -257,6 +257,14 @@ public static function storeTenants(array $params = [])
|
||||
|
||||
$tenant = Tenant::create($payload);
|
||||
|
||||
// 기존 기본값(is_default=1) 해제
|
||||
$apiUser = app('api_user');
|
||||
UserTenant::withoutGlobalScopes()
|
||||
->where('user_id', $apiUser)
|
||||
->where('is_default', 1)
|
||||
->update(['is_default' => 0]);
|
||||
|
||||
|
||||
// 성성된 테넌트를 나의 테넌트로 셋팅
|
||||
$apiUser = app('api_user');
|
||||
UserTenant::create([
|
||||
|
||||
Reference in New Issue
Block a user