Files
sam-api/app/Swagger/v1/FileApi.php
hskwon a27b1b2091 feat: Phase 5.1-1 사용자 초대 + Phase 5.2 알림 설정 API 연동
- 사용자 초대 API: role 문자열 지원 추가 (React 호환)
- 알림 설정 API: 그룹 기반 계층 구조 구현
  - notification_setting_groups 테이블 추가
  - notification_setting_group_items 테이블 추가
  - notification_setting_group_states 테이블 추가
  - GET/PUT /api/v1/settings/notifications 엔드포인트 추가
- Pint 코드 스타일 정리
2025-12-22 17:42:59 +09:00

531 lines
17 KiB
PHP

<?php
namespace App\Swagger\v1;
use OpenApi\Annotations as OA;
/**
* @OA\Tag(name="Files", description="파일 저장소 관리")
*
* ========= 스키마 정의 =========
*
* @OA\Schema(
* schema="File",
* type="object",
* description="파일 모델",
*
* @OA\Property(property="id", type="integer", example=1),
* @OA\Property(property="tenant_id", type="integer", example=1),
* @OA\Property(property="display_name", type="string", example="계약서.pdf", description="사용자에게 표시되는 파일명"),
* @OA\Property(property="stored_name", type="string", example="a1b2c3d4e5f6g7h8.pdf", description="실제 저장된 파일명"),
* @OA\Property(property="folder_id", type="integer", nullable=true, example=1),
* @OA\Property(property="is_temp", type="boolean", example=false),
* @OA\Property(property="file_path", type="string", example="1/product/2025/01/a1b2c3d4e5f6g7h8.pdf"),
* @OA\Property(property="file_size", type="integer", example=1024000, description="파일 크기 (bytes)"),
* @OA\Property(property="mime_type", type="string", example="application/pdf"),
* @OA\Property(property="file_type", type="string", enum={"document","image","excel","archive"}, example="document"),
* @OA\Property(property="document_id", type="integer", nullable=true, example=null),
* @OA\Property(property="document_type", type="string", nullable=true, example=null),
* @OA\Property(property="uploaded_by", type="integer", example=1),
* @OA\Property(property="deleted_by", type="integer", nullable=true, example=null),
* @OA\Property(property="created_at", type="string", format="date-time", example="2025-01-01T00:00:00Z"),
* @OA\Property(property="updated_at", type="string", format="date-time", example="2025-01-01T00:00:00Z"),
* @OA\Property(property="deleted_at", type="string", format="date-time", nullable=true, example=null)
* )
*
* @OA\Schema(
* schema="FileShareLink",
* type="object",
* description="파일 공유 링크",
*
* @OA\Property(property="token", type="string", example="a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6", description="64자 공유 토큰"),
* @OA\Property(property="url", type="string", example="https://api.sam.kr/api/v1/files/share/a1b2c3d4"),
* @OA\Property(property="expires_at", type="string", format="date-time", example="2025-01-02T00:00:00Z")
* )
*
* @OA\Schema(
* schema="StorageUsage",
* type="object",
* description="저장소 사용량 정보",
*
* @OA\Property(property="storage_limit", type="integer", example=10737418240, description="저장소 한도 (bytes)"),
* @OA\Property(property="storage_used", type="integer", example=5368709120, description="사용 중인 용량 (bytes)"),
* @OA\Property(property="storage_used_formatted", type="string", example="5.00 GB"),
* @OA\Property(property="storage_limit_formatted", type="string", example="10.00 GB"),
* @OA\Property(property="usage_percentage", type="number", format="float", example=50.0),
* @OA\Property(property="file_count", type="integer", example=150),
* @OA\Property(
* property="folder_breakdown",
* type="object",
* description="폴더별 사용량",
* example={"product": 2147483648, "quality": 1073741824, "accounting": 536870912}
* )
* )
*/
class FileApi
{
/**
* 파일 업로드 (임시 폴더)
*
* @OA\Post(
* path="/api/v1/files/upload",
* summary="파일 업로드",
* description="파일을 임시 폴더에 업로드합니다. 이후 /files/move로 정식 폴더로 이동시켜야 합니다.",
* tags={"Files"},
* security={{"ApiKeyAuth": {}},{"BearerAuth": {}}},
*
* @OA\RequestBody(
* required=true,
*
* @OA\MediaType(
* mediaType="multipart/form-data",
*
* @OA\Schema(
* required={"file"},
*
* @OA\Property(property="file", type="string", format="binary", description="업로드할 파일 (최대 20MB)"),
* @OA\Property(property="description", type="string", nullable=true, maxLength=500, example="계약서 원본")
* )
* )
* ),
*
* @OA\Response(
* response=200,
* description="파일 업로드 성공",
*
* @OA\JsonContent(
* allOf={
*
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
* @OA\Schema(
*
* @OA\Property(property="data", ref="#/components/schemas/File")
* )
* }
* )
* ),
*
* @OA\Response(response=400, description="용량 초과 또는 파일 형식 오류"),
* @OA\Response(response=401, description="인증 실패")
* )
*/
public function upload() {}
/**
* 파일 이동 (temp → folder)
*
* @OA\Post(
* path="/api/v1/files/move",
* summary="파일 이동",
* description="임시 폴더의 파일들을 정식 폴더로 이동하고 문서에 첨부합니다.",
* tags={"Files"},
* security={{"ApiKeyAuth": {}},{"BearerAuth": {}}},
*
* @OA\RequestBody(
* required=true,
*
* @OA\JsonContent(
* required={"file_ids","folder_id"},
*
* @OA\Property(property="file_ids", type="array", @OA\Items(type="integer"), example={1,2,3}),
* @OA\Property(property="folder_id", type="integer", example=1, description="대상 폴더 ID"),
* @OA\Property(property="document_id", type="integer", nullable=true, example=10, description="첨부할 문서 ID"),
* @OA\Property(property="document_type", type="string", nullable=true, maxLength=100, example="Order", description="문서 타입")
* )
* ),
*
* @OA\Response(
* response=200,
* description="파일 이동 성공",
*
* @OA\JsonContent(
* allOf={
*
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
* @OA\Schema(
*
* @OA\Property(property="data", type="array", @OA\Items(ref="#/components/schemas/File"))
* )
* }
* )
* ),
*
* @OA\Response(response=404, description="파일 또는 폴더를 찾을 수 없음")
* )
*/
public function move() {}
/**
* 파일 목록 조회
*
* @OA\Get(
* path="/api/v1/files",
* summary="파일 목록 조회",
* description="폴더별, 문서별 파일 목록을 조회합니다.",
* tags={"Files"},
* security={{"ApiKeyAuth": {}},{"BearerAuth": {}}},
*
* @OA\Parameter(
* name="folder_id",
* in="query",
* description="폴더 ID (선택)",
*
* @OA\Schema(type="integer")
* ),
*
* @OA\Parameter(
* name="document_id",
* in="query",
* description="문서 ID (선택)",
*
* @OA\Schema(type="integer")
* ),
*
* @OA\Parameter(
* name="document_type",
* in="query",
* description="문서 타입 (선택)",
*
* @OA\Schema(type="string")
* ),
*
* @OA\Parameter(
* name="is_temp",
* in="query",
* description="임시 파일만 조회",
*
* @OA\Schema(type="boolean")
* ),
*
* @OA\Response(
* response=200,
* description="파일 목록 조회 성공",
*
* @OA\JsonContent(
* allOf={
*
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
* @OA\Schema(
*
* @OA\Property(property="data", type="array", @OA\Items(ref="#/components/schemas/File"))
* )
* }
* )
* )
* )
*/
public function index() {}
/**
* 파일 상세 조회
*
* @OA\Get(
* path="/api/v1/files/{id}",
* summary="파일 상세 조회",
* description="파일 ID로 상세 정보를 조회합니다.",
* tags={"Files"},
* security={{"ApiKeyAuth": {}},{"BearerAuth": {}}},
*
* @OA\Parameter(
* name="id",
* in="path",
* required=true,
*
* @OA\Schema(type="integer")
* ),
*
* @OA\Response(
* response=200,
* description="파일 조회 성공",
*
* @OA\JsonContent(
* allOf={
*
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
* @OA\Schema(
*
* @OA\Property(property="data", ref="#/components/schemas/File")
* )
* }
* )
* ),
*
* @OA\Response(response=404, description="파일을 찾을 수 없음")
* )
*/
public function show() {}
/**
* 휴지통 파일 목록
*
* @OA\Get(
* path="/api/v1/files/trash",
* summary="휴지통 파일 목록",
* description="삭제된 파일 목록을 조회합니다 (30일 보관).",
* tags={"Files"},
* security={{"ApiKeyAuth": {}},{"BearerAuth": {}}},
*
* @OA\Response(
* response=200,
* description="휴지통 조회 성공",
*
* @OA\JsonContent(
* allOf={
*
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
* @OA\Schema(
*
* @OA\Property(property="data", type="array", @OA\Items(ref="#/components/schemas/File"))
* )
* }
* )
* )
* )
*/
public function trash() {}
/**
* 파일 다운로드
*
* @OA\Get(
* path="/api/v1/files/{id}/download",
* summary="파일 다운로드",
* description="파일을 다운로드합니다.",
* tags={"Files"},
* security={{"ApiKeyAuth": {}},{"BearerAuth": {}}},
*
* @OA\Parameter(
* name="id",
* in="path",
* required=true,
*
* @OA\Schema(type="integer")
* ),
*
* @OA\Response(
* response=200,
* description="파일 다운로드",
*
* @OA\MediaType(
* mediaType="application/octet-stream",
*
* @OA\Schema(type="string", format="binary")
* )
* ),
*
* @OA\Response(response=404, description="파일을 찾을 수 없음")
* )
*/
public function download() {}
/**
* 파일 삭제 (soft delete)
*
* @OA\Delete(
* path="/api/v1/files/{id}",
* summary="파일 삭제",
* description="파일을 휴지통으로 이동합니다 (복구 가능).",
* tags={"Files"},
* security={{"ApiKeyAuth": {}},{"BearerAuth": {}}},
*
* @OA\Parameter(
* name="id",
* in="path",
* required=true,
*
* @OA\Schema(type="integer")
* ),
*
* @OA\Response(
* response=200,
* description="파일 삭제 성공",
*
* @OA\JsonContent(ref="#/components/schemas/ApiResponse")
* ),
*
* @OA\Response(response=404, description="파일을 찾을 수 없음")
* )
*/
public function destroy() {}
/**
* 파일 복구
*
* @OA\Post(
* path="/api/v1/files/{id}/restore",
* summary="파일 복구",
* description="휴지통의 파일을 복구합니다.",
* tags={"Files"},
* security={{"ApiKeyAuth": {}},{"BearerAuth": {}}},
*
* @OA\Parameter(
* name="id",
* in="path",
* required=true,
*
* @OA\Schema(type="integer")
* ),
*
* @OA\Response(
* response=200,
* description="파일 복구 성공",
*
* @OA\JsonContent(
* allOf={
*
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
* @OA\Schema(
*
* @OA\Property(property="data", ref="#/components/schemas/File")
* )
* }
* )
* ),
*
* @OA\Response(response=404, description="파일을 찾을 수 없음")
* )
*/
public function restore() {}
/**
* 파일 영구 삭제
*
* @OA\Delete(
* path="/api/v1/files/{id}/permanent",
* summary="파일 영구 삭제",
* description="파일을 물리적으로 완전히 삭제합니다 (복구 불가).",
* tags={"Files"},
* security={{"ApiKeyAuth": {}},{"BearerAuth": {}}},
*
* @OA\Parameter(
* name="id",
* in="path",
* required=true,
*
* @OA\Schema(type="integer")
* ),
*
* @OA\Response(
* response=200,
* description="파일 영구 삭제 성공",
*
* @OA\JsonContent(ref="#/components/schemas/ApiResponse")
* ),
*
* @OA\Response(response=404, description="파일을 찾을 수 없음")
* )
*/
public function permanentDelete() {}
/**
* 공유 링크 생성
*
* @OA\Post(
* path="/api/v1/files/{id}/share",
* summary="공유 링크 생성",
* description="파일의 임시 공유 링크를 생성합니다 (기본 24시간).",
* tags={"Files"},
* security={{"ApiKeyAuth": {}},{"BearerAuth": {}}},
*
* @OA\Parameter(
* name="id",
* in="path",
* required=true,
*
* @OA\Schema(type="integer")
* ),
*
* @OA\RequestBody(
*
* @OA\JsonContent(
*
* @OA\Property(property="expiry_hours", type="integer", minimum=1, maximum=168, example=24, description="만료 시간 (시간 단위, 최대 7일)")
* )
* ),
*
* @OA\Response(
* response=200,
* description="공유 링크 생성 성공",
*
* @OA\JsonContent(
* allOf={
*
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
* @OA\Schema(
*
* @OA\Property(property="data", ref="#/components/schemas/FileShareLink")
* )
* }
* )
* ),
*
* @OA\Response(response=404, description="파일을 찾을 수 없음")
* )
*/
public function createShareLink() {}
/**
* 공유 링크로 다운로드 (인증 불필요)
*
* @OA\Get(
* path="/api/v1/files/share/{token}",
* summary="공유 파일 다운로드",
* description="공유 토큰으로 파일을 다운로드합니다 (인증 불필요).",
* tags={"Files"},
*
* @OA\Parameter(
* name="token",
* in="path",
* required=true,
* description="64자 공유 토큰",
*
* @OA\Schema(type="string")
* ),
*
* @OA\Response(
* response=200,
* description="파일 다운로드",
*
* @OA\MediaType(
* mediaType="application/octet-stream",
*
* @OA\Schema(type="string", format="binary")
* )
* ),
*
* @OA\Response(response=404, description="토큰을 찾을 수 없음"),
* @OA\Response(response=410, description="링크가 만료됨")
* )
*/
public function downloadShared() {}
/**
* 저장소 사용량 조회
*
* @OA\Get(
* path="/api/v1/storage/usage",
* summary="저장소 사용량 조회",
* description="현재 테넌트의 저장소 사용량 정보를 조회합니다.",
* tags={"Files"},
* security={{"ApiKeyAuth": {}},{"BearerAuth": {}}},
*
* @OA\Response(
* response=200,
* description="사용량 조회 성공",
*
* @OA\JsonContent(
* allOf={
*
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
* @OA\Schema(
*
* @OA\Property(property="data", ref="#/components/schemas/StorageUsage")
* )
* }
* )
* )
* )
*/
public function storageUsage() {}
}