diff --git a/app/Http/Controllers/AppVersionController.php b/app/Http/Controllers/AppVersionController.php new file mode 100644 index 00000000..a8f42528 --- /dev/null +++ b/app/Http/Controllers/AppVersionController.php @@ -0,0 +1,71 @@ +appVersionService->list(); + + return view('app-versions.index', compact('versions')); + } + + /** + * 새 버전 등록 + */ + public function store(Request $request): RedirectResponse + { + $request->validate([ + 'version_code' => 'required|integer|min:1|unique:app_versions,version_code', + 'version_name' => 'required|string|max:20', + 'platform' => 'required|in:android,ios', + 'release_notes' => 'nullable|string', + 'force_update' => 'nullable|boolean', + 'apk_file' => 'nullable|file|max:204800', // 200MB + ]); + + $this->appVersionService->store( + $request->only(['version_code', 'version_name', 'platform', 'release_notes', 'force_update']), + $request->file('apk_file') + ); + + return redirect()->route('app-versions.index') + ->with('success', '새 버전이 등록되었습니다.'); + } + + /** + * 활성 토글 + */ + public function toggleActive(int $id): RedirectResponse + { + $version = $this->appVersionService->toggleActive($id); + $status = $version->is_active ? '활성화' : '비활성화'; + + return redirect()->route('app-versions.index') + ->with('success', "v{$version->version_name}이 {$status}되었습니다."); + } + + /** + * 삭제 + */ + public function destroy(int $id): RedirectResponse + { + $this->appVersionService->destroy($id); + + return redirect()->route('app-versions.index') + ->with('success', '버전이 삭제되었습니다.'); + } +} diff --git a/app/Models/AppVersion.php b/app/Models/AppVersion.php new file mode 100644 index 00000000..4129a645 --- /dev/null +++ b/app/Models/AppVersion.php @@ -0,0 +1,36 @@ + 'integer', + 'apk_size' => 'integer', + 'force_update' => 'boolean', + 'is_active' => 'boolean', + 'download_count' => 'integer', + 'published_at' => 'datetime', + ]; +} diff --git a/app/Services/AppVersionService.php b/app/Services/AppVersionService.php new file mode 100644 index 00000000..b70439dd --- /dev/null +++ b/app/Services/AppVersionService.php @@ -0,0 +1,65 @@ +paginate($perPage); + } + + /** + * 새 버전 등록 + */ + public function store(array $data, ?UploadedFile $apkFile = null): AppVersion + { + if ($apkFile) { + $data['apk_original_name'] = $apkFile->getClientOriginalName(); + $data['apk_size'] = $apkFile->getSize(); + $data['apk_path'] = $apkFile->store('apk', 'app_releases'); + } + + $data['created_by'] = auth()->id(); + $data['published_at'] = $data['published_at'] ?? now(); + + return AppVersion::create($data); + } + + /** + * 활성 토글 + */ + public function toggleActive(int $id): AppVersion + { + $version = AppVersion::findOrFail($id); + $version->is_active = ! $version->is_active; + $version->updated_by = auth()->id(); + $version->save(); + + return $version; + } + + /** + * 삭제 + */ + public function destroy(int $id): void + { + $version = AppVersion::findOrFail($id); + + // APK 파일 삭제 + if ($version->apk_path && Storage::disk('app_releases')->exists($version->apk_path)) { + Storage::disk('app_releases')->delete($version->apk_path); + } + + $version->delete(); + } +} diff --git a/config/filesystems.php b/config/filesystems.php index 902cb1fa..6413fd2e 100644 --- a/config/filesystems.php +++ b/config/filesystems.php @@ -66,6 +66,14 @@ 'report' => false, ], + 'app_releases' => [ + 'driver' => 'local', + 'root' => env('APP_RELEASES_PATH', '/var/www/shared-storage/releases'), + 'visibility' => 'private', + 'throw' => false, + 'report' => false, + ], + 's3' => [ 'driver' => 's3', 'key' => env('AWS_ACCESS_KEY_ID'), diff --git a/resources/views/app-versions/index.blade.php b/resources/views/app-versions/index.blade.php new file mode 100644 index 00000000..da81cc39 --- /dev/null +++ b/resources/views/app-versions/index.blade.php @@ -0,0 +1,169 @@ +@extends('layouts.app') + +@section('title', '앱 버전 관리') + +@section('content') +
+

앱 버전 관리

+ + {{-- 성공 메시지 --}} + @if(session('success')) +
+ {{ session('success') }} +
+ @endif + + {{-- 에러 메시지 --}} + @if($errors->any()) +
+ +
+ @endif + + {{-- 등록 폼 --}} +
+

새 버전 등록

+
+ @csrf +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+
+ +
+
+
+ + {{-- 버전 목록 --}} +
+
+ + + + + + + + + + + + + + + + @forelse($versions as $version) + + + + + + + + + + + + @empty + + + + @endforelse + +
버전플랫폼변경사항강제활성다운로드APK배포일관리
+
v{{ $version->version_name }}
+
code: {{ $version->version_code }}
+
+ {{ strtoupper($version->platform) }} + + {{ $version->release_notes ? \Illuminate\Support\Str::limit($version->release_notes, 60) : '-' }} + + @if($version->force_update) + 필수 + @else + - + @endif + +
+ @csrf + +
+
+ {{ number_format($version->download_count) }} + + @if($version->apk_path) + + {{ $version->apk_original_name ? \Illuminate\Support\Str::limit($version->apk_original_name, 20) : 'APK' }} + + @if($version->apk_size) + ({{ number_format($version->apk_size / 1024 / 1024, 1) }}MB) + @endif + @else + - + @endif + + {{ $version->published_at?->format('Y-m-d') ?? '-' }} + +
+ @csrf + @method('DELETE') + +
+
등록된 버전이 없습니다.
+
+ + {{-- 페이지네이션 --}} + @if($versions->hasPages()) +
+ {{ $versions->links() }} +
+ @endif +
+
+@endsection diff --git a/routes/web.php b/routes/web.php index 76c2e18e..90a1984e 100644 --- a/routes/web.php +++ b/routes/web.php @@ -4,6 +4,7 @@ use App\Http\Controllers\ApiLogController; use App\Http\Controllers\ArchivedRecordController; use App\Http\Controllers\AuditLogController; +use App\Http\Controllers\AppVersionController; use App\Http\Controllers\Auth\LoginController; use App\Http\Controllers\BoardController; use App\Http\Controllers\CategoryController; @@ -319,6 +320,7 @@ Route::prefix('common-codes')->name('common-codes.')->group(function () { Route::get('/', [CommonCodeController::class, 'index'])->name('index'); Route::post('/', [CommonCodeController::class, 'store'])->name('store'); + Route::post('/store-group', [CommonCodeController::class, 'storeGroup'])->name('store-group'); Route::post('/bulk-copy', [CommonCodeController::class, 'bulkCopy'])->name('bulk-copy'); Route::post('/bulk-promote', [CommonCodeController::class, 'bulkPromoteToGlobal'])->name('bulk-promote'); Route::put('/{id}', [CommonCodeController::class, 'update'])->name('update'); @@ -350,6 +352,14 @@ Route::get('/{id}/edit', [DocumentController::class, 'edit'])->whereNumber('id')->name('edit'); }); + // 앱 버전 관리 + Route::prefix('app-versions')->name('app-versions.')->group(function () { + Route::get('/', [AppVersionController::class, 'index'])->name('index'); + Route::post('/', [AppVersionController::class, 'store'])->name('store'); + Route::post('/{id}/toggle', [AppVersionController::class, 'toggleActive'])->name('toggle'); + Route::delete('/{id}', [AppVersionController::class, 'destroy'])->name('destroy'); + }); + // AI 설정 관리 Route::prefix('system/ai-config')->name('system.ai-config.')->group(function () { Route::get('/', [AiConfigController::class, 'index'])->name('index'); @@ -367,6 +377,7 @@ // 카테고리 관리 Route::prefix('categories')->name('categories.')->group(function () { Route::get('/', [CategoryController::class, 'index'])->name('index'); + Route::post('/store-group', [CategoryController::class, 'storeGroup'])->name('store-group'); // 카테고리 동기화 Route::prefix('sync')->name('sync.')->group(function () {