| Dual response wrappers | ApiResponse (7 controllers) / SuccessResponse (4) coexist | Unify to SuccessResponse, then delete ApiResponse |
| Abbreviated DTO suffixes | ToolDetailGetRes, CategoryRes, BoardRes, etc. (dto/res, dto/req packages) | XxxResponse/XxxRequest + dto/response, dto/request |
| Entity suffix | CommentEntity, UserEntity, NotificationEntity, ReportEntity | Comment, User, Notification, Report |
| Transaction import | 17 files importing jakarta.transaction.Transactional — 12 repositories with @Modifying + 5 services (UserService, CommentService, AuthService, NotificationService, TokenService). All 31 annotation declarations in these files are bare, attribute-less annotations, so swapping the import alone keeps behavior identical | org.springframework.transaction.annotation.Transactional |
| Transaction scope | Class-level @Transactional (write) in 23 places. Of these, UserService·CommentService also overlap with a jakarta import, so swapping the import alone still leaves reads as write transactions | Class readOnly = true + @Transactional on write methods only |
| ErrorCode duplicates | Code values E400009·E400012·E400013 are duplicated; the typo constant REFREH_TOKEN_EMPTY_ERROR duplicates REFRESH_TOKEN_EMPTY_ERROR | Make code values unique + remove the typo constant |
| HTTP status mismatch | ResponseEntity.ok() + SuccessCode.SUCCESS_CREATE(201) → body says 201, actual response is 200 | Creation APIs use status(HttpStatus.CREATED) |
| Soft-delete columns | is_deleted (comment) / del_yn (board) / hard delete (ToolLike) mixed | Unify column name·strategy |
| Unused code | S3Service (actually uses OciService), ApiResponse.ofFailure | Delete |
| QueryDSL location | BoardService uses JPAQueryFactory directly | Split into a custom repository (BoardRepositoryCustom) |
BaseTimeEntity type | java.sql.Timestamp | LocalDateTime |