From 31bc703b4e8a78611ad8242c2878d153f29d1fc2 Mon Sep 17 00:00:00 2001 From: Wonjun You <59856002+wonjunYou@users.noreply.github.com> Date: Sat, 9 Nov 2024 15:37:41 +0900 Subject: [PATCH] release/v1.1.1 (#237) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [IDLE-000] Production CI 스크립트 작성 * [IDLE-000] 센터 공고 수정 API 내 접수 방법 null 비허용 * [IDLE-000] 공고 지원자 조회 시, 삭제된 유저는 조회되지 않도록 수정 * [IDLE-363] 센터 관리자 전화 인증 요청 API * [IDLE-363] 개발 환경 ddl-auto 옵션 임시로 create 옵션으로 변경 * [IDLE-365] 공고 크롤러 selenium 로직 수정 및 로컬 동작 확인 * [IDLE-365] 테스트를 위한 스케줄러 기준 시각 변경 * [IDLE-365] batch job enable 옵션 비활성화 * [IDLE-365] ddl-auto update로 변경 * [IDLE-365] 크롤링 공고 필드 null 비허용 * [IDLE-365] 스케줄러 시간 02시로 설정 * [IDLE-366] spring batch selenium 크롤링을 위한 빌드 스크립트 수정 * [IDLE-366] worknet 사이트가 고용 24 사이트로 통합됨에 따라, 크롤링 스크립트 수정 * [IDLE-366] 크롤러 동작 스케줄링 시간 변경 * [IDLE-366] 스프링 초기 실행 시, spring batch 자동 실행 방지 옵션 추가 * [IDLE-366] spring batch 5 버전에서 업데이트 된 변경사항 적용 및 별도의 JobLauncher 구현 * [IDLE-366] 불필요 의존성 및 옵션 제거 * [IDLE-366] 크롤링 조회 API 내 entity status 필드 추가 및 쿼리 수정 * [IDLE-358] 운영 환경 CD 구축 및 운영 환경 profile 설정 추가 * [IDLE-358] 운영 환경 docker run 실행 시 예외 처리 * [IDLE-000] 크롤링 전체 조회 DTO 생성자 추가 * [IDLE-000] 공고 전체 조회 fetchJoin() 중복 이슈 해결을 위한 subquery 분리 * [IDLE-000] AI 코드리뷰 coderabbit 도입 * [IDLE-000] 크롤링 공고 반경범위 조회 필터를 위한 where절 추가 * [IDLE-000] TimeZone 설정이 적용되지 않는 문제 해결 * [IDLE-000] TimeZone 지정 및 @EnableScheduling 설정 * [IDLE-000] TimeZone 설정 제거 * [IDLE-000] 공고 전체 조회 쿼리 롤백 * [IDLE-000] 기존 공고 내 location을 기반으로 위.경도 값을 decoding하도록 변경 * [IDLE-000] 배포 전 최종 QA * [IDLE-000] 크롤링 공고 생성일자 필드 type 변경(timestamp -> date) * [IDLE-000] Redis 비밀번호 설정 추가 (#171) * [IDLE-000] Redis 비밀번호 설정 추가 * [IDLE-000] yaml 파일에 password 필드 추가 * [IDLE-000] redis local default password 설정 * [IDLE-000] compose 파일 내, 비밀번호 지정 command 설정 * [IDLE-000] Redis 볼륨 설정 * [IDLE-000] ci triggering branch 임시 변경 * [IDLE-000] ci triggering branch 롤백 * [IDLE-000] 테스트 심사 통과를 위한 전화번호 검증 로직 추가 * [IDLE-000] 테스트 심사 통과를 위한 전화번호 검증 로직 추가 * [IDLE-389] 센터 인증 요청 이벤트 발생 시, 디스코드 웹훅 알림을 전송하는 로직 작성 * [IDLE-389] 사업자 등록번호 client properties 클래스명 변경 * [IDLE-389] 코드 리뷰 반영 * [IDLE-000] 트랜잭션 전파 레벨 변경(REQUIRED -> REQUIRES_NEW) * [IDLE-396] FCM 모듈 추가 및 firebase 의존성 설정 * readme 제목 수정 * readme 제목 수정 * [IDLE-399] FCM Device Token 관리 API * [IDLE-399] 알림 도메인 설계 * [IDLE-000] fcm service 설정값 주입을 위한 디렉토리 구조 변경 * [IDLE-000] fcm service 설정값 주입을 위한 디렉토리 구조 변경 * [IDLE-400] 채용 공고 지원자 발생 시, 센터 관리자에게 알림을 발송한다. * [IDLE-400] fcm 모듈 설정 추가 * [IDLE-400] 공고 지원자 발생 시, 모든 센터 관리자들에게 다중 알림을 발송한다. * [IDLE-415] 알림 조회 처리 API * [IDLE-417] 읽지 않은 알림 수 집계 API * [IDLE-418] 알림 목록 조회 API * [IDLE-418] 피드백 반영 * [IDLE-423] soft-delete가 적용된 즐겨찾기 entity에서, 즐겨찾기 해제 후 다시 설정하는 경우 발생하는 버그를 해결한다. * [IDLE-424] 요양 보호사 공고 전체 조회 시, 삭제된 공고가 함께 보이는 문제 해결 * [IDLE-400] 채용 공고 지원자 발생 시, 센터 관리자에게 알림을 발송한다. (#183) * [IDLE-400] 채용 공고 지원자 발생 시, 센터 관리자에게 알림을 발송한다. * [IDLE-400] fcm 모듈 설정 추가 * [IDLE-400] 공고 지원자 발생 시, 모든 센터 관리자들에게 다중 알림을 발송한다. * [IDLE-400] 채용 공고 지원자 발생 시, 센터 관리자에게 알림을 발송한다. * [IDLE-400] fcm 모듈 설정 추가 * [IDLE-400] 공고 지원자 발생 시, 모든 센터 관리자들에게 다중 알림을 발송한다. * [IDLE-415] 알림 조회 처리 API * [IDLE-417] 읽지 않은 알림 수 집계 API * [IDLE-418] 알림 목록 조회 API * [IDLE-418] 피드백 반영 * [IDLE-423] soft-delete가 적용된 즐겨찾기 entity에서, 즐겨찾기 해제 후 다시 설정하는 경우 발생하는 버그를 해결한다. * [IDLE-424] 요양 보호사 공고 전체 조회 시, 삭제된 공고가 함께 보이는 문제 해결 * [IDLE-400] 알림 명세 변경 * [IDLE-400] fcm 설정 파일 디렉토리 path 변경 * [IDLE-400] ddl 옵션 변경 * [IDLE-400] ci/cd 옵션 변경 * [IDLE-000] ci triggering branch 복구 * [IDLE-000] develop 환경에서 fcm service 설정 파일 path 수정 * [IDLE-000] fcm service json 파일명 변경 * [IDLE-000] firebase service key 설정 경로 체크를 위해, cd 스크립트를 수정합니다. * Update README.md * [IDLE-000] fcm service key 경로를 class path 경로로 수정 시도 * [IDLE-000] ci triggering branch 임시 변경 * [IDLE-000] firebaseApp 초기화 임시 비활성화 * [IDLE-000] firebaseApp 초기화 임시 비활성화 * [IDLE-000] firebaseApp 초기화 임시 비활성화 * [IDLE-000] file path 앞에 ./ 제외 * [IDLE-000] 절대 경로로 변경 시도 * [IDLE-000] fcm service key 생성 path 수정 * [IDLE-000] fcm service key json file 생성 스크립트 작성 * [IDLE-000] ci triggering branch develop으로 롤백 * [IDLE-000] 불필요 스크립트 제거 * [IDLE-000] firebase config의 현재 경로를 출력하도록 print문 추가 (#197) * [IDLE-000] firebase config의 현재 경로를 출력하도록 print문 추가 * [IDLE-000] ci triggering branch 수정 * [IDLE-000] ci triggering branch 수정 * [IDLE-000] firebase app 초기화 로직 주석 처리 * [IDLE-000] firebase app 초기화 로직 주석 처리 * [IDLE-000] firebase app 초기화 로직 주석 처리 * [IDLE-000] file 대신 string으로 주입받아 초기화하는 방식으로 전환 * [IDLE-000] 일반 string 대신 base64 인코딩 된 문자열을 주입하도록 처리 * [IDLE-000] 일반 string 주입으로 rollback * [IDLE-000] json string log 추가 * [IDLE-000] base64 문자열로 재 변경 * [IDLE-000] firebase config의 현재 경로를 출력하도록 print문 추가 * [IDLE-429] DB 형상관리를 위한 Flyway 적용 * [IDLE-429] Flyway latest version으로 설정 * [IDLE-429] 피드백 반영 * [IDLE-000] firebase config의 현재 경로를 출력하도록 print문 추가 * [IDLE-000] 알림 조회 처리 로직에 @Transactional 추가 * [IDLE-000] 알림 조회 시, 생성 시각 기준이 아닌 uuid v7 id 기준으로 내림차순 정렬하도록 변경 * [IDLE-000] CI 트리거 브랜치 복구 * [IDLE-454] 테스트를 위한 크롤링 수행 시각 변경 및 로깅 추가 * [IDLE-000] 공고 범위 검색 내 중복 데이터 발생 방지 * [IDLE-000] ci 트리거 브랜치 변경 * [IDLE-000] ci 트리거 브랜치 롤백 * [IDLE-000] monitoring 모듈 추가 및 actuator, prometheus 의존성 추가 * [IDLE-456] monitoring.yml 작성 * [IDLE-456] monitoring profile 추가 * [IDLE-456] actuator dependency group name 수정 * [IDLE-000] 크롤링 주기 하루 2회로 변경 * [IDLE-000] 불필요 로그 제거 * [IDLE-000] 인증번호 SMS 내용 수정 * [IDLE-000] classPath 하위 yaml 파일 확장자 모두 .yml로 통일 * [IDLE-000] 즐겨찾기 facade service에 트랜잭션 추가 (#206) * [IDLE-000] firebase config의 현재 경로를 출력하도록 print문 추가 * [IDLE-000] facade service에 transaction 추가 * [IDLE-461] 유저가 다중 디바이스에서 알림을 받을 수 있도록 개선한다. (#207) * [IDLE-000] firebase config의 현재 경로를 출력하도록 print문 추가 * [IDLE-461] 한 유저가 다중 디바이스 설정이 가능하도록 한다. * [IDLE-000] firebase config의 현재 경로를 출력하도록 print문 추가 * [IDLE-000] presentation module의 gradle 파일에 monitoring 모듈 dependency 추가 * [IDLE-000] deviceToken 서비스에서 트랜잭션 어노테이션 추가 * [IDLE-000] batch 테스트를 위해 30분마다 동작하도록 변경 * [IDLE-475] 채팅, 채팅방 도메인 설계 * [IDLE-475] 채팅 메세지 최소, 최대 길이 제한 설정 * [IDLE-000] Batch 메타데이터 테이블 스크립트 변경 및 크롤링 수행 주기 설정 * [IDLE-000] 운영 환경 CD 스크립트에서 불필요한 step 제거 * [IDLE-000] 도텐브 파일 공백 제거 스크립트 작성, 크롤링 대상일자 전날 등록된 공고로 변경 * [IDLE-000] 크롤링 기준 시각 15시로 임시 변경 * [IDLE-000] cd env 파일 생성 스크립트 변경 * [IDLE-492] 동일 유저가 여러 개의 디바이스를 사용 시, 알림이 중복해서 누적되는 현상 * [IDLE-493] flyway 스크립트 오탈자 수정 * [IDLE-493] flyway 스크립트 오탈자 수정 * [IDLE-494] 요양 보호사 및 센터 프로필에서 긴 텍스트를 입력 가능한 항목을 TEXT 컬럼으로 지정한다. * [IDLE-495] 요양 보호사는 마감된 공고에 지원이 불가능하다. * [IDLE-495] 공고 마감 처리 메서드명 변경 complete -> completed * [IDLE-496] 스프링 프로파일 지정을 위한 환경변수 주입 * [IDLE-496] 로그 일부 수정 * [IDLE-000] 안드로이드 app link를 위한 asset 추가 * [IDLE-190] 센터 관리자 인증 요청 목록 조회 API * [IDLE-000] 크롤링 주기 변경 및 테스트용 에러 로그 추가 * [IDLE-000] 크롤링 대상 사이트에 알림창(alert)이 뜨는 케이스에 대한 처리 * [IDLE-509] 크롤링 전체 조회 시, 공고가 중복 노출되는 현상 해결 * [IDLE-509] 피드백 반영 * [IDLE-509] 사용하지 않는 하위 서비스 의존성 제거 * [IDLE-000] 크롤링 진행 시각 저녁 11시로 변경 --- .../domain/CrawlingJobPostingService.kt | 2 + .../jobposting/domain/JobPostingService.kt | 5 +- .../facade/CarerJobPostingFacadeService.kt | 4 +- .../facade/CrawlingJobPostingFacadeService.kt | 19 +- .../service/domain/CenterManagerService.kt | 4 + .../service/facade/CenterAuthFacadeService.kt | 11 + .../common/scheduler/CrawlingJobScheduler.kt | 2 +- .../com/swm/idle/batch/util/WorknetCrawler.kt | 324 +----------------- ...rawlingJobPostingSpatialQueryRepository.kt | 10 +- .../JobPostingSpatialQueryRepository.kt | 7 +- .../jpa/CenterManagerJpaRepository.kt | 12 + .../auth/center/api/CenterAuthApi.kt | 8 + .../center/controller/CenterAuthController.kt | 5 + .../static/.well-known/assetlinks.json | 14 + .../center/CenterManagerForPendingResponse.kt | 56 +++ 15 files changed, 153 insertions(+), 330 deletions(-) create mode 100644 idle-presentation/src/main/resources/static/.well-known/assetlinks.json create mode 100644 idle-support/transfer/src/main/kotlin/com/swm/idle/support/transfer/auth/center/CenterManagerForPendingResponse.kt diff --git a/idle-application/src/main/kotlin/com/swm/idle/application/jobposting/domain/CrawlingJobPostingService.kt b/idle-application/src/main/kotlin/com/swm/idle/application/jobposting/domain/CrawlingJobPostingService.kt index 869b35b8..5f3c23dc 100644 --- a/idle-application/src/main/kotlin/com/swm/idle/application/jobposting/domain/CrawlingJobPostingService.kt +++ b/idle-application/src/main/kotlin/com/swm/idle/application/jobposting/domain/CrawlingJobPostingService.kt @@ -29,11 +29,13 @@ class CrawlingJobPostingService( } fun findAllByCarerLocationInRange( + carerId: UUID, location: Point, next: UUID?, limit: Long, ): List { return crawlingJobPostingSpatialQueryRepository.findAllInRange( + carerId = carerId, location = location, next = next, limit = limit, diff --git a/idle-application/src/main/kotlin/com/swm/idle/application/jobposting/domain/JobPostingService.kt b/idle-application/src/main/kotlin/com/swm/idle/application/jobposting/domain/JobPostingService.kt index 2bf2c274..9cad2995 100644 --- a/idle-application/src/main/kotlin/com/swm/idle/application/jobposting/domain/JobPostingService.kt +++ b/idle-application/src/main/kotlin/com/swm/idle/application/jobposting/domain/JobPostingService.kt @@ -11,7 +11,6 @@ import com.swm.idle.domain.jobposting.enums.PayType import com.swm.idle.domain.jobposting.repository.jpa.JobPostingJpaRepository import com.swm.idle.domain.jobposting.repository.querydsl.JobPostingQueryRepository import com.swm.idle.domain.jobposting.repository.querydsl.JobPostingSpatialQueryRepository -import com.swm.idle.domain.user.carer.entity.jpa.Carer import com.swm.idle.domain.user.common.enum.GenderType import com.swm.idle.domain.user.common.vo.BirthYear import org.locationtech.jts.geom.Point @@ -195,13 +194,13 @@ class JobPostingService( } fun findAllByCarerLocationInRange( - carer: Carer, + carerId: UUID, location: Point, next: UUID?, limit: Long, ): List { return jobPostingSpatialQueryRepository.findAllWithWeekdaysInRange( - carer = carer, + carerId = carerId, location = location, next = next, limit = limit, diff --git a/idle-application/src/main/kotlin/com/swm/idle/application/jobposting/facade/CarerJobPostingFacadeService.kt b/idle-application/src/main/kotlin/com/swm/idle/application/jobposting/facade/CarerJobPostingFacadeService.kt index aed5b300..cefab582 100644 --- a/idle-application/src/main/kotlin/com/swm/idle/application/jobposting/facade/CarerJobPostingFacadeService.kt +++ b/idle-application/src/main/kotlin/com/swm/idle/application/jobposting/facade/CarerJobPostingFacadeService.kt @@ -106,19 +106,17 @@ class CarerJobPostingFacadeService( } val jobPostingPreviewDtos = jobPostingService.findAllByCarerLocationInRange( - carer = carer, + carerId = carer.id, location = location, next = next, limit = limit + 1, ) - val carerLocation = PointConverter.convertToPoint( latitude = carer.latitude.toDouble(), longitude = carer.longitude.toDouble(), ) - for (jobPostingPreviewDto in jobPostingPreviewDtos) { jobPostingPreviewDto.distance = jobPostingService.calculateDistance( jobPostingPreviewDto.jobPosting, diff --git a/idle-application/src/main/kotlin/com/swm/idle/application/jobposting/facade/CrawlingJobPostingFacadeService.kt b/idle-application/src/main/kotlin/com/swm/idle/application/jobposting/facade/CrawlingJobPostingFacadeService.kt index cf72c638..411e3d1f 100644 --- a/idle-application/src/main/kotlin/com/swm/idle/application/jobposting/facade/CrawlingJobPostingFacadeService.kt +++ b/idle-application/src/main/kotlin/com/swm/idle/application/jobposting/facade/CrawlingJobPostingFacadeService.kt @@ -7,7 +7,6 @@ import com.swm.idle.application.jobposting.domain.JobPostingFavoriteService import com.swm.idle.application.user.carer.domain.CarerService import com.swm.idle.domain.common.dto.CrawlingJobPostingPreviewDto import com.swm.idle.domain.user.carer.entity.jpa.Carer -import com.swm.idle.infrastructure.client.geocode.service.GeoCodeService import com.swm.idle.support.transfer.common.CursorScrollRequest import com.swm.idle.support.transfer.jobposting.carer.CrawlingJobPostingFavoriteResponse import com.swm.idle.support.transfer.jobposting.carer.CrawlingJobPostingScrollResponse @@ -19,7 +18,6 @@ import java.util.* @Service class CrawlingJobPostingFacadeService( private val crawlingJobPostingService: CrawlingJobPostingService, - private val geoCodeService: GeoCodeService, private val carerService: CarerService, private val jobPostingFavoriteService: JobPostingFavoriteService, ) { @@ -99,20 +97,21 @@ class CrawlingJobPostingFacadeService( next: UUID?, limit: Long, ): Pair, UUID?> { + val carer = getUserAuthentication().userId.let { + carerService.getById(it) + } + val crawlingJobPostingPreviewDtos = crawlingJobPostingService.findAllByCarerLocationInRange( + carerId = carer.id, location = location, next = next, limit = limit + 1, ) - val carerLocation = getUserAuthentication().userId.let { - carerService.getById(it) - }.let { - PointConverter.convertToPoint( - latitude = it.latitude.toDouble(), - longitude = it.longitude.toDouble(), - ) - } + val carerLocation = PointConverter.convertToPoint( + latitude = carer.latitude.toDouble(), + longitude = carer.longitude.toDouble(), + ) for (crawlingJobPostingPreviewDto in crawlingJobPostingPreviewDtos) { crawlingJobPostingPreviewDto.distance = crawlingJobPostingService.calculateDistance( diff --git a/idle-application/src/main/kotlin/com/swm/idle/application/user/center/service/domain/CenterManagerService.kt b/idle-application/src/main/kotlin/com/swm/idle/application/user/center/service/domain/CenterManagerService.kt index 6fd56c9f..47681d49 100644 --- a/idle-application/src/main/kotlin/com/swm/idle/application/user/center/service/domain/CenterManagerService.kt +++ b/idle-application/src/main/kotlin/com/swm/idle/application/user/center/service/domain/CenterManagerService.kt @@ -91,4 +91,8 @@ class CenterManagerService( ) } + fun findAllByStatusPending(): List? { + return centerManagerJpaRepository.findAllByStatusPending() + } + } diff --git a/idle-application/src/main/kotlin/com/swm/idle/application/user/center/service/facade/CenterAuthFacadeService.kt b/idle-application/src/main/kotlin/com/swm/idle/application/user/center/service/facade/CenterAuthFacadeService.kt index f4467be9..a2e6ea1e 100644 --- a/idle-application/src/main/kotlin/com/swm/idle/application/user/center/service/facade/CenterAuthFacadeService.kt +++ b/idle-application/src/main/kotlin/com/swm/idle/application/user/center/service/facade/CenterAuthFacadeService.kt @@ -19,6 +19,7 @@ import com.swm.idle.infrastructure.client.businessregistration.exception.Busines import com.swm.idle.infrastructure.client.businessregistration.service.BusinessRegistrationNumberValidationService import com.swm.idle.support.common.encrypt.PasswordEncryptor import com.swm.idle.support.security.exception.SecurityException +import com.swm.idle.support.transfer.auth.center.CenterManagerForPendingResponse import com.swm.idle.support.transfer.auth.center.ValidateBusinessRegistrationNumberResponse import com.swm.idle.support.transfer.auth.common.LoginResponse import com.swm.idle.support.transfer.user.center.JoinStatusInfoResponse @@ -151,4 +152,14 @@ class CenterAuthFacadeService( } } + fun getCenterManagerForPending(): CenterManagerForPendingResponse { + val centerManagers = centerManagerService.findAllByStatusPending() + + return centerManagers?.map { centerManager -> + CenterManagerForPendingResponse.PendingCenterManagerDto.of(centerManager) + }?.let { + CenterManagerForPendingResponse.of(it) + } ?: CenterManagerForPendingResponse.of(emptyList()) + } + } diff --git a/idle-batch/src/main/kotlin/com/swm/idle/batch/common/scheduler/CrawlingJobScheduler.kt b/idle-batch/src/main/kotlin/com/swm/idle/batch/common/scheduler/CrawlingJobScheduler.kt index 2db761e8..7192a043 100644 --- a/idle-batch/src/main/kotlin/com/swm/idle/batch/common/scheduler/CrawlingJobScheduler.kt +++ b/idle-batch/src/main/kotlin/com/swm/idle/batch/common/scheduler/CrawlingJobScheduler.kt @@ -13,7 +13,7 @@ class CrawlingJobScheduler( private val crawlingJobConfig: CrawlingJobConfig, ) { - @Scheduled(cron = "0 05 15 * * *") + @Scheduled(cron = "0 0 23 * * *") fun scheduleJob() { val jobParameters: JobParameters = JobParametersBuilder() .addLong("timestamp", System.currentTimeMillis()) diff --git a/idle-batch/src/main/kotlin/com/swm/idle/batch/util/WorknetCrawler.kt b/idle-batch/src/main/kotlin/com/swm/idle/batch/util/WorknetCrawler.kt index 810c75dd..3c8bf139 100644 --- a/idle-batch/src/main/kotlin/com/swm/idle/batch/util/WorknetCrawler.kt +++ b/idle-batch/src/main/kotlin/com/swm/idle/batch/util/WorknetCrawler.kt @@ -1,293 +1,3 @@ -//package com.swm.idle.batch.util -// -//import com.swm.idle.batch.common.dto.CrawledJobPostingDto -//import io.github.oshai.kotlinlogging.KotlinLogging -//import org.openqa.selenium.Alert -//import org.openqa.selenium.By -//import org.openqa.selenium.NoAlertPresentException -//import org.openqa.selenium.WebDriver -//import org.openqa.selenium.chrome.ChromeDriver -//import org.openqa.selenium.chrome.ChromeDriverService -//import org.openqa.selenium.chrome.ChromeOptions -//import org.openqa.selenium.support.ui.ExpectedConditions -//import org.openqa.selenium.support.ui.WebDriverWait -//import java.io.File -//import java.time.Duration -//import java.time.LocalDate -//import java.time.format.DateTimeFormatter -// -//object WorknetCrawler { -// -// private val logger = KotlinLogging.logger { } -// -// private const val CRAWLING_TARGET_URL_FORMAT = -// "https://www.work24.go.kr/wk/a/b/1200/retriveDtlEmpSrchList.do?basicSetupYn=&careerTo=&keywordJobCd=&occupation=&seqNo=&cloDateEndtParam=&payGbn=&templateInfo=&rot2WorkYn=&shsyWorkSecd=&srcKeywordParam=%EC%9A%94%EC%96%91%EB%B3%B4%ED%98%B8%EC%82%AC&resultCnt=10&keywordJobCont=&cert=&moreButtonYn=Y&minPay=&codeDepth2Info=11000¤tPageNo=1&eventNo=&mode=&major=&resrDutyExcYn=&eodwYn=&sortField=DATE&staArea=&sortOrderBy=DESC&keyword=%EC%9A%94%EC%96%91%EB%B3%B4%ED%98%B8%EC%82%AC&termSearchGbn=all&carrEssYns=&benefitSrchAndOr=O&disableEmpHopeGbn=&actServExcYn=&keywordStaAreaNm=&maxPay=&emailApplyYn=&codeDepth1Info=11000&keywordEtcYn=®DateStdtParam={yesterday}&publDutyExcYn=&keywordJobCdSeqNo=&viewType=&exJobsCd=&templateDepthNmInfo=®ion=&employGbn=&empTpGbcd=&computerPreferential=&infaYn=&cloDateStdtParam=&siteClcd=WORK&searchMode=Y&birthFromYY=&indArea=&careerTypes=&subEmpHopeYn=&tlmgYn=&academicGbn=&templateDepthNoInfo=&foriegn=&entryRoute=&mealOfferClcd=&basicSetupYnChk=&station=&holidayGbn=&srcKeyword=%EC%9A%94%EC%96%91%EB%B3%B4%ED%98%B8%EC%82%AC&academicGbnoEdu=noEdu&enterPriseGbn=all&cloTermSearchGbn=all&birthToYY=&keywordWantedTitle=&stationNm=&benefitGbn=¬SrcKeywordParam=&keywordFlag=¬SrcKeyword=&essCertChk=&depth2SelCode=&keywordBusiNm=&preferentialGbn=&rot3WorkYn=®DateEndtParam={yesterday}&pfMatterPreferential=&pageIndex={pageIndex}&termContractMmcnt=&careerFrom=&laborHrShortYn=#scrollLoc" -// -// private const val JOB_POSTING_COUNT_PER_PAGE = 50 -// -// private lateinit var driver: WebDriver -// -// private val postings = mutableListOf() -// -// private fun initializeDriver() { -// val service = ChromeDriverService.Builder() -// .usingDriverExecutable(File(System.getenv("CHROMEDRIVER_BIN"))) -// .build() -// -// val options = ChromeOptions().apply { -// addArguments("--headless") -// addArguments("--no-sandbox") -// addArguments("--disable-dev-shm-usage") -// addArguments("--disable-gpu") -// addArguments("window-size=1920x1080") -// addArguments("--disable-software-rasterizer") -// addArguments("--ignore-ssl-errors=yes") -// addArguments("--ignore-certificate-errors") -// -// setBinary(System.getenv("CHROME_BIN")) -// } -// -// driver = ChromeDriver(service, options) -// } -// -// fun run(): List? { -// try { -// initializeDriver() -// } catch (e: Exception) { -// logger.error { e.toString() } -// } -// -// logger.info { "=====초기화 완료, 크롤링 작업 시작" } -// -// val formatter = DateTimeFormatter.ofPattern("yyyyMMdd") -// val yesterday = LocalDate.now().minusDays(1).format(formatter) -// val crawlingUrl = CRAWLING_TARGET_URL_FORMAT -// .replace("{yesterday}", yesterday) -// .replace("{pageIndex}", "1") -// -// driver.get(crawlingUrl) -// -// logger.info { "=====크롤링 url: $crawlingUrl" } -// -// val wait = WebDriverWait(driver, Duration.ofSeconds(10)) -// wait.until(ExpectedConditions.visibilityOfElementLocated(By.xpath("//*[@id=\"mForm\"]/div[2]/div/div[1]/div[1]/span/span"))) -// -// val jobPostingCountText = -// driver.findElement(By.xpath("//*[@id=\"mForm\"]/div[2]/div/div[1]/div[1]/span/span")).text -// -// logger.info { "=====크롤링 대상 공고 수: $jobPostingCountText" } -// -// val jobPostingCount = Integer.parseInt(jobPostingCountText.replace(",", "")) -// -// if (jobPostingCount == 0) { -// driver.quit() -// logger.info { "=====크롤링 할 공고가 없어 미리 종료합니다." } -// return null -// } -// -// val pageCount = jobPostingCount / JOB_POSTING_COUNT_PER_PAGE -// -// logger.warn { "===== 크롤링 페이지 수 " + pageCount } -// -// for (i in 1..pageCount) { -// if (i >= 2) { -// val updatedCrawlingUrl = crawlingUrl -// .replace("{yesterday}", yesterday) -// .replace(Regex("pageIndex=\\d+"), "pageIndex=${i}") -// driver.get(updatedCrawlingUrl) -// } -// -// wait.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector("#list1"))) -// -// crawlPosts(1, JOB_POSTING_COUNT_PER_PAGE, postings) -// } -// -// val lastPageJobPostingCount = jobPostingCount % JOB_POSTING_COUNT_PER_PAGE -// -// if (lastPageJobPostingCount > 0) { -// val updateCrawlingUrl = crawlingUrl -// .replace("{yesterday}", yesterday) -// .replace(Regex("pageIndex=\\d+"), "pageIndex=${pageCount + 1}") -// driver.get(updateCrawlingUrl) -// -// wait.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector("#list1"))) -// -// crawlPosts(1, lastPageJobPostingCount, postings) -// } -// -// driver.quit() -// return postings -// } -// -// private fun handleAlertIfPresent() { -// try { -// val alert: Alert = driver.switchTo().alert() -// alert.accept() -// driver.navigate().back() -// } catch (e: NoAlertPresentException) { -// } -// } -// -// private fun crawlPosts( -// start: Int, -// end: Int, -// postings: MutableList, -// ) { -// for (i in start..end) { -// try { -// val originalWindow = driver.windowHandle -// -// val element = driver.findElement(By.xpath("//*[@id=\"list$i\"]/td[2]/a")) -// element.click() -// -// handleAlertIfPresent() -// -// val wait = WebDriverWait(driver, Duration.ofSeconds(5)) -// wait.until(ExpectedConditions.numberOfWindowsToBe(2)) -// -// val allWindows = driver.windowHandles -// -// for (windowHandle in allWindows) { -// if (windowHandle != originalWindow) { -// driver.switchTo().window(windowHandle) -// break -// } -// } -// -// val crawledJobPostingDto = CrawledJobPostingDto( -// title = getTitle(), -// content = getContent(), -// clientAddress = getClientAddress(), -// createdAt = getCreatedAt(), -// payInfo = getPayInfo(), -// workTime = getWorkTime(), -// workSchedule = getWorkSchedule(), -// applyDeadline = getApplyDeadline(), -// recruitmentProcess = getRecruitmentProcess(), -// applyMethod = getApplyMethod(), -// requiredDocument = getRequiredDocument(), -// centerName = getCenterName(), -// centerAddress = getCenterAddress(), -// directUrl = driver.currentUrl, -// ) -// -// postings.add(crawledJobPostingDto) -// -// driver.close() -// driver.switchTo().window(originalWindow) -// } catch (e: Exception) { -// handleAlertIfPresent() -// } -// } -// } -// -// private fun getClientAddress(): String { -// val xpaths = listOf( -// "//*[@id=\"contents\"]/section/div/div[3]/div[15]/div/table/tbody/tr[4]/td/p[2]", -// "//*[@id=\"contents\"]/section/div/div[3]/div[17]/div/table/tbody/tr[4]/td/p[2]", -// "//*[@id=\"contents\"]/section/div/div[3]/div[16]/div/table/tbody/tr[4]/td/p[2]" -// ) -// -// for (xpath in xpaths) { -// try { -// val address = driver.findElement(By.xpath(xpath)).text -// return address.replace("지도보기", "").trim() -// } catch (e: Exception) { -// // Ignore and try the next XPath -// } -// } -// -// throw NoSuchElementException("클라이언트 주소 크롤링 에러") -// } -// -// private fun getRequiredDocument(): String { -// return driver.findElement(By.xpath("//*[@id=\"contents\"]/section/div/div[3]/div[7]/table/tbody/tr/td[4]")).text -// } -// -// private fun getApplyMethod(): String { -// return driver.findElement(By.xpath("//*[@id=\"contents\"]/section/div/div[3]/div[7]/table/tbody/tr/td[3]")).text -// } -// -// private fun getRecruitmentProcess(): String { -// return driver.findElement(By.xpath("//*[@id=\"contents\"]/section/div/div[3]/div[7]/table/tbody/tr/td[2]")).text -// } -// -// private fun getApplyDeadline(): String { -// val applyDeadline = -// driver.findElement(By.xpath("//*[@id=\"contents\"]/section/div/div[3]/div[7]/table/tbody/tr/td[1]")).text -// -// return if (applyDeadline.contains("채용시까지")) { -// LocalDate.now().plusDays(15).format(DateTimeFormatter.ofPattern("yyyyMMdd")) -// } else { -// applyDeadline -// } -// } -// -// private fun getWorkSchedule(): String { -// return driver.findElement(By.xpath("//*[@id=\"contents\"]/section/div/div[3]/div[6]/table/tbody/tr/td[3]")).text -// } -// -// private fun getWorkTime(): String { -// return driver.findElement(By.xpath("//*[@id=\"contents\"]/section/div/div[3]/div[6]/table/tbody/tr/td[2]")).text -// .replace("(근무시간) ", "") -// .substringBefore("주 소정근로시간").trim() -// } -// -// private fun getPayInfo(): String { -// return driver.findElement(By.xpath("//*[@id=\"contents\"]/section/div/div[3]/div[2]/div[1]/div[1]/div[2]/div[2]/div/ul/li[2]/span")).text -// } -// -// private fun getCenterName(): String { -// return driver.findElement(By.xpath("//*[@id=\"contents\"]/section/div/div[3]/div[2]/div[1]/div[2]/div[2]/ul/li[1]/div")).text -// } -// -// private fun getCreatedAt(): String { -// val xpaths = listOf( -// "//*[@id=\"contents\"]/section/div/div[3]/div[16]/table/tbody/tr/td[1]", -// "//*[@id=\"contents\"]/section/div/div[3]/div[18]/table/tbody/tr/td[1]", -// "//*[@id=\"contents\"]/section/div/div[3]/div[17]/table/tbody/tr/td[1]" -// ) -// -// for (xpath in xpaths) { -// try { -// return driver.findElement(By.xpath(xpath)).text -// } catch (e: Exception) { -// // Ignore and try the next XPath -// } -// } -// -// throw NoSuchElementException("CreatedAt element not found using any of the provided XPaths") -// } -// -// private fun getCenterAddress(): String { -// val xpaths = listOf( -// "//*[@id=\"contents\"]/section/div/div[3]/div[15]/div/table/tbody/tr[1]/td", -// "//*[@id=\"contents\"]/section/div/div[3]/div[17]/div/table/tbody/tr[1]/td", -// "//*[@id=\"contents\"]/section/div/div[3]/div[16]/div/table/tbody/tr[4]/td/p[2]" -// ) -// -// for (xpath in xpaths) { -// try { -// val address = driver.findElement(By.xpath(xpath)).text -// return address.replace("지도보기", "").trim().replace(Regex("\\(\\d{5}\\)"), "").trim() -// } catch (e: Exception) { -// } -// } -// -// throw NoSuchElementException("Center address not found using any of the provided XPaths") -// } -// -// private fun getContent(): String { -// return driver.findElement(By.xpath("//*[@id=\"contents\"]/section/div/div[3]/div[3]/table/tbody/tr/td")).text -// } -// -// private fun getTitle(): String { -// val em = driver.findElement(By.cssSelector(".left")) -// return em.findElement(By.cssSelector(".tit-area .tit")).text -// } -// -//} - package com.swm.idle.batch.util import com.swm.idle.batch.common.dto.CrawledJobPostingDto @@ -358,7 +68,7 @@ object WorknetCrawler { logger.info { "=====초기화 완료, 크롤링 작업 시작" } val formatter = DateTimeFormatter.ofPattern("yyyyMMdd") - val yesterday = LocalDate.now().minusDays(1).format(formatter) + val yesterday = LocalDate.now().format(formatter) val crawlingUrl = CRAWLING_TARGET_URL_FORMAT .replace("{yesterday}", yesterday) .replace("{pageIndex}", "1") @@ -422,16 +132,6 @@ object WorknetCrawler { errorCountMap[method] = errorCountMap.getOrDefault(method, 0) + 1 } - private fun handleAlertIfPresent() { - try { - val alert: Alert = driver.switchTo().alert() - alert.accept() - driver.navigate().back() - } catch (e: NoAlertPresentException) { - logError("handleAlertIfPresent", e) - } - } - private fun crawlPosts( start: Int, end: Int, @@ -444,7 +144,10 @@ object WorknetCrawler { val element = driver.findElement(By.xpath("//*[@id=\"list$i\"]/td[2]/a")) element.click() - handleAlertIfPresent() + if (handleAlertIfPresent()) { + driver.navigate().back() + continue + } val wait = WebDriverWait(driver, Duration.ofSeconds(5)) wait.until(ExpectedConditions.numberOfWindowsToBe(2)) @@ -480,12 +183,21 @@ object WorknetCrawler { driver.close() driver.switchTo().window(originalWindow) } catch (e: Exception) { - logError("crawlPosts", e) - handleAlertIfPresent() + logError("=== 에러 원인은..", e) } } } + private fun handleAlertIfPresent(): Boolean { + return try { + val alert: Alert = driver.switchTo().alert() + alert.accept() // 알림창이 있을 경우 수락 + true // 알림창이 있었음을 표시 + } catch (e: NoAlertPresentException) { + false // 알림창이 없었음을 표시 + } + } + private fun getClientAddress(): String { val xpaths = listOf( "//*[@id=\"contents\"]/section/div/div[3]/div[15]/div/table/tbody/tr[4]/td/p[2]", @@ -644,9 +356,9 @@ object WorknetCrawler { // 에러 집계를 출력하는 메서드 추가 fun printErrorSummary() { - logger.info { "===== 에러 집계 =====" } + logger.error { "===== 에러 집계 =====" } errorCountMap.forEach { (method, count) -> - logger.info { "$method: $count errors" } + logger.error { "$method: $count errors" } } } } diff --git a/idle-domain/src/main/kotlin/com/swm/idle/domain/jobposting/repository/querydsl/CrawlingJobPostingSpatialQueryRepository.kt b/idle-domain/src/main/kotlin/com/swm/idle/domain/jobposting/repository/querydsl/CrawlingJobPostingSpatialQueryRepository.kt index 43b6f8fe..91f495b1 100644 --- a/idle-domain/src/main/kotlin/com/swm/idle/domain/jobposting/repository/querydsl/CrawlingJobPostingSpatialQueryRepository.kt +++ b/idle-domain/src/main/kotlin/com/swm/idle/domain/jobposting/repository/querydsl/CrawlingJobPostingSpatialQueryRepository.kt @@ -19,6 +19,7 @@ class CrawlingJobPostingSpatialQueryRepository( ) { fun findAllInRange( + carerId: UUID, location: Point, next: UUID?, limit: Long, @@ -41,8 +42,11 @@ class CrawlingJobPostingSpatialQueryRepository( return jpaQueryFactory .select(crawledJobPosting, jobPostingFavorite) .from(crawledJobPosting) - .leftJoin(jobPostingFavorite).fetchJoin() - .on(crawledJobPosting.id.eq(jobPostingFavorite.jobPostingId)) + .leftJoin(jobPostingFavorite) + .on( + crawledJobPosting.id.eq(jobPostingFavorite.jobPostingId) + .and(jobPostingFavorite.carerId.eq(carerId)) + ) .where(crawledJobPosting.id.`in`(crawledJobPostingIds)) .transform( groupBy(crawledJobPosting.id) @@ -61,7 +65,7 @@ class CrawlingJobPostingSpatialQueryRepository( location: Point, ): BooleanExpression { return Expressions.booleanTemplate( - "ST_Contains(ST_BUFFER({0}, 3000), {1})", + "ST_Contains(ST_BUFFER({0}, 5000), {1})", location, crawledJobPosting.location, ) diff --git a/idle-domain/src/main/kotlin/com/swm/idle/domain/jobposting/repository/querydsl/JobPostingSpatialQueryRepository.kt b/idle-domain/src/main/kotlin/com/swm/idle/domain/jobposting/repository/querydsl/JobPostingSpatialQueryRepository.kt index 085f224c..70d9ed8d 100644 --- a/idle-domain/src/main/kotlin/com/swm/idle/domain/jobposting/repository/querydsl/JobPostingSpatialQueryRepository.kt +++ b/idle-domain/src/main/kotlin/com/swm/idle/domain/jobposting/repository/querydsl/JobPostingSpatialQueryRepository.kt @@ -13,7 +13,6 @@ import com.swm.idle.domain.jobposting.entity.jpa.QJobPosting.jobPosting import com.swm.idle.domain.jobposting.entity.jpa.QJobPostingFavorite.jobPostingFavorite import com.swm.idle.domain.jobposting.entity.jpa.QJobPostingWeekday.jobPostingWeekday import com.swm.idle.domain.jobposting.enums.JobPostingStatus -import com.swm.idle.domain.user.carer.entity.jpa.Carer import org.locationtech.jts.geom.Point import org.springframework.stereotype.Repository import java.util.* @@ -24,7 +23,7 @@ class JobPostingSpatialQueryRepository( ) { fun findAllWithWeekdaysInRange( - carer: Carer, + carerId: UUID, location: Point, next: UUID?, limit: Long, @@ -58,7 +57,7 @@ class JobPostingSpatialQueryRepository( .leftJoin(applys) .on( jobPosting.id.eq(applys.jobPostingId) - .and(applys.carerId.eq(carer.id)) + .and(applys.carerId.eq(carerId)) ) .leftJoin(jobPostingFavorite) .on(jobPosting.id.eq(jobPostingFavorite.jobPostingId)) @@ -86,7 +85,7 @@ class JobPostingSpatialQueryRepository( location: Point, ): BooleanExpression { return Expressions.booleanTemplate( - "ST_Contains(ST_BUFFER({0}, 3000), {1})", + "ST_Contains(ST_BUFFER({0}, 5000), {1})", location, jobPosting.location, ) diff --git a/idle-domain/src/main/kotlin/com/swm/idle/domain/user/center/repository/jpa/CenterManagerJpaRepository.kt b/idle-domain/src/main/kotlin/com/swm/idle/domain/user/center/repository/jpa/CenterManagerJpaRepository.kt index 76fc4261..db066d23 100644 --- a/idle-domain/src/main/kotlin/com/swm/idle/domain/user/center/repository/jpa/CenterManagerJpaRepository.kt +++ b/idle-domain/src/main/kotlin/com/swm/idle/domain/user/center/repository/jpa/CenterManagerJpaRepository.kt @@ -2,6 +2,7 @@ package com.swm.idle.domain.user.center.repository.jpa import com.swm.idle.domain.user.center.entity.jpa.CenterManager import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Query import org.springframework.stereotype.Repository import java.util.* @@ -16,4 +17,15 @@ interface CenterManagerJpaRepository : JpaRepository { fun findAllByCenterBusinessRegistrationNumber(centerBusinessRegistrationNumber: String): List? + @Query( + value = + """ + SELECT * + FROM center_manager + WHERE center_manager.status = 'PENDING' + """, + nativeQuery = true + ) + fun findAllByStatusPending(): List? + } diff --git a/idle-presentation/src/main/kotlin/com/swm/idle/presentation/auth/center/api/CenterAuthApi.kt b/idle-presentation/src/main/kotlin/com/swm/idle/presentation/auth/center/api/CenterAuthApi.kt index cc483d83..658dcdf4 100644 --- a/idle-presentation/src/main/kotlin/com/swm/idle/presentation/auth/center/api/CenterAuthApi.kt +++ b/idle-presentation/src/main/kotlin/com/swm/idle/presentation/auth/center/api/CenterAuthApi.kt @@ -3,12 +3,14 @@ package com.swm.idle.presentation.auth.center.api import com.swm.idle.presentation.common.exception.ErrorResponse import com.swm.idle.presentation.common.security.annotation.Secured import com.swm.idle.support.transfer.auth.center.CenterLoginRequest +import com.swm.idle.support.transfer.auth.center.CenterManagerForPendingResponse import com.swm.idle.support.transfer.auth.center.ChangePasswordRequest import com.swm.idle.support.transfer.auth.center.JoinRequest import com.swm.idle.support.transfer.auth.center.ValidateBusinessRegistrationNumberResponse import com.swm.idle.support.transfer.auth.center.WithdrawRequest import com.swm.idle.support.transfer.auth.common.LoginResponse import com.swm.idle.support.transfer.user.center.JoinStatusInfoResponse +import io.swagger.v3.oas.annotations.Hidden import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.media.Content import io.swagger.v3.oas.annotations.media.Schema @@ -47,6 +49,12 @@ interface CenterAuthApi { @ResponseStatus(HttpStatus.NO_CONTENT) fun requestCenterManagerVerification() + @Hidden + @Operation(summary = "센터 관리자 인증 대기 목록 조회 API") + @GetMapping("/join/requests") + @ResponseStatus(HttpStatus.OK) + fun getCenterManagerForPending(): CenterManagerForPendingResponse + @Operation(summary = "사업자 등록번호 인증 API") @GetMapping("/authentication/{business-registration-number}") @ResponseStatus(HttpStatus.OK) diff --git a/idle-presentation/src/main/kotlin/com/swm/idle/presentation/auth/center/controller/CenterAuthController.kt b/idle-presentation/src/main/kotlin/com/swm/idle/presentation/auth/center/controller/CenterAuthController.kt index 9b59cb1d..00d7c40e 100644 --- a/idle-presentation/src/main/kotlin/com/swm/idle/presentation/auth/center/controller/CenterAuthController.kt +++ b/idle-presentation/src/main/kotlin/com/swm/idle/presentation/auth/center/controller/CenterAuthController.kt @@ -7,6 +7,7 @@ import com.swm.idle.domain.user.center.vo.Password import com.swm.idle.domain.user.common.vo.PhoneNumber import com.swm.idle.presentation.auth.center.api.CenterAuthApi import com.swm.idle.support.transfer.auth.center.CenterLoginRequest +import com.swm.idle.support.transfer.auth.center.CenterManagerForPendingResponse import com.swm.idle.support.transfer.auth.center.ChangePasswordRequest import com.swm.idle.support.transfer.auth.center.JoinRequest import com.swm.idle.support.transfer.auth.center.ValidateBusinessRegistrationNumberResponse @@ -38,6 +39,10 @@ class CenterAuthController( return centerAuthFacadeService.requestCenterManagerVerification() } + override fun getCenterManagerForPending(): CenterManagerForPendingResponse { + return centerAuthFacadeService.getCenterManagerForPending() + } + override fun validateBusinessRegistrationNumber(businessRegistrationNumber: String): ValidateBusinessRegistrationNumberResponse { return centerAuthFacadeService.validateCompany( businessRegistrationNumber = BusinessRegistrationNumber(businessRegistrationNumber), diff --git a/idle-presentation/src/main/resources/static/.well-known/assetlinks.json b/idle-presentation/src/main/resources/static/.well-known/assetlinks.json new file mode 100644 index 00000000..5b52c0e2 --- /dev/null +++ b/idle-presentation/src/main/resources/static/.well-known/assetlinks.json @@ -0,0 +1,14 @@ +[ + { + "relation": [ + "delegate_permission/common.handle_all_urls" + ], + "target": { + "namespace": "android_app", + "package_name": "com.idle.care", + "sha256_cert_fingerprints": [ + "0F:82:C2:F8:FE:04:0F:B0:61:7E:8A:68:DE:D8:35:81:15:D1:E5:6D:30:78:1C:1A:0E:83:F3:EB:5E:2F:8E:68" + ] + } + } +] diff --git a/idle-support/transfer/src/main/kotlin/com/swm/idle/support/transfer/auth/center/CenterManagerForPendingResponse.kt b/idle-support/transfer/src/main/kotlin/com/swm/idle/support/transfer/auth/center/CenterManagerForPendingResponse.kt new file mode 100644 index 00000000..5e126549 --- /dev/null +++ b/idle-support/transfer/src/main/kotlin/com/swm/idle/support/transfer/auth/center/CenterManagerForPendingResponse.kt @@ -0,0 +1,56 @@ +package com.swm.idle.support.transfer.auth.center + +import com.swm.idle.domain.user.center.entity.jpa.CenterManager +import io.swagger.v3.oas.annotations.media.Schema +import java.util.* + +@Schema( + name = "CenterManagerForPendingResponse", + description = "센터 관리자 인증 대기 목록 조회 응답" +) +data class CenterManagerForPendingResponse( + val pendingCenterManagers: List?, +) { + + data class PendingCenterManagerDto( + @Schema(description = "센터 관리자 ID") + val id: UUID, + + @Schema(description = "센터 관리자 로그인 ID") + val identifier: String, + + @Schema(description = "센터 관리자 성명") + val managerName: String, + + @Schema(description = "센터 사업자 등록 번호") + val centerBusinessRegistrationNumber: String, + + @Schema(description = "센터 관리자 개인 연락처") + val phoneNumber: String, + ) { + + companion object { + + fun of(centerManager: CenterManager): PendingCenterManagerDto { + return PendingCenterManagerDto( + id = centerManager.id, + identifier = centerManager.identifier, + managerName = centerManager.name, + centerBusinessRegistrationNumber = centerManager.centerBusinessRegistrationNumber, + phoneNumber = centerManager.phoneNumber + ) + } + + } + + } + + companion object { + + fun of(pendingCenterManagerDtos: List?): CenterManagerForPendingResponse { + return CenterManagerForPendingResponse(pendingCenterManagerDtos) + } + + } + +}