# 소프트 삭제 (Soft Delete) 가이드 ## 개요 소프트 삭제는 데이터를 실제로 삭제하지 않고 `is_deleted` 필드를 `True`로 설정하여 삭제된 것처럼 처리하는 방식입니다. 이를 통해 데이터 복구가 가능하고, 삭제 이력을 추적할 수 있습니다. ## 적용 테이블 | 테이블 | is_deleted | 인덱스 | 비고 | |--------|------------|--------|------| | User | ✅ | idx_user_is_deleted | deleted_at 필드도 포함 | | Project | ✅ | idx_project_is_deleted | | | Image | ✅ | idx_image_is_deleted | | | Lyric | ✅ | idx_lyric_is_deleted | | | Song | ✅ | idx_song_is_deleted | | | SongTimestamp | ✅ | idx_song_timestamp_is_deleted | | | Video | ✅ | idx_video_is_deleted | | | SocialAccount | ✅ | idx_social_account_is_deleted | | | RefreshToken | ❌ | - | 토큰은 is_revoked로 관리 | ## 필드 정의 ```python is_deleted: Mapped[bool] = mapped_column( Boolean, nullable=False, default=False, comment="소프트 삭제 여부 (True: 삭제됨)", ) ``` ## API 엔드포인트 ### 아카이브 삭제 API ``` DELETE /archive/videos/delete/{task_id} ``` task_id에 해당하는 모든 관련 데이터를 소프트 삭제합니다. 백그라운드에서 비동기로 처리됩니다. ## 백그라운드 태스크 ### soft_delete_by_task_id 위치: `app/archive/worker/archive_task.py` ```python from app.archive.worker.archive_task import soft_delete_by_task_id # 백그라운드 태스크로 실행 background_tasks.add_task(soft_delete_by_task_id, task_id) ``` 삭제 대상 테이블 (순서대로): 1. Video 2. SongTimestamp (suno_audio_id 기준) 3. Song 4. Lyric 5. Image 6. Project ## 사용 예시 ### 소프트 삭제 수행 ```python async def soft_delete_project(session: AsyncSession, project_id: int) -> None: """프로젝트를 소프트 삭제합니다.""" stmt = ( update(Project) .where(Project.id == project_id) .values(is_deleted=True) ) await session.execute(stmt) await session.commit() ``` ### 삭제되지 않은 데이터만 조회 ```python async def get_active_projects(session: AsyncSession) -> list[Project]: """삭제되지 않은 프로젝트만 조회합니다.""" stmt = select(Project).where(Project.is_deleted == False) result = await session.execute(stmt) return result.scalars().all() ``` ### 삭제된 데이터 복구 ```python async def restore_project(session: AsyncSession, project_id: int) -> None: """삭제된 프로젝트를 복구합니다.""" stmt = ( update(Project) .where(Project.id == project_id) .values(is_deleted=False) ) await session.execute(stmt) await session.commit() ``` ## 쿼리 시 주의사항 1. **기본 조회 시 is_deleted 필터 추가** - 모든 조회 쿼리에서 `is_deleted == False` 조건을 명시적으로 추가해야 합니다. 2. **관리자 기능에서만 삭제된 데이터 포함** - 일반 사용자 API에서는 삭제된 데이터가 노출되지 않도록 해야 합니다. 3. **CASCADE 삭제와의 관계** - 부모 테이블 소프트 삭제 시 자식 테이블도 함께 소프트 삭제하는 로직 필요 - 또는 부모만 소프트 삭제하고 자식은 JOIN 시 필터링 ## 마이그레이션 기존 데이터베이스에 `is_deleted` 필드를 추가하려면: ```bash # 마이그레이션 SQL 실행 mysql -u -p < docs/database-schema/migration_add_is_deleted.sql ``` 또는 Alembic 마이그레이션 사용: ```bash alembic revision --autogenerate -m "Add is_deleted field to all tables" alembic upgrade head ``` ## 인덱스 활용 `is_deleted` 필드에 인덱스가 생성되어 있으므로, 다음과 같은 쿼리가 효율적으로 실행됩니다: ```sql -- 삭제되지 않은 프로젝트 조회 (인덱스 활용) SELECT * FROM project WHERE is_deleted = FALSE; -- 복합 조건 (task_id + is_deleted) SELECT * FROM project WHERE task_id = 'xxx' AND is_deleted = FALSE; ```