First Commit (API Project)

This commit is contained in:
2025-07-17 10:05:47 +09:00
commit ad702d5ccf
371 changed files with 141373 additions and 0 deletions

View File

@@ -0,0 +1,39 @@
<?php
namespace App\Actions\Fortify;
use App\Models\User;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Laravel\Fortify\Contracts\CreatesNewUsers;
use Laravel\Jetstream\Jetstream;
class CreateNewUser implements CreatesNewUsers
{
use PasswordValidationRules;
/**
* Validate and create a newly registered user.
*
* @param array<string, string> $input
*/
public function create(array $input): User
{
Validator::make($input, [
'USER_ID' => ['required', 'string', 'max:30', 'unique:SITE_USER_INFO,USER_ID'],
'USER_PWD' => ['required', 'string', 'min:8'],
'USER_EMAIL' => ['nullable', 'string', 'email', 'max:40'],
'USER_NCNM' => ['nullable', 'string', 'max:50'],
])->validate();
return User::create([
'USER_ID' => $input['USER_ID'],
'USER_PWD' => Hash::make($input['USER_PWD']), // 비밀번호 암호화 저장
'USER_NCNM' => $input['USER_NCNM'] ?? null,
'USER_EMAIL' => $input['USER_EMAIL'] ?? null,
'USER_HP' => $input['USER_HP'] ?? null,
'USER_STATUS' => '01', // 기본적으로 활성화 상태
'REG_DTTM' => now(), // 등록 날짜 자동 설정
]);
}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace App\Actions\Fortify;
use Illuminate\Validation\Rules\Password;
trait PasswordValidationRules
{
/**
* Get the validation rules used to validate passwords.
*
* @return array<int, \Illuminate\Contracts\Validation\Rule|array<mixed>|string>
*/
protected function passwordRules(): array
{
return ['required', 'string', Password::default(), 'confirmed'];
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace App\Actions\Fortify;
use App\Models\User;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Laravel\Fortify\Contracts\ResetsUserPasswords;
class ResetUserPassword implements ResetsUserPasswords
{
use PasswordValidationRules;
/**
* Validate and reset the user's forgotten password.
*
* @param array<string, string> $input
*/
public function reset(User $user, array $input): void
{
Validator::make($input, [
'password' => $this->passwordRules(),
])->validate();
$user->forceFill([
'password' => Hash::make($input['password']),
])->save();
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace App\Actions\Fortify;
use App\Models\User;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Laravel\Fortify\Contracts\UpdatesUserPasswords;
class UpdateUserPassword implements UpdatesUserPasswords
{
use PasswordValidationRules;
/**
* Validate and update the user's password.
*
* @param array<string, string> $input
*/
public function update(User $user, array $input): void
{
Validator::make($input, [
'current_password' => ['required', 'string', 'current_password:web'],
'password' => $this->passwordRules(),
], [
'current_password.current_password' => __('The provided password does not match your current password.'),
])->validateWithBag('updatePassword');
$user->forceFill([
'password' => Hash::make($input['password']),
])->save();
}
}

View File

@@ -0,0 +1,56 @@
<?php
namespace App\Actions\Fortify;
use App\Models\User;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Laravel\Fortify\Contracts\UpdatesUserProfileInformation;
class UpdateUserProfileInformation implements UpdatesUserProfileInformation
{
/**
* Validate and update the given user's profile information.
*
* @param array<string, mixed> $input
*/
public function update(User $user, array $input): void
{
Validator::make($input, [
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'email', 'max:255', Rule::unique('users')->ignore($user->id)],
'photo' => ['nullable', 'mimes:jpg,jpeg,png', 'max:1024'],
])->validateWithBag('updateProfileInformation');
if (isset($input['photo'])) {
$user->updateProfilePhoto($input['photo']);
}
if ($input['email'] !== $user->email &&
$user instanceof MustVerifyEmail) {
$this->updateVerifiedUser($user, $input);
} else {
$user->forceFill([
'name' => $input['name'],
'email' => $input['email'],
])->save();
}
}
/**
* Update the given verified user's profile information.
*
* @param array<string, string> $input
*/
protected function updateVerifiedUser(User $user, array $input): void
{
$user->forceFill([
'name' => $input['name'],
'email' => $input['email'],
'email_verified_at' => null,
])->save();
$user->sendEmailVerificationNotification();
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Actions\Jetstream;
use App\Models\User;
use Laravel\Jetstream\Contracts\DeletesUsers;
class DeleteUser implements DeletesUsers
{
/**
* Delete the given user.
*/
public function delete(User $user): void
{
$user->deleteProfilePhoto();
$user->tokens->each->delete();
$user->delete();
}
}

131
app/Helpers/ApiResponse.php Normal file
View File

@@ -0,0 +1,131 @@
<?php
namespace App\Helpers;
use Illuminate\Support\Facades\DB;
use Illuminate\Http\JsonResponse;
class ApiResponse
{
function normalizeFiles(array $laravelFiles): array {
$files = ['name' => [], 'type' => [], 'tmp_name' => [], 'size' => [], 'fileType' => []];
foreach ($laravelFiles as $file) {
$files['name'][] = $file->getClientOriginalName();
$files['type'][] = $file->getClientMimeType();
$files['tmp_name'][] = $file->getPathname();
$files['size'][] = $file->getSize();
$files['fileType'][] = '';
}
return $files;
}
# DebugQuery Helper
public static function debugQueryLog(): array
{
$logs = DB::getQueryLog();
return collect($logs)->map(function ($log) {
$query = $log['query'];
foreach ($log['bindings'] as $binding) {
$binding = is_numeric($binding) ? $binding : "'" . addslashes($binding) . "'";
$query = preg_replace('/\\?/', $binding, $query, 1);
}
// \n 제거
$query = str_replace(["\n", "\r"], ' ', $query);
return trim($query);
})->toArray();
}
# ApiResponse Helper
public static function success(
$data = null,
string $message = '요청 성공',
array $debug = []
): JsonResponse {
$response = [
'success' => true,
'message' => $message,
'data' => $data,
];
if(!empty($debug)) $response['query'] = $debug;
return response()->json($response);
}
public static function error(
string $message = '요청 실패',
int $code = 400,
array $error = []
): JsonResponse {
return response()->json([
'success' => false,
'message' => $message,
'error' => [
'code' => $code,
'details' => $error['details'] ?? null,
],
], $code);
}
public static function validate(
bool $condition,
string $message = 'Validation failed',
int $code = 422,
array $extra = []
): ?JsonResponse {
return $condition ? null : self::error($message, $code, $extra);
}
public static function response($type = '', $query = '', $debug = false, $key = ''): array
{
if ($debug && $type != 'success') DB::enableQueryLog(); // 쿼리 추적
$result = match ($type) {
'get' => $key ? $query->get()->keyBy($key) : $query->get(),
'getSub' => $query->get(),
'count' => $query->count(),
'first' => $query->first(),
'success' => 'Success',
'result' => $query,
default => null,
};
if($type=='getSub'){
$array = $result->map(function ($item) {
return (array) $item;
})->toArray();
foreach ($array as $row) {
$data[$row[$key]][] = $row;
}
$result = $data ?? [];
}
$response['data'] = $result;
$response['query'] = ($debug) ? self::debugQueryLog() : [];
return $response;
}
public static function handle(
callable $callback,
string $successMessage = '요청 성공',
string $errorMessage = '요청 실패'
): JsonResponse {
try {
$result = $callback();
if ($result instanceof JsonResponse) {
return $result;
}
return self::success(
$result['data'] ?? null, $successMessage, $result['query'] ?? []
);
} catch (\Throwable $e) {
return self::error($errorMessage, 500, [
'details' => $e->getMessage(),
]);
}
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Helpers\ApiResponse;
use App\Models\SiteAdmin;
class AdminApiController extends Controller
{
/* /**
* @OA\Post(
* path="/api/admin/list",
* summary="관리자 리스트",
* tags={"Admin"},
* security={{"ApiKeyAuth":{}}},
* @OA\RequestBody(
* required=true,
* @OA\JsonContent(
* required={"user_token"},
* @OA\Property(property="user_token", type="string", example="XXXXXXX")
* )
* ),
* @OA\Response(response=200, description="성공"),
* @OA\Response(response=401, description="실패")
* )
*/
public function list(Request $request)
{
return ApiResponse::handle(function () use ($request) {
$admins = new SiteAdmin;
return ApiResponse::response('get', $admins, $request->debug);
}, '관리자 목록 조회 성공', '관리자 목록 조회 실패');
}
}

View File

@@ -0,0 +1,144 @@
<?php
namespace App\Http\Controllers\Api;
use App\Models\Member;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use App\Http\Controllers\Controller;
use App\Models\User;
/**
* @OA\Info(
* version="1.0.0",
* title="SAM API Documentation",
* description="SAM(Semi-Automatics Management) API 입니다.",
* @OA\Contact(
* email="shine1324@gmail.com"
* )
* )
*
* @OA\Server(
* url="https://api.5130.co.kr",
* description="SAM API 서버"
* )
*/
class ApiController extends Controller
{
/**
* @OA\Get(
* path="/api/debug-apikey",
* tags={"API Key 인증"},
* summary="API Key 인증 확인",
* security={{"ApiKeyAuth":{}}},
* @OA\Response(
* response=200,
* description="API Key 인증 성공"
* ),
* @OA\Response(
* response=401,
* description="인증 실패"
* )
* )
*/
public function debugApikey()
{
return response()->json(['message' => 'API Key 인증 성공']);
}
/**
* @OA\Post(
* path="/api/login",
* summary="회원 토큰 정보확인",
* tags={"Auth"},
* @OA\RequestBody(
* required=true,
* @OA\JsonContent(
* required={"USER_ID", "USER_PWD"},
* @OA\Property(property="USER_ID", type="string", example="admin"),
* @OA\Property(property="USER_PWD", type="string", example="1234")
* )
* ),
* @OA\Response(
* response=200,
* description="로그인 성공",
* @OA\JsonContent(
* @OA\Property(property="message", type="string"),
* @OA\Property(property="USER_TOKEN", type="string")
* )
* ),
* @OA\Response(response=401, description="로그인 실패")
* )
*/
public function login(Request $request)
{
$userId = $request->input('user_id');
$userPwd = $request->input('user_pwd');
if (!$userId || !$userPwd) {
return response()->json(['error' => '아이디 또는 비밀번호 누락'], 400);
}
$user = Member::where('mb_id', $userId)->first();
if (!$user) {
return response()->json(['error' => '사용자를 찾을 수 없습니다.'], 404);
}
$isValid = false;
if (Str::startsWith($user->mb_pass, '$2y$')) {
// bcrypt로 해싱된 경우
$isValid = Hash::check($userPwd, $user->mb_pass);
} else {
// sha256으로 해싱된 경우
$isValid = strtoupper(hash('sha256', $userPwd)) === strtoupper($user->mb_pass);
}
if (!$isValid) {
return response()->json(['error' => '아이디 또는 비밀번호가 올바르지 않습니다.'], 401);
}
// 선택: DB에 신규 token 저장
$USER_TOKEN = hash('sha256', $user->mb_id.date('YmdHis'));
$user->remember_token = $USER_TOKEN;
$user->save();
return response()->json([
'message' => '로그인 성공',
'USER_TOKEN' => $user->USER_TOKEN,
]);
}
/**
* @OA\Post(
* path="/api/logout",
* summary="로그아웃 (Access 및 Token 무효화)",
* tags={"Auth"},
* security={{"ApiKeyAuth":{}}},
* @OA\Response(response=200, description="로그아웃 성공"),
* @OA\Response(response=401, description="인증 실패")
* )
*/
public function logout(Request $request)
{
$token = $request->header('X-API-KEY'); // 또는 Authorization 헤더
// 회원 테이블에서 해당 토큰으로 유저 찾기
$user = User::where('remember_token', $token)->first();
if ($user) {
$user->USER_TOKEN = null;
$user->save();
}
return response()->json(['message' => '로그아웃 완료']);
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace App\Http\Controllers\Api;
use App\Helpers\ApiResponse;
use Illuminate\Support\Facades\DB;
class CommonController
{
public static function getComeCode()
{
$query = DB::table('COM_CODE')
->select(['CODE_TP_ID', 'CODE_ID', 'CODE_VAL', 'CODE_DESC', 'USE_YN']);
return ApiResponse::response('get', $query);
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Services\FileService;
use App\Helpers\ApiResponse;
class FileController extends Controller
{
// 파일 업로드
public function upload(Request $request)
{
return ApiResponse::handle(function () use ($request) {
return FileService::uploadFiles($request->all());
}, '파일 업로드 성공', '파일 업로드 실패');
}
// 파일 목록 조회
public function list(Request $request)
{
return ApiResponse::handle(function () use ($request) {
return FileService::getFiles($request->all());
}, '파일 목록조회 성공', '파일 목록조회 실패');
}
// 파일 삭제
public function delete(Request $request)
{
return ApiResponse::handle(function () use ($request) {
return FileService::deleteFiles($request->all());
}, '파일 삭제 성공', '파일 삭제 실패');
}
// 파일 정보 조회 (단건)
public function findFile(Request $request)
{
return ApiResponse::handle(function () use ($request) {
return FileService::findFile($request->all());
}, '파일 정보 조회 성공', '파일 정보 조회 실패');
}
}

View File

@@ -0,0 +1,235 @@
<?php
namespace App\Http\Controllers\Api;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Services\MemberService;
use App\Helpers\ApiResponse;
class MemberController extends Controller
{
/**
* @OA\Get(
* path="/api/member/index",
* summary="회원 목록 조회",
* description="회원 목록을 조회합니다.",
* tags={"Member"},
* security={{"ApiKeyAuth":{}}},
*
* @OA\Parameter(
* name="user_token",
* in="query",
* required=true,
* description="회원 인증용 토큰",
* @OA\Schema(type="string", example="abc123token")
* ),
* @OA\Parameter(
* name="type",
* in="query",
* required=false,
* description="조회 타입: 기본(default), 상세(info)",
* @OA\Schema(type="string", enum={"default", "info"}, example="default")
* ),
* @OA\Parameter(
* name="debug",
* in="query",
* required=false,
* description="디버그 모드 여부 (쿼리 로그 포함 여부)",
* @OA\Schema(type="boolean", example=true)
* ),
* @OA\Parameter(
* name="status",
* in="query",
* required=false,
* description="상태 필터링 (01,02,03 사용자만 조회)",
* @OA\Schema(type="boolean", example=true)
* ),
*
* @OA\Response(
* response=200,
* description="회원 목록 조회 성공",
* @OA\JsonContent(
* type="object",
* @OA\Property(property="success", type="boolean", example=true),
* @OA\Property(property="message", type="string", example="회원목록 조회 성공"),
* @OA\Property(
* property="data",
* type="object",
* @OA\Property(
* property="1",
* type="object",
* @OA\Property(property="mb_num", type="integer", example=1),
* @OA\Property(property="tn_num", type="string", example=null),
* @OA\Property(property="mb_id", type="string", example="admin"),
* @OA\Property(property="mb_name", type="string", example="권혁성"),
* @OA\Property(property="mb_phone", type="string", example="010-4820-9104"),
* @OA\Property(property="mb_mail", type="string", example="shine1324@gmail.com"),
* @OA\Property(property="email_verified_at", type="string", format="date-time", example=null),
* @OA\Property(property="mb_type", type="string", example=null),
* @OA\Property(property="mb_level", type="integer", example=1),
* @OA\Property(property="last_login", type="string", format="date-time", example=null),
* @OA\Property(property="reg_date", type="string", format="date-time", example="2025-07-16T09:28:41.000000Z"),
* @OA\Property(property="created_at", type="string", format="date-time", example=null),
* @OA\Property(property="updated_at", type="string", format="date-time", example="2025-07-16T09:30:56.000000Z")
* )
* ),
* @OA\Property(
* property="query",
* type="array",
* @OA\Items(type="string", example="select * from `members`")
* )
* )
* ),
*
* @OA\Response(response=401, description="인증 실패"),
* @OA\Response(response=403, description="권한 없음")
* )
*/
public function index(Request $request)
{
try {
$type = $request->input('type', 'default');
$userToken = $request->input('user_token', '');
$debug = $request->boolean('debug', false);
$result = MemberService::getMembers($userToken, $type, $debug);
return ApiResponse::success($result['data'], '회원목록 조회 성공',$result['query']);
} catch (\Throwable $e) {
return ApiResponse::error('회원목록 조회 실패', 500, [
'details' => $e->getMessage(),
]);
}
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
//
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
return ApiResponse::handle(function () use ($request) {
return MemberService::setMember($request->all());
}, '회원등록 성공', '회원등록 실패');
}
/**
* @OA\Get (
* path="/api/member/show/{user_no}",
* summary="회원 상세조회",
* description="user_no 기준으로 회원 상세 정보를 조회합니다.",
* tags={"Member"},
* security={{"ApiKeyAuth":{}}},
*
* @OA\Parameter(
* name="user_no",
* in="path",
* required=true,
* description="회원 번호 (USER_NO)",
* @OA\Schema(type="integer", example=1)
* ),
* @OA\Parameter(
* name="debug",
* in="query",
* required=false,
* description="디버그 모드 여부 (쿼리 확인용)",
* @OA\Schema(type="boolean", example=true)
* ),
*
* @OA\Response(
* response=200,
* description="조회 성공",
* @OA\JsonContent(
* type="object",
* @OA\Property(property="success", type="boolean", example=true),
* @OA\Property(property="message", type="string", example="회원 상세조회 성공"),
* @OA\Property(
* property="data",
* type="object",
* @OA\Property(property="mb_num", type="integer", example=1),
* @OA\Property(property="tn_num", type="string", example=null),
* @OA\Property(property="mb_id", type="string", example="admin"),
* @OA\Property(property="mb_name", type="string", example="권혁성"),
* @OA\Property(property="mb_phone", type="string", example="010-4820-9104"),
* @OA\Property(property="mb_mail", type="string", example="shine1324@gmail.com"),
* @OA\Property(property="email_verified_at", type="string", format="date-time", example=null),
* @OA\Property(property="mb_type", type="string", example=null),
* @OA\Property(property="mb_level", type="integer", example=1),
* @OA\Property(property="last_login", type="string", format="date-time", example=null),
* @OA\Property(property="reg_date", type="string", format="date-time", example="2025-07-16T09:28:41.000000Z"),
* @OA\Property(property="created_at", type="string", format="date-time", example=null),
* @OA\Property(property="updated_at", type="string", format="date-time", example="2025-07-16T09:30:56.000000Z")
* ),
* @OA\Property(
* property="query",
* type="array",
* @OA\Items(type="string", example="select * from `members` where `mb_num` = 1 limit 1")
* )
* )
* ),
*
* @OA\Response(response=404, description="회원 정보 없음"),
* @OA\Response(response=401, description="인증 실패")
* )
*/
public function show(Request $request, $userNo)
{
try {
$debug = $request->boolean('debug', false);
$result = MemberService::getMember($userNo, $debug);
return ApiResponse::success($result['data'], '회원 상세조회 성공',$result['query']);
} catch (\Throwable $e) {
return ApiResponse::error('회원 상세조회 실패', 500, [
'details' => $e->getMessage(),
]);
}
}
/**
* Show the form for editing the specified resource.
*/
public function edit(string $id)
{
//
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, string $id)
{
//
}
/**
* Remove the specified resource from storage.
*/
public function delAdmin($userNo, Request $request)
{
return ApiResponse::handle(function () use ($userNo, $request) {
return MemberService::delAdmin($userNo);
}, '관리자 제외 성공', '관리자 제외 실패');
}
/**
* 관리자 설정
*/
public function setAdmin($userNo, Request $request)
{
return ApiResponse::handle(function () use ($userNo, $request) {
return MemberService::setAdmin($userNo);
}, '관리자 설정 성공', '관리자 설정 실패');
}
}

View File

@@ -0,0 +1,8 @@
<?php
namespace App\Http\Controllers;
abstract class Controller
{
//
}

28
app/Http/Kernel.php Normal file
View File

@@ -0,0 +1,28 @@
<?php
namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
/**
* 전역 미들웨어
*/
protected $middleware = [
\App\Http\Middleware\CorsMiddleware::class, // CORS 미들웨어 추가
];
/**
* 웹 미들웨어 그룹
*/
protected $middlewareGroups = [
'web' => [],
'api' => [],
];
/**
* 개별 미들웨어 설정
*/
protected $routeMiddleware = [];
}

View File

@@ -0,0 +1,50 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use App\Models\User;
class ApiKeyMiddleware
{
public function handle(Request $request, Closure $next)
{
$apiKey = $request->header('X-API-KEY');
$validApiKey = false;
// 1. API 키가 유효한지 확인
if ($apiKey) {
$validApiKey = DB::table('api_keys')
->where('key', $apiKey)
->where('is_active', true)
->exists();
// 2. 회원 인증 (remember_token으로)
if (!$validApiKey) {
$user = User::where('remember_token', $apiKey)->first();
if ($user) {
$validApiKey = true;
// ✅ 세션에 유저 정보 저장
session(['Adm' => [
'idx' => $user->mb_num,
'id' => $user->mb_id,
'name' => $user->mb_name,
'level' => $user->mb_level,
'token' => $user->remember_token,
]]);
}
}
}
if (!$validApiKey) {
return response()->json(['message' => 'Unauthorized. Invalid or missing API key or token'], 401);
}
return $next($request);
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use App\Services\AdminPermissionService;
class CheckPermission
{
public function handle(Request $request, Closure $next, string $permissionCode)
{
$userToken = $request->input('user_token');
if (!$userToken) {
$userToken = $request->header('X-API-KEY');
if (!$userToken) {
return response()->json(['error' => '토큰이 없습니다.'], 401);
}
}
if (!AdminPermissionService::hasPermission($userToken, $permissionCode)) {
return response()->json(['error' => '권한이 없습니다.'], 403);
}
return $next($request);
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Session;
use App\Models\Member;
class CheckSwaggerAuth
{
public function handle(Request $request, Closure $next)
{
$token = Session::get('USER_TOKEN');
if (!$token) {
// 원래 URL 저장 후 로그인 페이지로 이동
Session::put('redirect_to', $request->fullUrl());
return redirect()->route('login');
}
$user = Member::where('remember_token', $token)->first();
if (!$user) {
Session::forget('USER_TOKEN');
Session::forget('USER_ID');
Session::put('redirect_to', $request->fullUrl());
return redirect()->route('login');
}
return $next($request);
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class CorsMiddleware
{
public function handle(Request $request, Closure $next): Response
{
$response = $next($request);
$response->headers->set('Access-Control-Allow-Origin', '*');
$response->headers->set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
$response->headers->set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
if ($request->isMethod('OPTIONS')) {
return response()->json([], 200, $response->headers->all());
}
return $response;
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace App\Http\Responses;
use Laravel\Fortify\Contracts\LoginResponse as LoginResponseContract;
use Illuminate\Support\Facades\Session;
use App\Models\User;
class CustomLoginResponse implements LoginResponseContract
{
public function toResponse($request)
{
$user = auth('web')->user();
if (!$user) {
abort(500, '로그인 유저 정보를 가져올 수 없습니다.');
}
//TOKEN 설정
$token = $user->remember_token;
if(!$token || substr($user->reg_date,0,10) < date('Y-m-d', strtotime('-15 day'))) {
$token = hash('sha256', $user->mb_id . now()->format('YmdHis'));
User::where('USER_NO', $user->mb_num)->update(['USER_TOKEN' => $token]);
}
// ✅ 세션에 유저 정보 저장
session(['Adm' => [
'idx' => $user->USER_NO,
'id' => $user->USER_ID,
'name' => $user->USER_NCNM,
'level' => $user->LEVEL,
'part' => $user->USER_PART,
'dept' => $user->USER_DEPT,
'token' => $token,
]]);
Session::put('USER_TOKEN', $token);
Session::put('USER_ID', $user->mb_id);
$redirectTo = session('redirect_to', route('dashboard'));
session()->forget('redirect_to');
return redirect()->to($redirectTo);
}
}

10
app/Models/ApiKey.php Normal file
View File

@@ -0,0 +1,10 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class ApiKey extends Model
{
protected $fillable = ['key', 'description', 'is_active'];
}

39
app/Models/Member.php Normal file
View File

@@ -0,0 +1,39 @@
<?php
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Fortify\TwoFactorAuthenticatable;
use Laravel\Sanctum\HasApiTokens;
class Member extends Authenticatable
{
use HasApiTokens, Notifiable, TwoFactorAuthenticatable;
protected $primaryKey = 'mb_id'; // 기본 키 변경
protected $fillable = [
'mb_id', 'mb_pass', 'mb_name', 'mb_phone', 'mb_mail',
'email_verified_at', 'mb_type', 'mb_level', 'last_login',
'reg_date', 'remember_token'
];
protected $hidden = [
'mb_pass', 'remember_token',
];
protected $casts = [
'reg_date' => 'datetime',
];
public function getAuthPassword()
{
return $this->mb_pass; // 기본 비밀번호 필드를 mb_pass로 설정
}
public function getAuthIdentifierName()
{
return 'mb_id'; // 기본 로그인 필드를 mb_id로 변경
}
}

19
app/Models/SiteAdmin.php Normal file
View File

@@ -0,0 +1,19 @@
<?php
namespace App\Models;
use App\Traits\UppercaseAttributes;
use Illuminate\Database\Eloquent\Model;
class SiteAdmin extends Model
{
use UppercaseAttributes; // 테이블 컬럼명 대문자 처리
protected $table = 'SITE_ADMIN';
protected $primaryKey = 'UNO';
public $timestamps = false;
protected $fillable = [
'UNO', 'LEVEL'. 'COMMENT'
];
}

42
app/Models/User.php Normal file
View File

@@ -0,0 +1,42 @@
<?php
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Fortify\TwoFactorAuthenticatable;
use Laravel\Sanctum\HasApiTokens;
use App\Traits\UppercaseAttributes;
class User extends Authenticatable
{
use HasApiTokens, Notifiable, TwoFactorAuthenticatable;
protected $table = 'members'; // 테이블 이름 변경
protected $primaryKey = 'mb_id'; // 기본 키 변경
protected $fillable = [
'mb_id', 'mb_pass', 'mb_name', 'mb_phone', 'mb_mail',
'email_verified_at', 'mb_type', 'mb_level', 'last_login',
'reg_date', 'remember_token'
];
protected $hidden = [
'mb_pass', 'remember_token',
];
protected $casts = [
'reg_date' => 'datetime',
];
public function getAuthPassword()
{
return $this->mb_pass; // 기본 비밀번호 필드를 mb_pass로 설정
}
public function getAuthIdentifierName()
{
return 'mb_id'; // 기본 로그인 필드를 mb_id로 변경
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
//
}
/**
* Bootstrap any application services.
*/
public function boot(): void
{
//
}
}

View File

@@ -0,0 +1,78 @@
<?php
namespace App\Providers;
use App\Actions\Fortify\CreateNewUser;
use App\Actions\Fortify\ResetUserPassword;
use App\Actions\Fortify\UpdateUserPassword;
use App\Actions\Fortify\UpdateUserProfileInformation;
use App\Models\Member;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Str;
use Laravel\Fortify\Fortify;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Log;
use Laravel\Fortify\Contracts\LoginResponse;
use App\Http\Responses\CustomLoginResponse;
class FortifyServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
// ✅ 커스텀 로그인 응답 등록
$this->app->singleton(LoginResponse::class, CustomLoginResponse::class);
}
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Fortify::createUsersUsing(CreateNewUser::class);
Fortify::updateUserProfileInformationUsing(UpdateUserProfileInformation::class);
Fortify::updateUserPasswordsUsing(UpdateUserPassword::class);
Fortify::resetUserPasswordsUsing(ResetUserPassword::class);
// ✅ 로그인 시 `USER_ID`를 사용하도록 변경
Fortify::authenticateUsing(function (Request $request) {
$user = Member::where('mb_id', $request->USER_ID)->first();
if(!$user) return null;
// 기존 sha256 방식 확인
if ($user && strtoupper(hash('sha256', $request->password)) === $user->mb_pass) {
return $user;
}
// bcrypt 방식으로 저장된 사용자 로그인 처리
else if (Hash::check($request->password, $user->mb_pass)) {
return $user;
}
return null;
});
Fortify::loginView(fn() => view('auth.login')); // 로그인 페이지 지정
RateLimiter::for('login', function (Request $request) {
$throttleKey = Str::transliterate(Str::lower($request->input('USER_ID')).'|'.$request->ip());
return Limit::perMinute(5)->by($throttleKey);
});
RateLimiter::for('two-factor', function (Request $request) {
return Limit::perMinute(5)->by($request->session()->get('login.id'));
});
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace App\Providers;
use App\Actions\Jetstream\DeleteUser;
use Illuminate\Support\ServiceProvider;
use Laravel\Jetstream\Jetstream;
class JetstreamServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
//
}
/**
* Bootstrap any application services.
*/
public function boot(): void
{
$this->configurePermissions();
Jetstream::deleteUsersUsing(DeleteUser::class);
}
/**
* Configure the permissions that are available within the application.
*/
protected function configurePermissions(): void
{
Jetstream::defaultApiTokenPermissions(['read']);
Jetstream::permissions([
'create',
'read',
'update',
'delete',
]);
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace App\Services;
use Illuminate\Support\Facades\DB;
use App\Models\User;
use App\Models\SiteAdmin;
class AdminPermissionService
{
public static function getPermissionsByToken(string $userToken): array
{
$user = User::where('USER_TOKEN', $userToken)
->first();
if (!$user) return [];
$admin = SiteAdmin::where('UNO', $user->USER_NO)
->first();
if (!$admin) return [];
$permissionCodes = DB::table('SITE_ADMIN_USER_ROLE AS ur')
->join('SITE_ADMIN_ROLE_PERMISSION AS rp', 'ur.ROLE_ID', '=', 'rp.ROLE_ID')
->join('SITE_ADMIN_PERMISSIONS AS p', 'rp.PERMISSION_ID', '=', 'p.ID')
->where('ur.USER_ID', $admin->A_IDX)
->pluck('p.CODE')
->toArray();
return $permissionCodes;
}
public static function hasPermission(string $userToken, string $code): bool
{
$permissions = self::getPermissionsByToken($userToken);
return in_array($code, $permissions);
}
}

View File

@@ -0,0 +1,188 @@
<?php
namespace App\Services;
use App\Helpers\ApiResponse;
use Illuminate\Support\Facades\DB;
class FileService
{
public static function saveFiles($files, string $table, string $t_id, string $t_id_type = '00')
{
if (isset($files) && is_array($files)) {
foreach ($files as $file) {
$fileName = $file->getClientOriginalName();
$randomName = bin2hex(random_bytes(16));
$fileType = $file->getClientMimeType();
$fileSize = $file->getSize();
//$tempFile = $file->getPathname();
$folder = config('custom.data_path') . substr($t_id, 0, 6) . "/";
$targetPath = $folder . $randomName;
if (!is_dir($folder)) {
mkdir($folder, 0755, true);
}
try {
//move_uploaded_file($tempFile, $targetPath);
$file->move($folder, $randomName);
chmod($targetPath, 0644);
DB::table('SITE_FILES')->insert([
'F_NAME' => $fileName,
'R_NAME' => $randomName,
'TABLE' => $table,
'T_ID' => $t_id,
'T_ID_TYPE' => $t_id_type,
'F_TYPE' => $fileType,
'F_SIZE' => $fileSize,
'REG_USER_NO' => session('Adm.idx') ?? 1,
]);
} catch (\Exception $e) {
return ApiResponse::error('파일업로드 실패', 422);
}
}
}
}
public static function uploadFiles($request)
{
$files = $request['upload'] ?? '';
$table = $request['table'] ?? 'COMPANY_INFO';
$t_id = $request['com_no'];
$file_no = $request['file_no'] ?? '';
if (isset($files) && is_array($files)) {
foreach ($files as $key => $file) {
$originalName = $file->getClientOriginalName(); // 예: "파일명.jpg"
$ext = pathinfo($originalName, PATHINFO_EXTENSION); // 확장자만 추출: "jpg"
$userInputName = $request['fileName'][$key] ?? null;
$fileName = $userInputName ? $userInputName . '.' . $ext : $originalName;
$randomName = bin2hex(random_bytes(16));
$fileType = $file->getClientMimeType();
$fileSize = $file->getSize();
$t_id_type = $request['fileType'][$key] ?? '00';
$folder = config('custom.data_path') . substr($t_id, 0, 6) . "/";
$targetPath = $folder . $randomName;
if (!is_dir($folder)) {
mkdir($folder, 0755, true);
}
try {
$file->move($folder, $randomName);
chmod($targetPath, 0644);
if (!empty($file_no)) {
self::deleteFiles(['f_id' => $file_no]);
}
// 신규 파일 등록
DB::table('SITE_FILES')->insert([
'F_NAME' => $fileName,
'R_NAME' => $randomName,
'TABLE' => $table,
'T_ID' => $t_id,
'T_ID_TYPE' => $t_id_type,
'F_TYPE' => $fileType,
'F_SIZE' => $fileSize,
'REG_USER_NO' => session('Adm.idx') ?? 1,
]);
} catch (\Exception $e) {
return ApiResponse::error('파일업로드 실패', 422);
}
}
}else if($file_no) {
// 기존파일인데 업로드 파일이 없을경우 이름과 타입만 변경
if ($request['fileName'][1]) {
$ext = pathinfo(DB::table('SITE_FILES')->where('F_NO', $file_no)->value('F_NAME'), PATHINFO_EXTENSION);
$data['F_NAME'] = $request['fileName'][1] . '.' . $ext;
}
if ($request['fileType'][1]) {
$data['T_ID_TYPE'] = $request['fileType'][1];
}
DB::table('SITE_FILES')->where('F_NO', $file_no)->update($data);
}
return self::getFiles($request);
}
public static function getFiles(array $params): array
{
$tType = $params['t_type'] ?? null;
$table = $params['table'] ?? '';
$idx = $params['idx'] ?? '';
$com_no = $params['com_no'] ?? '';
$params['debug'] = $params['debug'] ?? true;
$query = DB::table('SITE_FILES as SF')
->select('SF.T_ID', 'SF.F_NO', 'SF.F_NAME', 'SF.R_NAME', DB::raw('LEFT(SF.T_ID, 6) as PATH'), 'SF.REG_USER_NO');
if ($table) {
$query->where('SF.TABLE', $table);
}
if ($idx) {
$query->whereIn('SF.T_ID', explode(',', $idx));
}else if ($com_no) {
$query->where('SF.T_ID', $com_no);
}
if ($tType) {
$query->where('SF.T_ID_TYPE', $tType);
}
return ApiResponse::response('getSub', $query, $params['debug'], 'T_ID');
}
public static function deleteFiles(array $params): string
{
$table = $params['TABLE'] ?? null;
$t_id = $params['T_ID'] ?? null;
$f_id = $params['f_id'] ?? null;
$query = DB::table('SITE_FILES');
if ($table && $t_id) {
$query->where('TABLE', $table)->where('T_ID', $t_id);
} else if ($f_id) {
$query->whereIn('F_NO', explode(',', $f_id));
} else {
return 'Error';
logger('파일삭제 - 검색조건이 없음');
}
$files = $query->get();
if (empty($files)) {
return 'Success';
}
foreach ($files as $file) {
$filePath = config('custom.data_path') . substr($file->T_ID, 0, 6) . "/" . $file->R_NAME;
if (file_exists($filePath)) {
unlink($filePath);
}
}
$query->delete();
return 'Success';
}
public static function findFile(array $params): ?array
{
$fileName = $params['fileName'] ?? '';
$F_NO = $params['file_no'] ?? '';
$query = DB::table('SITE_FILES as SF')
->select('F_NAME', 'T_ID', 'F_TYPE', DB::raw("IFNULL((SELECT COM_NAME FROM COMPANY_INFO CI WHERE SF.T_ID = CI.COM_NO AND SF.TABLE = 'COMPANY_INFO' LIMIT 1), '') as COM_NAME"));
if($F_NO) $query->where('F_NO', $F_NO);
else $query->where('R_NAME', $fileName);
return ApiResponse::response('first', $query, $params['debug']);
}
}

View File

@@ -0,0 +1,151 @@
<?php
namespace App\Services;
use App\Helpers\ApiResponse;
use App\Models\Member;
use Illuminate\Support\Facades\DB;
class MemberService
{
/**
* 회원 조회(리스트)
*/
public static function getMembers(string $userToken, string $type = 'default', bool $debug = false, bool $status = false)
{
$query = new Member();
return ApiResponse::response('get', $query, $debug, 'mb_num');
}
/**
* 단일 회원 조회
*/
public static function getMember(int $userNo, bool $debug = false)
{
$query = Member::where('mb_num', $userNo);
return ApiResponse::response('first', $query, $debug);
}
/**
* 회원 등록 또는 수정
*/
public static function setMember(array $params)
{
if ($res = ApiResponse::validate(isset($params['user_id']), '아이디 없음')) return $res;
if ($res = ApiResponse::validate(isset($params['user_ncnm']), '이름 없음')) return $res;
$pwd1 = $params['user_pwd1'] ?? null;
$pwd2 = $params['user_pwd2'] ?? null;
if ($res = ApiResponse::validate(
!$pwd1 || $pwd1 === $pwd2,
'비밀번호가 일치하지 않음'
)) return $res;
$now = now();
$data = [
'USER_EMAIL' => $params['user_email'] ?? null,
'USER_HP' => $params['user_hp'] ?? null,
'USER_IP' => $params['user_ip'] ?? null,
'ALT_DTTM' => $now,
];
if (!empty($params['user_start_dt'])) {
$data['USER_START_DT'] = $params['user_start_dt'];
}
if (!empty($params['user_end_dt'])) {
$data['USER_END_DT'] = $params['user_end_dt'];
}
// 신규 등록
if (empty($params['user_no'])) {
// 초기 비빌번호 설정이 없으면 0000 으로 셋팅
$pwd = $pwd1 ?? '0000';
$data += [
'USER_ID' => $params['user_id'],
'USER_PWD' => hash('sha256', $pwd),
'USER_NCNM' => $params['user_ncnm'] ?? null,
'USER_PART' => $params['user_part'] ?? null,
'USER_DEPT' => $params['user_dept'] ?? null,
'USER_ROLE' => $params['user_role'] ?? null,
'USER_STATUS' => $params['user_status'] ?? null,
'USER_MEMO' => $params['user_memo'] ?? null,
'REG_DTTM' => $now,
'ALT_DTTM' => $now,
];
DB::table('SITE_USER_INFO')->insert($data);
}
// 수정
else {
if (!empty($pwd1)) {
$data['USER_PWD'] = hash('sha256', $pwd1);
}
if (AdminPermissionService::hasPermission(session('Adm.token'), 'AC')) {
$data += [
'USER_ID' => $params['user_id'],
'USER_NCNM' => $params['user_ncnm'],
'USER_PART' => $params['user_part'],
'USER_DEPT' => $params['user_dept'],
'USER_ROLE' => $params['user_role'],
'USER_STATUS' => $params['user_status'],
'USER_MEMO' => $params['user_memo'],
'ALT_DTTM' => $now,
];
}
DB::table('SITE_USER_INFO')
->where('USER_NO', $params['user_no'])
->update($data);
}
return ApiResponse::response('success');
}
/**
* 관리자 권한 삭제
*/
public static function delAdmin(int $userNo)
{
DB::table('SITE_ADMIN')->where('UNO', $userNo)->delete();
DB::table('SITE_USER_INFO')
->where('USER_NO', $userNo)
->update(['USER_STATUS' => '02']);
return ApiResponse::response('success');
}
/**
* 관리자 권한 등록
*/
public static function setAdmin(int $userNo)
{
$mem = DB::table('SITE_USER_INFO')
->select('USER_ROLE', 'USER_PART')
->where('USER_NO', $userNo)
->first();
if (!$mem) {
return ApiResponse::error('존재하지 않는 회원입니다.', 404);
}
DB::table('SITE_ADMIN')->updateOrInsert(
['UNO' => $userNo],
['LEVEL' => 'public', 'COMMENT' => '일반관리자']
);
DB::table('SITE_USER_INFO')
->where('USER_NO', $userNo)
->update(['USER_STATUS' => '01']);
return ApiResponse::response('success');
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace App\Traits;
trait UppercaseAttributes
{
protected function getAttributeFromArray($key)
{
$upperKey = strtoupper($key);
return parent::getAttributeFromArray($upperKey);
}
public function __get($key)
{
$upperKey = strtoupper($key);
return parent::__get($upperKey);
}
public function __set($key, $value)
{
$upperKey = strtoupper($key);
return parent::__set($upperKey, $value);
}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace App\View\Components;
use Illuminate\View\Component;
use Illuminate\View\View;
class AppLayout extends Component
{
/**
* Get the view / contents that represents the component.
*/
public function render(): View
{
return view('layouts.app');
}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace App\View\Components;
use Illuminate\View\Component;
use Illuminate\View\View;
class GuestLayout extends Component
{
/**
* Get the view / contents that represents the component.
*/
public function render(): View
{
return view('layouts.guest');
}
}