From 5adc4c061a51cb4ce970d9f6a16e9f1cbd8e23b8 Mon Sep 17 00:00:00 2001 From: JiWoo Date: Fri, 29 Mar 2024 16:56:11 +0900 Subject: [PATCH] [Refactor] time column change data type #74 (#168) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * setting: QueryDsl Qclass 생성 후 페키지 복사하도록 설정 완료 * feat(NoticeDateTime): NoticeDateTime 구현 * feat: NoticeDateTime을 Notice에 적용하기 * refactor(CategoryName): 지원 카테고리 순서 변경 * fix(NoticeDateTime): NoticeDateTime 에 null이나 empty string이 들어오는 경우도 처리하도록 추가 구현 * feat(FakeFirebaseAdapter): Loacal과 test에서 사용하게 될 FakeFirebaseAdapter 구현 * feat: 공지 시간 관련 UPDATE문 작성 * fix(LatestPageNoticeHtmlParser): 파서에서 noticeId를 3자리라 가정하고 파싱하던 문제 수정 * feat(LatestPageNoticeHtmlParserTwo): 더이상 사용하지 않는 LatestPageNoticeHtmlParserTwo 제거 * feat: prod-kr을 임시의 테스트 서버로 변경 --- ".github/workflows/\bprod-kr.yml" | 2 +- .github/workflows/dev.yml | 90 -- build.gradle | 11 +- .../service/AdminCommandService.java | 2 +- .../out/firebase/FakeFirebaseAdapter.java | 49 + .../adapter/out/firebase/FirebaseAdapter.java | 46 +- .../port/out/FirebaseAuthPort.java | 5 +- .../port/out/FirebaseMessagingPort.java | 2 +- .../port/out/FirebaseSubscribePort.java | 8 +- .../service/FirebaseSubscribeService.java | 31 +- .../NoticeQueryRepositoryImpl.java | 75 +- .../kuring/notice/domain/CategoryName.java | 2 +- .../kustacks/kuring/notice/domain/Notice.java | 23 +- .../kuring/notice/domain/NoticeDateTime.java | 102 ++ .../notice/LatestPageNoticeHtmlParser.java | 7 +- .../notice/LatestPageNoticeHtmlParserTwo.java | 39 - .../InterDisciplinaryStudiesDept.java | 4 +- .../PublicAdministrationDept.java | 4 +- .../notice/KuisHomepageNoticeUpdater.java | 1 - src/main/resources/application-local.yml | 103 ++ .../V240327__Edit_post_date_time.sql | 23 + ...Delete_IND_DESIGN_AND_EDU_TECH_notices.sql | 3 + .../acceptance/CategoryAcceptanceTest.java | 6 +- .../acceptance/NoticeAcceptanceTest.java | 19 +- .../out/persistence/NoticeRepositoryTest.java | 16 +- .../notice/domain/DepartmentNoticeTest.java | 6 +- .../notice/domain/NoticeDateTimeTest.java | 111 ++ .../kuring/notice/domain/NoticeTest.java | 4 +- .../kuring/support/DatabaseConfigurator.java | 12 +- .../parser/NoticeHtmlParserTemplateTest.java | 30 +- .../notice/designid-notice-2024.html | 1136 +++++++++++++++++ src/test/resources/notice/kbeauty.html | 688 ---------- 32 files changed, 1739 insertions(+), 921 deletions(-) delete mode 100644 .github/workflows/dev.yml create mode 100644 src/main/java/com/kustacks/kuring/message/adapter/out/firebase/FakeFirebaseAdapter.java create mode 100644 src/main/java/com/kustacks/kuring/notice/domain/NoticeDateTime.java delete mode 100644 src/main/java/com/kustacks/kuring/worker/parser/notice/LatestPageNoticeHtmlParserTwo.java create mode 100644 src/main/resources/application-local.yml create mode 100644 src/main/resources/db/migration/V240327__Edit_post_date_time.sql create mode 100644 src/main/resources/db/migration/V240329__Delete_IND_DESIGN_AND_EDU_TECH_notices.sql create mode 100644 src/test/java/com/kustacks/kuring/notice/domain/NoticeDateTimeTest.java create mode 100644 src/test/resources/notice/designid-notice-2024.html delete mode 100644 src/test/resources/notice/kbeauty.html diff --git "a/.github/workflows/\bprod-kr.yml" "b/.github/workflows/\bprod-kr.yml" index 08701919..e66486e0 100644 --- "a/.github/workflows/\bprod-kr.yml" +++ "b/.github/workflows/\bprod-kr.yml" @@ -6,7 +6,7 @@ name: Deploy to prod KR on: # Triggers the workflow on push or pull request events but only for the master branch push: - branches: [ main ] + branches: [ develop ] env: GPG_PASSPHRASE: ${{ secrets.KR_PROD_GPG_PASSPHRASE }} diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml deleted file mode 100644 index 0695cde1..00000000 --- a/.github/workflows/dev.yml +++ /dev/null @@ -1,90 +0,0 @@ -# This is a basic workflow to help you get started with Actions - -name: Deploy to develop - -# Controls when the workflow will run -on: - # Triggers the workflow on push or pull request events but only for the master branch - push: - branches: [ develop ] - -env: - AWS_REGION: ${{ secrets.TEST_AWS_REGION }} - S3_BUCKET_NAME: ${{ secrets.TEST_S3_BUCKET_NAME }} - CODE_DEPLOY_APPLICATION_NAME: ${{ secrets.TEST_CODE_DEPLOY_APPLICATION_NAME }} - CODE_DEPLOY_DEPLOYMENT_GROUP_NAME: ${{ secrets.TEST_CODE_DEPLOY_DEPLOYMENT_GROUP_NAME }} - AWS_ACCESS_KEY_ID: ${{ secrets.TEST_AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.TEST_AWS_SECRET_ACCESS_KEY }} - - GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - DB_URL: ${{ secrets.APP_ENV_TEST_DB_URL }} - DB_USER: ${{ secrets.APP_ENV_TEST_DB_USER }} - DB_PASSWORD: ${{ secrets.APP_ENV_TEST_DB_PASSWORD }} - DEPLOY_ENV: ${{ secrets.APP_ENV_TEST_DEPLOY_ENV }} - KU_ID: ${{ secrets.APP_ENV_TEST_KU_ID }} - KU_PASSWORD: ${{ secrets.APP_ENV_TEST_KU_PASSWORD }} - SENTRY_URL: ${{ secrets.APP_ENV_SENTRY_URL }} - TZ: ${{ secrets.APP_ENV_TZ }} - JWT_SECRET_KEY: ${{ secrets.JWT_SECRET_KEY }} - JWT_EXPIRE_LENGTH: ${{ secrets.JWT_EXPIRE_LENGTH }} - -permissions: - packages: write - contents: read - id-token: write - -# A workflow run is made up of one or more jobs that can run sequentially or in parallel -jobs: - deploy: - name: Deploy - runs-on: ubuntu-latest - environment: TestServer - - steps: - # (1) 기본 체크아웃 - - name: Checkout - uses: actions/checkout@v3 - - # (2) JDK 17 세팅 - - name: Set up JDK 17 - uses: actions/setup-java@v3 - with: - distribution: 'temurin' - java-version: '17' - - # (3) firebase secret decrypt - - name: Decrypt Secrets - run: | - sh .github/workflows/decrypt.sh - - # (4) Gradle build - - name: Build with Gradle - uses: gradle/gradle-build-action@0d13054264b0bb894ded474f08ebb30921341cee - with: - arguments: clean build - - # (5) AWS 인증 (IAM 사용자 Access Key, Secret Key 활용) - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@master - with: - aws-access-key-id: ${{ env.AWS_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ env.AWS_SECRET_ACCESS_KEY }} - aws-region: ${{ env.AWS_REGION }} - - # (6) 빌드 결과물을 S3 버킷에 업로드 - - name: Upload to AWS S3 - run: | - aws deploy push \ - --application-name ${{ env.CODE_DEPLOY_APPLICATION_NAME }} \ - --ignore-hidden-files \ - --s3-location s3://$S3_BUCKET_NAME/$GITHUB_SHA.zip \ - --source . - - # (7) S3 버킷에 있는 파일을 대상으로 CodeDeploy 실행 - - name: Deploy to AWS EC2 from S3 - run: | - aws deploy create-deployment \ - --application-name ${{ env.CODE_DEPLOY_APPLICATION_NAME }} \ - --deployment-config-name CodeDeployDefault.AllAtOnce \ - --deployment-group-name ${{ env.CODE_DEPLOY_DEPLOYMENT_GROUP_NAME }} \ - --s3-location bucket=$S3_BUCKET_NAME,key=$GITHUB_SHA.zip,bundleType=zip diff --git a/build.gradle b/build.gradle index ef52b484..66239aeb 100644 --- a/build.gradle +++ b/build.gradle @@ -126,7 +126,16 @@ jar { } // -- QueryDsl 설정 ------------------------------------------------------- -//Querydsl 추가, 자동 생성된 Q클래스 gradle clean으로 제거 +//Querydsl 추가 +def querydslSrcDir = 'src/main/generated' +clean { + delete file(querydslSrcDir) +} +tasks.withType(JavaCompile) { + options.generatedSourceOutputDirectory = file(querydslSrcDir) +} + +// 자동 생성된 Q클래스 gradle clean으로 제거 clean { delete file('src/main/generated') } diff --git a/src/main/java/com/kustacks/kuring/admin/application/service/AdminCommandService.java b/src/main/java/com/kustacks/kuring/admin/application/service/AdminCommandService.java index e0b6f716..23a76cb9 100644 --- a/src/main/java/com/kustacks/kuring/admin/application/service/AdminCommandService.java +++ b/src/main/java/com/kustacks/kuring/admin/application/service/AdminCommandService.java @@ -37,7 +37,7 @@ public class AdminCommandService implements AdminCommandUseCase { @Override public void createTestNotice(TestNotificationCommand command) { String testNoticePostedDate = LocalDateTime.now() - .format(DateTimeFormatter.ofPattern("yyyy-MM-dd")); + .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); CategoryName testCategoryName = CategoryName.fromStringName(command.category()); diff --git a/src/main/java/com/kustacks/kuring/message/adapter/out/firebase/FakeFirebaseAdapter.java b/src/main/java/com/kustacks/kuring/message/adapter/out/firebase/FakeFirebaseAdapter.java new file mode 100644 index 00000000..8163f8bd --- /dev/null +++ b/src/main/java/com/kustacks/kuring/message/adapter/out/firebase/FakeFirebaseAdapter.java @@ -0,0 +1,49 @@ +package com.kustacks.kuring.message.adapter.out.firebase; + +import com.google.firebase.messaging.FirebaseMessagingException; +import com.google.firebase.messaging.Message; +import com.kustacks.kuring.message.application.port.out.FirebaseAuthPort; +import com.kustacks.kuring.message.application.port.out.FirebaseMessagingPort; +import com.kustacks.kuring.message.application.port.out.FirebaseSubscribePort; +import com.kustacks.kuring.message.application.service.exception.FirebaseInvalidTokenException; +import com.kustacks.kuring.message.application.service.exception.FirebaseSubscribeException; +import com.kustacks.kuring.message.application.service.exception.FirebaseUnSubscribeException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Slf4j +@Component +@Profile("local | test") +@RequiredArgsConstructor +public class FakeFirebaseAdapter implements FirebaseSubscribePort, FirebaseAuthPort, FirebaseMessagingPort { + + @Override + public void verifyIdToken(String idToken) throws FirebaseInvalidTokenException { + log.info("FirebaseAdapter.verifyIdToken()"); + } + + @Override + public void subscribeToTopic( + List tokens, + String topic + ) throws FirebaseSubscribeException { + log.info("FirebaseAdapter.subscribeToTopic()"); + } + + @Override + public void unsubscribeFromTopic( + List tokens, + String topic + ) throws FirebaseUnSubscribeException { + log.info("FirebaseAdapter.unsubscribeFromTopic()"); + } + + @Override + public void send(Message message) throws FirebaseMessagingException { + log.info("FirebaseAdapter.send()"); + } +} diff --git a/src/main/java/com/kustacks/kuring/message/adapter/out/firebase/FirebaseAdapter.java b/src/main/java/com/kustacks/kuring/message/adapter/out/firebase/FirebaseAdapter.java index 457f2152..cc387db4 100644 --- a/src/main/java/com/kustacks/kuring/message/adapter/out/firebase/FirebaseAdapter.java +++ b/src/main/java/com/kustacks/kuring/message/adapter/out/firebase/FirebaseAdapter.java @@ -2,7 +2,6 @@ import com.google.firebase.auth.FirebaseAuth; import com.google.firebase.auth.FirebaseAuthException; -import com.google.firebase.auth.FirebaseToken; import com.google.firebase.messaging.FirebaseMessaging; import com.google.firebase.messaging.FirebaseMessagingException; import com.google.firebase.messaging.Message; @@ -10,12 +9,17 @@ import com.kustacks.kuring.message.application.port.out.FirebaseAuthPort; import com.kustacks.kuring.message.application.port.out.FirebaseMessagingPort; import com.kustacks.kuring.message.application.port.out.FirebaseSubscribePort; +import com.kustacks.kuring.message.application.service.exception.FirebaseInvalidTokenException; +import com.kustacks.kuring.message.application.service.exception.FirebaseSubscribeException; +import com.kustacks.kuring.message.application.service.exception.FirebaseUnSubscribeException; import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Component; import java.util.List; @Component +@Profile("prod | dev") @RequiredArgsConstructor public class FirebaseAdapter implements FirebaseSubscribePort, FirebaseAuthPort, FirebaseMessagingPort { @@ -23,28 +27,48 @@ public class FirebaseAdapter implements FirebaseSubscribePort, FirebaseAuthPort, private final FirebaseAuth firebaseAuth; @Override - public FirebaseToken verifyIdToken(String idToken) throws FirebaseAuthException { - return firebaseAuth.verifyIdToken(idToken); + public void verifyIdToken(String idToken) throws FirebaseInvalidTokenException { + try { + firebaseAuth.verifyIdToken(idToken); + } catch (FirebaseAuthException exception) { + throw new FirebaseInvalidTokenException(); + } } @Override - public TopicManagementResponse subscribeToTopic( + public void subscribeToTopic( List tokens, String topic - ) throws FirebaseMessagingException { - return firebaseMessaging.subscribeToTopic(tokens, topic); + ) throws FirebaseSubscribeException { + try { + TopicManagementResponse response = firebaseMessaging.subscribeToTopic(tokens, topic); + + if (response.getFailureCount() > 0) { + throw new FirebaseSubscribeException(); + } + } catch (FirebaseMessagingException | FirebaseSubscribeException exception) { + throw new FirebaseSubscribeException(); + } } @Override - public TopicManagementResponse unsubscribeFromTopic( + public void unsubscribeFromTopic( List tokens, String topic - ) throws FirebaseMessagingException { - return firebaseMessaging.unsubscribeFromTopic(tokens, topic); + ) throws FirebaseUnSubscribeException { + try { + TopicManagementResponse response = firebaseMessaging.unsubscribeFromTopic(tokens, topic); + + if (response.getFailureCount() > 0) { + throw new FirebaseUnSubscribeException(); + } + } catch (FirebaseMessagingException | FirebaseUnSubscribeException exception) { + throw new FirebaseUnSubscribeException(); + } } @Override - public String send(Message message) throws FirebaseMessagingException { - return firebaseMessaging.send(message); + public void send(Message message) throws FirebaseMessagingException { + firebaseMessaging.send(message); } } diff --git a/src/main/java/com/kustacks/kuring/message/application/port/out/FirebaseAuthPort.java b/src/main/java/com/kustacks/kuring/message/application/port/out/FirebaseAuthPort.java index 158aaa73..a3c5e828 100644 --- a/src/main/java/com/kustacks/kuring/message/application/port/out/FirebaseAuthPort.java +++ b/src/main/java/com/kustacks/kuring/message/application/port/out/FirebaseAuthPort.java @@ -1,8 +1,7 @@ package com.kustacks.kuring.message.application.port.out; -import com.google.firebase.auth.FirebaseAuthException; -import com.google.firebase.auth.FirebaseToken; +import com.kustacks.kuring.message.application.service.exception.FirebaseInvalidTokenException; public interface FirebaseAuthPort { - FirebaseToken verifyIdToken(String idToken) throws FirebaseAuthException; + void verifyIdToken(String idToken) throws FirebaseInvalidTokenException; } diff --git a/src/main/java/com/kustacks/kuring/message/application/port/out/FirebaseMessagingPort.java b/src/main/java/com/kustacks/kuring/message/application/port/out/FirebaseMessagingPort.java index 5599df91..9ad47be1 100644 --- a/src/main/java/com/kustacks/kuring/message/application/port/out/FirebaseMessagingPort.java +++ b/src/main/java/com/kustacks/kuring/message/application/port/out/FirebaseMessagingPort.java @@ -4,5 +4,5 @@ import com.google.firebase.messaging.Message; public interface FirebaseMessagingPort { - String send(Message message) throws FirebaseMessagingException; + void send(Message message) throws FirebaseMessagingException; } diff --git a/src/main/java/com/kustacks/kuring/message/application/port/out/FirebaseSubscribePort.java b/src/main/java/com/kustacks/kuring/message/application/port/out/FirebaseSubscribePort.java index 3b499d13..e32be8a1 100644 --- a/src/main/java/com/kustacks/kuring/message/application/port/out/FirebaseSubscribePort.java +++ b/src/main/java/com/kustacks/kuring/message/application/port/out/FirebaseSubscribePort.java @@ -1,11 +1,11 @@ package com.kustacks.kuring.message.application.port.out; -import com.google.firebase.messaging.FirebaseMessagingException; -import com.google.firebase.messaging.TopicManagementResponse; +import com.kustacks.kuring.message.application.service.exception.FirebaseSubscribeException; +import com.kustacks.kuring.message.application.service.exception.FirebaseUnSubscribeException; import java.util.List; public interface FirebaseSubscribePort { - TopicManagementResponse subscribeToTopic(List tokens, String topic) throws FirebaseMessagingException; - TopicManagementResponse unsubscribeFromTopic(List tokens, String topic) throws FirebaseMessagingException; + void subscribeToTopic(List tokens, String topic) throws FirebaseSubscribeException; + void unsubscribeFromTopic(List tokens, String topic) throws FirebaseUnSubscribeException; } diff --git a/src/main/java/com/kustacks/kuring/message/application/service/FirebaseSubscribeService.java b/src/main/java/com/kustacks/kuring/message/application/service/FirebaseSubscribeService.java index 8bbe457f..a9a12f33 100644 --- a/src/main/java/com/kustacks/kuring/message/application/service/FirebaseSubscribeService.java +++ b/src/main/java/com/kustacks/kuring/message/application/service/FirebaseSubscribeService.java @@ -2,7 +2,6 @@ import com.google.firebase.messaging.FirebaseMessagingException; import com.google.firebase.messaging.Message; -import com.google.firebase.messaging.TopicManagementResponse; import com.kustacks.kuring.common.annotation.UseCase; import com.kustacks.kuring.common.properties.ServerProperties; import com.kustacks.kuring.message.application.port.in.FirebaseWithUserUseCase; @@ -13,10 +12,11 @@ import com.kustacks.kuring.message.application.service.exception.FirebaseInvalidTokenException; import com.kustacks.kuring.message.application.service.exception.FirebaseSubscribeException; import com.kustacks.kuring.message.application.service.exception.FirebaseUnSubscribeException; -import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import java.util.List; + @Slf4j @UseCase @RequiredArgsConstructor @@ -41,16 +41,11 @@ public void validationToken(String token) throws FirebaseInvalidTokenException { @Override public void subscribe(UserSubscribeCommand command) throws FirebaseSubscribeException { try { - TopicManagementResponse response = firebaseSubscribePort - .subscribeToTopic( - List.of(command.token()), - serverProperties.ifDevThenAddSuffix(command.topic()) - ); - - if (response.getFailureCount() > 0) { - throw new FirebaseSubscribeException(); - } - } catch (FirebaseMessagingException | FirebaseSubscribeException exception) { + firebaseSubscribePort.subscribeToTopic( + List.of(command.token()), + serverProperties.ifDevThenAddSuffix(command.topic()) + ); + } catch (FirebaseSubscribeException exception) { throw new FirebaseSubscribeException(); } } @@ -58,13 +53,11 @@ public void subscribe(UserSubscribeCommand command) throws FirebaseSubscribeExce @Override public void unsubscribe(UserUnsubscribeCommand command) throws FirebaseUnSubscribeException { try { - TopicManagementResponse response = firebaseSubscribePort - .unsubscribeFromTopic(List.of(command.token()), serverProperties.ifDevThenAddSuffix(command.topic())); - - if (response.getFailureCount() > 0) { - throw new FirebaseUnSubscribeException(); - } - } catch (FirebaseMessagingException | FirebaseUnSubscribeException exception) { + firebaseSubscribePort.unsubscribeFromTopic( + List.of(command.token()), + serverProperties.ifDevThenAddSuffix(command.topic()) + ); + } catch (FirebaseUnSubscribeException exception) { throw new FirebaseUnSubscribeException(); } } diff --git a/src/main/java/com/kustacks/kuring/notice/adapter/out/persistence/NoticeQueryRepositoryImpl.java b/src/main/java/com/kustacks/kuring/notice/adapter/out/persistence/NoticeQueryRepositoryImpl.java index 05e50bf1..2540790b 100644 --- a/src/main/java/com/kustacks/kuring/notice/adapter/out/persistence/NoticeQueryRepositoryImpl.java +++ b/src/main/java/com/kustacks/kuring/notice/adapter/out/persistence/NoticeQueryRepositoryImpl.java @@ -9,8 +9,10 @@ import com.kustacks.kuring.user.application.port.out.dto.BookmarkDto; import com.kustacks.kuring.user.application.port.out.dto.QBookmarkDto; import com.querydsl.core.BooleanBuilder; +import com.querydsl.core.types.ConstantImpl; import com.querydsl.core.types.dsl.Expressions; import com.querydsl.core.types.dsl.NumberTemplate; +import com.querydsl.core.types.dsl.StringTemplate; import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Pageable; @@ -24,34 +26,55 @@ @RequiredArgsConstructor class NoticeQueryRepositoryImpl implements NoticeQueryRepository { + private static final String DATE_TIME_TEMPLATE = "%Y-%m-%d %H:%i:%s"; + private final JPAQueryFactory queryFactory; @Transactional(readOnly = true) @Override public List findNoticesByCategoryWithOffset(CategoryName categoryName, Pageable pageable) { + StringTemplate postedDate = Expressions.stringTemplate( + "DATE_FORMAT({0}, {1})", + notice.noticeDateTime.postedDate, + ConstantImpl.create(DATE_TIME_TEMPLATE) + ); + return queryFactory - .select(new QNoticeDto(notice.articleId, notice.postedDate, notice.url.value, notice.subject, notice.categoryName.stringValue().toLowerCase(), notice.important)) - .from(notice) + .select( + new QNoticeDto( + notice.articleId, + postedDate, + notice.url.value, + notice.subject, + notice.categoryName.stringValue().toLowerCase(), + notice.important) + ).from(notice) .where(notice.categoryName.eq(categoryName)) .offset(pageable.getOffset()) .limit(pageable.getPageSize()) - .orderBy(notice.postedDate.desc()) + .orderBy(notice.noticeDateTime.postedDate.desc()) .fetch(); } @Transactional(readOnly = true) @Override public List findAllByKeywords(List keywords) { + StringTemplate postedDate = Expressions.stringTemplate( + "DATE_FORMAT({0}, {1})", + notice.noticeDateTime.postedDate, + ConstantImpl.create(DATE_TIME_TEMPLATE) + ); + return queryFactory .select(new QNoticeSearchDto( notice.articleId, - notice.postedDate, + postedDate, notice.subject, notice.categoryName.stringValue().toLowerCase(), notice.url.value)) .from(notice) .where(isContainSubject(keywords).or(isContainCategory(keywords))) - .orderBy(notice.postedDate.desc()) + .orderBy(notice.noticeDateTime.postedDate.desc()) .fetch(); } @@ -69,7 +92,7 @@ public List findNormalArticleIdsByCategory(CategoryName categoryName) { @Transactional @Override public void deleteAllByIdsAndCategory(CategoryName categoryName, List articleIds) { - if(articleIds.isEmpty()) { + if (articleIds.isEmpty()) { return; } @@ -131,10 +154,16 @@ public List findNormalArticleIdsByDepartment(DepartmentName departmentN @Transactional(readOnly = true) @Override public List findImportantNoticesByDepartment(DepartmentName departmentName) { + StringTemplate postedDate = Expressions.stringTemplate( + "DATE_FORMAT({0}, {1})", + departmentNotice.noticeDateTime.postedDate, + ConstantImpl.create(DATE_TIME_TEMPLATE) + ); + return queryFactory .select(new QNoticeDto( departmentNotice.articleId, - departmentNotice.postedDate, + postedDate, departmentNotice.url.value, departmentNotice.subject, departmentNotice.categoryName.stringValue().toLowerCase(), @@ -142,17 +171,23 @@ public List findImportantNoticesByDepartment(DepartmentName departmen .from(departmentNotice) .where(departmentNotice.departmentName.eq(departmentName) .and(departmentNotice.important.isTrue())) - .orderBy(departmentNotice.postedDate.desc()) + .orderBy(departmentNotice.noticeDateTime.postedDate.desc()) .fetch(); } @Transactional(readOnly = true) @Override public List findNormalNoticesByDepartmentWithOffset(DepartmentName departmentName, Pageable pageable) { + StringTemplate postedDate = Expressions.stringTemplate( + "DATE_FORMAT({0}, {1})", + departmentNotice.noticeDateTime.postedDate, + ConstantImpl.create(DATE_TIME_TEMPLATE) + ); + return queryFactory .select(new QNoticeDto( departmentNotice.articleId, - departmentNotice.postedDate, + postedDate, departmentNotice.url.value, departmentNotice.subject, departmentNotice.categoryName.stringValue().toLowerCase(), @@ -162,14 +197,14 @@ public List findNormalNoticesByDepartmentWithOffset(DepartmentName de .and(departmentNotice.important.isFalse())) .offset(pageable.getOffset()) .limit(pageable.getPageSize()) - .orderBy(departmentNotice.postedDate.desc()) + .orderBy(departmentNotice.noticeDateTime.postedDate.desc()) .fetch(); } @Transactional @Override public void deleteAllByIdsAndDepartment(DepartmentName departmentName, List articleIds) { - if(articleIds.isEmpty()) { + if (articleIds.isEmpty()) { return; } @@ -183,24 +218,36 @@ public void deleteAllByIdsAndDepartment(DepartmentName departmentName, List findAllByBookmarkIds(List ids) { + StringTemplate postedDate = Expressions.stringTemplate( + "DATE_FORMAT({0}, {1})", + notice.noticeDateTime.postedDate, + ConstantImpl.create(DATE_TIME_TEMPLATE) + ); + return queryFactory.select( new QBookmarkDto( notice.articleId, - notice.postedDate, + postedDate, notice.subject, notice.categoryName.stringValue(), notice.url.value ) ).from(notice) .where(notice.articleId.in(ids)) - .orderBy(notice.postedDate.desc()) + .orderBy(notice.noticeDateTime.postedDate.desc()) .fetch(); } private static BooleanBuilder isContainSubject(List keywords) { BooleanBuilder booleanBuilder = new BooleanBuilder(); for (String containedName : keywords) { - NumberTemplate booleanTemplate = Expressions.numberTemplate(Double.class, "function('match',{0},{1})", notice.subject, "*" + containedName + "*"); + NumberTemplate booleanTemplate = + Expressions.numberTemplate( + Double.class, + "function('match',{0},{1})", + notice.subject, + "*" + containedName + "*" + ); booleanBuilder.or(booleanTemplate.gt(0)); } diff --git a/src/main/java/com/kustacks/kuring/notice/domain/CategoryName.java b/src/main/java/com/kustacks/kuring/notice/domain/CategoryName.java index fb0bfafc..aeb2128f 100644 --- a/src/main/java/com/kustacks/kuring/notice/domain/CategoryName.java +++ b/src/main/java/com/kustacks/kuring/notice/domain/CategoryName.java @@ -16,12 +16,12 @@ public enum CategoryName { BACHELOR("bachelor", "bch", "학사"), SCHOLARSHIP("scholarship", "sch", "장학"), + LIBRARY("library", "lib", "도서관"), EMPLOYMENT("employment", "emp", "취창업"), NATIONAL("national", "nat", "국제"), STUDENT("student", "stu", "학생"), INDUSTRY_UNIVERSITY("industry_university", "ind", "산학"), NORMAL("normal", "nor", "일반"), - LIBRARY("library", "lib", "도서관"), DEPARTMENT("department", "dep", "학과"); private static final Map NAME_MAP; diff --git a/src/main/java/com/kustacks/kuring/notice/domain/Notice.java b/src/main/java/com/kustacks/kuring/notice/domain/Notice.java index 6145dc7c..72849608 100644 --- a/src/main/java/com/kustacks/kuring/notice/domain/Notice.java +++ b/src/main/java/com/kustacks/kuring/notice/domain/Notice.java @@ -21,14 +21,6 @@ public class Notice { @Column(name = "article_id", length = 32, nullable = false) private String articleId; - @Getter(AccessLevel.PUBLIC) - @Column(name = "posted_dt", length = 32, nullable = false) - private String postedDate; - - @Getter(AccessLevel.PUBLIC) - @Column(name = "updated_dt", length = 32) - private String updatedDate; - @Getter(AccessLevel.PUBLIC) @Column(name = "subject", length = 128, nullable = false) private String subject; @@ -36,6 +28,9 @@ public class Notice { @Column(name = "important") private Boolean important = false; + @Embedded + protected NoticeDateTime noticeDateTime; + @Embedded private Url url; @@ -48,17 +43,17 @@ public Notice(String articleId, String postedDate, String updatedDate, String fullUrl) { this.articleId = articleId; - this.postedDate = postedDate; - this.updatedDate = updatedDate; this.subject = subject; this.categoryName = categoryName; this.important = important; + this.noticeDateTime = new NoticeDateTime(postedDate, updatedDate); this.url = new Url(fullUrl); } public boolean isImportant() { return this.important; } + public String getCategoryName() { return this.categoryName.getName(); } @@ -71,6 +66,14 @@ public String getUrl() { return this.url.getValue(); } + public String getPostedDate() { + return this.noticeDateTime.postedDateStr(); + } + + public String getUpdatedDate() { + return this.noticeDateTime.updatedDateStr(); + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/src/main/java/com/kustacks/kuring/notice/domain/NoticeDateTime.java b/src/main/java/com/kustacks/kuring/notice/domain/NoticeDateTime.java new file mode 100644 index 00000000..be63bd36 --- /dev/null +++ b/src/main/java/com/kustacks/kuring/notice/domain/NoticeDateTime.java @@ -0,0 +1,102 @@ +package com.kustacks.kuring.notice.domain; + +import com.kustacks.kuring.common.exception.InternalLogicException; +import com.kustacks.kuring.common.exception.code.ErrorCode; +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.util.Locale; +import java.util.regex.Pattern; + +@Embeddable +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class NoticeDateTime { + + private static final String REGEX_DATE = "^\\d{4}-\\d{2}-\\d{2}$"; + private static final String REGEX_DATE_DOT_SPLIT = "^\\d{4}\\.\\d{2}\\.\\d{2}$"; + private static final String REGEX_DATE_TIME = "^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}$"; + private static final Pattern compiledDatePattern = Pattern.compile(REGEX_DATE); + private static final Pattern compiledDateDotPattern = Pattern.compile(REGEX_DATE_DOT_SPLIT); + private static final Pattern compiledDateTimePattern = Pattern.compile(REGEX_DATE_TIME); + private static final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withLocale(Locale.KOREA); + private static final DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern(" HH:mm:ss").withLocale(Locale.KOREA); + + @Getter(AccessLevel.PUBLIC) + @Column(name = "posted_dt", length = 32, nullable = false) + private LocalDateTime postedDate; + + @Getter(AccessLevel.PUBLIC) + @Column(name = "updated_dt", length = 32) + private LocalDateTime updatedDate; + + public NoticeDateTime(String postedDate, String updatedDate) { + if (postedDate == null || postedDate.isBlank()) { + postedDate = LocalDateTime.now().format(dateTimeFormatter); + } + + if (updatedDate == null || updatedDate.isBlank()) { + updatedDate = postedDate; + } + + postedDate = postedDate.trim(); + updatedDate = updatedDate.trim(); + + if (isValidDateDot(postedDate, updatedDate)) { + initDate(postedDate.replaceAll("[.]", "-"), updatedDate.replaceAll("[.]", "-")); + return; + } + + if (isValidDate(postedDate, updatedDate)) { + initDate(postedDate, updatedDate); + return; + } + + if (isValidDateTime(postedDate, updatedDate)) { + initDateTime(postedDate, updatedDate); + return; + } + + throw new InternalLogicException(ErrorCode.DOMAIN_CANNOT_CREATE); + } + + public String postedDateStr() { + return this.postedDate.format(dateTimeFormatter); + } + + public String updatedDateStr() { + return this.updatedDate.format(dateTimeFormatter); + } + + private void initDateTime(String postedDate, String updatedDate) { + this.postedDate = LocalDateTime.parse(postedDate, dateTimeFormatter); + this.updatedDate = LocalDateTime.parse(updatedDate, dateTimeFormatter); + } + + private void initDate(String postedDate, String updatedDate) { + LocalTime now = LocalTime.now(); + postedDate += now.format(timeFormatter); + updatedDate += now.format(timeFormatter); + initDateTime(postedDate, updatedDate); + } + + private boolean isValidDateDot(String postedDate, String updatedDate) { + return compiledDateDotPattern.matcher(postedDate).matches() && + compiledDateDotPattern.matcher(updatedDate).matches(); + } + + private boolean isValidDate(String postedDate, String updatedDate) { + return compiledDatePattern.matcher(postedDate).matches() && + compiledDatePattern.matcher(updatedDate).matches(); + } + + private boolean isValidDateTime(String postedDate, String updatedDate) { + return compiledDateTimePattern.matcher(postedDate).matches() && + compiledDateTimePattern.matcher(updatedDate).matches(); + } +} diff --git a/src/main/java/com/kustacks/kuring/worker/parser/notice/LatestPageNoticeHtmlParser.java b/src/main/java/com/kustacks/kuring/worker/parser/notice/LatestPageNoticeHtmlParser.java index 51340121..9f9127fc 100644 --- a/src/main/java/com/kustacks/kuring/worker/parser/notice/LatestPageNoticeHtmlParser.java +++ b/src/main/java/com/kustacks/kuring/worker/parser/notice/LatestPageNoticeHtmlParser.java @@ -30,7 +30,12 @@ protected String[] extractNoticeFromRow(Element row) { Elements tds = row.getElementsByTag("td"); // articleId, postedDate, subject - String number = tds.get(1).select("a").attr("onclick").replaceAll("[^0-9]", "").substring(3); + String[] splitResults = tds.get(1).select("a").attr("onclick") + .replace("jf_viewArtcl('", "") + .replace("')", "") + .split("', '"); + + String number = splitResults[2]; String date = tds.get(3).text(); String title = tds.get(1).select("strong").text(); diff --git a/src/main/java/com/kustacks/kuring/worker/parser/notice/LatestPageNoticeHtmlParserTwo.java b/src/main/java/com/kustacks/kuring/worker/parser/notice/LatestPageNoticeHtmlParserTwo.java deleted file mode 100644 index e824c510..00000000 --- a/src/main/java/com/kustacks/kuring/worker/parser/notice/LatestPageNoticeHtmlParserTwo.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.kustacks.kuring.worker.parser.notice; - -import com.kustacks.kuring.worker.scrap.deptinfo.DeptInfo; -import com.kustacks.kuring.worker.scrap.deptinfo.real_estate.RealEstateDept; -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; -import org.jsoup.select.Elements; -import org.springframework.stereotype.Component; - -@Component -public class LatestPageNoticeHtmlParserTwo extends NoticeHtmlParserTemplate { - - @Override - public boolean support(DeptInfo deptInfo) { - return !(deptInfo instanceof RealEstateDept); - } - - @Override - protected Elements selectImportantRows(Document document) { - return document.select(".notice"); - } - - @Override - protected Elements selectNormalRows(Document document) { - return document.select(".list_item").not(".notice").not(".thead"); - } - - @Override - protected String[] extractNoticeFromRow(Element row) { - String[] oneNoticeInfo = new String[3]; - Element subjectAndIdElement = row.selectFirst(".subject a"); - Element postedDateElement = row.selectFirst(".subject_info > .list_date > span"); - - oneNoticeInfo[0] = subjectAndIdElement.attr("data-itsp-view-link"); // articleId - oneNoticeInfo[1] = postedDateElement.ownText(); // postedDate - oneNoticeInfo[2] = subjectAndIdElement.ownText(); // subject - return oneNoticeInfo; - } -} diff --git a/src/main/java/com/kustacks/kuring/worker/scrap/deptinfo/social_science/InterDisciplinaryStudiesDept.java b/src/main/java/com/kustacks/kuring/worker/scrap/deptinfo/social_science/InterDisciplinaryStudiesDept.java index d1656399..26a748f2 100644 --- a/src/main/java/com/kustacks/kuring/worker/scrap/deptinfo/social_science/InterDisciplinaryStudiesDept.java +++ b/src/main/java/com/kustacks/kuring/worker/scrap/deptinfo/social_science/InterDisciplinaryStudiesDept.java @@ -24,9 +24,9 @@ public InterDisciplinaryStudiesDept( this.htmlParser = latestPageNoticeHtmlParser; this.latestPageNoticeProperties = latestPageNoticeProperties; - List professorForumIds = List.of("0"); + List professorForumIds = List.of("10430"); this.staffScrapInfo = new StaffScrapInfo(professorForumIds); - this.noticeScrapInfo = new NoticeScrapInfo(DISCI_STUDIES.getHostPrefix(), 0); + this.noticeScrapInfo = new NoticeScrapInfo(DISCI_STUDIES.getHostPrefix(), 433); this.departmentName = DISCI_STUDIES; } } diff --git a/src/main/java/com/kustacks/kuring/worker/scrap/deptinfo/social_science/PublicAdministrationDept.java b/src/main/java/com/kustacks/kuring/worker/scrap/deptinfo/social_science/PublicAdministrationDept.java index e62e90b3..a386df2a 100644 --- a/src/main/java/com/kustacks/kuring/worker/scrap/deptinfo/social_science/PublicAdministrationDept.java +++ b/src/main/java/com/kustacks/kuring/worker/scrap/deptinfo/social_science/PublicAdministrationDept.java @@ -24,9 +24,9 @@ public PublicAdministrationDept( this.htmlParser = latestPageNoticeHtmlParser; this.latestPageNoticeProperties = latestPageNoticeProperties; - List professorForumIds = List.of("2507"); + List professorForumIds = List.of("10264"); this.staffScrapInfo = new StaffScrapInfo(professorForumIds); - this.noticeScrapInfo = new NoticeScrapInfo(ADMINISTRATION.getHostPrefix(), 0); + this.noticeScrapInfo = new NoticeScrapInfo(ADMINISTRATION.getHostPrefix(), 855); this.departmentName = ADMINISTRATION; } } diff --git a/src/main/java/com/kustacks/kuring/worker/update/notice/KuisHomepageNoticeUpdater.java b/src/main/java/com/kustacks/kuring/worker/update/notice/KuisHomepageNoticeUpdater.java index 5a317031..1552e6ac 100644 --- a/src/main/java/com/kustacks/kuring/worker/update/notice/KuisHomepageNoticeUpdater.java +++ b/src/main/java/com/kustacks/kuring/worker/update/notice/KuisHomepageNoticeUpdater.java @@ -159,7 +159,6 @@ private void compareAllAndUpdateDB(List scrapResults, Ca private List saveNewNotices(List scrapResults, List savedArticleIds, CategoryName categoryName, boolean important) { List newNotices = noticeUpdateSupport.filteringSoonSaveNotices(scrapResults, savedArticleIds, categoryName, important); - Collections.reverse(newNotices); noticeCommandPort.saveAllCategoryNotices(newNotices); return newNotices; } diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml new file mode 100644 index 00000000..d587d814 --- /dev/null +++ b/src/main/resources/application-local.yml @@ -0,0 +1,103 @@ +server: + deploy: + environment: ${DEPLOY_ENV} + +spring: + jpa: + generate-ddl: false + hibernate: + ddl-auto: none + properties: + hibernate: + jdbc: + batch_size: 100 + format_sql: true + defer-datasource-initialization: false + show-sql: false + open-in-view: false + datasource: + hikari: + connectionTimeout: '30000' + maximum-pool-size: '8' + max-lifetime: '1800000' + password: ${DB_PASSWORD} + username: ${DB_USER} + url: jdbc:${DB_URL} + driver-class-name: org.mariadb.jdbc.Driver + flyway: + enabled: true + baseline-on-migrate: true + url: jdbc:${DB_URL} + user: ${DB_USER} + password: ${DB_PASSWORD} + jackson: + serialization: + FAIL_ON_EMPTY_BEANS: false + +logging: + level: + org: + apache: + coyote: + http11: info + file: + name: classpath:/kuring.log + +management: + endpoint: + health: + show-details: always + endpoints: + web: + exposure: + include: "*" + +security: + jwt: + token: + secret-key: localtestsecretkey + expire-length: 3600000 + + +notice: + kuis: + request-url: https://kuis.konkuk.ac.kr/CmmnOneStop/find.do + referer-url: https://kuis.konkuk.ac.kr/index.do + homepage: + list-url: https://www.konkuk.ac.kr/bbs/{category}/{siteId}/artclList.do + view-url: https://www.konkuk.ac.kr/bbs/{category}/{siteId}/{noticeId}/artclView.do + real-estate: + list-url: http://www.realestate.ac.kr/gb/bbs/board.php?bo_table=notice + view-url: http://www.realestate.ac.kr/gb/bbs/board.php?bo_table=notice + recent: + list-url: https://{department}.konkuk.ac.kr/bbs/{department}/{siteId}/artclList.do + view-url: https://{department}.konkuk.ac.kr/bbs/{department}/{siteId}/{noticeId}/artclView.do + normal-base-url: https://old.konkuk.ac.kr/do/MessageBoard/ArticleRead.do + library: + request-url: https://library.konkuk.ac.kr/pyxis-api/1/bulletin-boards/1/bulletins + library-base-url: https://library.konkuk.ac.kr/library-guide/bulletins/notice + +auth: + api-skeleton-producer-url: https://kuis.konkuk.ac.kr/ui/cpr-lib/user-modules.js?p=0.9460032500983822 + password: ${KU_PASSWORD} + user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36 Edg/94.0.992.38 + id: ${KU_ID} + session: JSESSIONID=00015GJS2T_gl7M-TqGjYvyHAuJ:-103KQM + referer: https://kuis.konkuk.ac.kr/index.do + login-url: https://kuis.konkuk.ac.kr/Login/login.do + +firebase: + file-path: third/ku-stack-firebase-adminsdk-87nwq-5ba04dfc12.json + +admin: + password: ${ADMIN_PASSWORD} + id: ${ADMIN_ID} + +ip: + proxy-list: 14.63.228.239:80, 101.79.15.198:80, 222.104.128.205:8678, 106.244.154.91:8080, 103.51.205.42:8181 + +staff: + real-estate-url: http://www.realestate.ac.kr/gb/bbs/board.php?bo_table=faculty + communication-design-url: http://www.konkuk.ac.kr/jsp/Coll/coll_01_13_01_01_tab01.jsp + living-design-url: http://www.konkuk.ac.kr/jsp/Coll/coll_01_13_01_05_tab01.jsp + each-dept-url: http://home.konkuk.ac.kr/cms/Common/Professor/ProfessorList.do diff --git a/src/main/resources/db/migration/V240327__Edit_post_date_time.sql b/src/main/resources/db/migration/V240327__Edit_post_date_time.sql new file mode 100644 index 00000000..ccfc8b2e --- /dev/null +++ b/src/main/resources/db/migration/V240327__Edit_post_date_time.sql @@ -0,0 +1,23 @@ +# notice에서 2022.07.26 의 형태를 2022-07-26 으로 변경한다 +UPDATE notice +SET posted_dt = REPLACE(posted_dt, '.', '-') +WHERE LENGTH(notice.posted_dt) = 10; + +UPDATE notice +SET updated_dt = REPLACE(updated_dt, '.', '-') +WHERE LENGTH(notice.updated_dt) = 10; + + +# notice에서 2022-07-26 의 형태에 시분초를 추가해준다 +UPDATE notice +SET notice.posted_dt = CONCAT(notice.posted_dt, ' 00:00:01') +WHERE LENGTH(notice.posted_dt) = 10; + +UPDATE notice +SET notice.updated_dt = CONCAT(notice.updated_dt, ' 00:00:01') +WHERE LENGTH(notice.updated_dt) = 10; + + +# notice 테이블의 posted_dt, updated_dt 컬럼의 타입을 DATETIME으로 변경한다 +ALTER TABLE notice MODIFY posted_dt DATETIME; +ALTER TABLE notice MODIFY updated_dt DATETIME; diff --git a/src/main/resources/db/migration/V240329__Delete_IND_DESIGN_AND_EDU_TECH_notices.sql b/src/main/resources/db/migration/V240329__Delete_IND_DESIGN_AND_EDU_TECH_notices.sql new file mode 100644 index 00000000..6e437e1a --- /dev/null +++ b/src/main/resources/db/migration/V240329__Delete_IND_DESIGN_AND_EDU_TECH_notices.sql @@ -0,0 +1,3 @@ +DELETE +FROM notice +WHERE department_name = 'EDU_TECH' OR department_name = 'IND_DESIGN'; diff --git a/src/test/java/com/kustacks/kuring/acceptance/CategoryAcceptanceTest.java b/src/test/java/com/kustacks/kuring/acceptance/CategoryAcceptanceTest.java index 122cca3b..7cf53402 100644 --- a/src/test/java/com/kustacks/kuring/acceptance/CategoryAcceptanceTest.java +++ b/src/test/java/com/kustacks/kuring/acceptance/CategoryAcceptanceTest.java @@ -34,8 +34,10 @@ void look_up_category_list() { var 카테고리_조회_요청_응답 = 지원하는_카테고리_조회_요청(); // then - 카테고리_조회_요청_응답_확인(카테고리_조회_요청_응답, "bachelor", "scholarship", "employment", "national", - "student", "industry_university", "normal", "library", "department"); + 카테고리_조회_요청_응답_확인(카테고리_조회_요청_응답, + "bachelor", "scholarship", "library", + "employment", "national", "student", + "industry_university", "normal", "department"); } diff --git a/src/test/java/com/kustacks/kuring/acceptance/NoticeAcceptanceTest.java b/src/test/java/com/kustacks/kuring/acceptance/NoticeAcceptanceTest.java index 1279cb91..7062dd5e 100644 --- a/src/test/java/com/kustacks/kuring/acceptance/NoticeAcceptanceTest.java +++ b/src/test/java/com/kustacks/kuring/acceptance/NoticeAcceptanceTest.java @@ -4,10 +4,12 @@ import com.kustacks.kuring.support.IntegrationTestSupport; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.springframework.http.HttpStatus; import static com.kustacks.kuring.acceptance.CategoryStep.지원하는_카테고리_조회_요청; -import static com.kustacks.kuring.acceptance.CategoryStep.카테고리_조회_요청_응답_확인; import static com.kustacks.kuring.acceptance.NoticeStep.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; @DisplayName("인수 : 공지사항") class NoticeAcceptanceTest extends IntegrationTestSupport { @@ -34,7 +36,20 @@ void look_up_support_university_category() { var 카테고리_조회_요청_응답 = 지원하는_카테고리_조회_요청(); // then - 카테고리_조회_요청_응답_확인(카테고리_조회_요청_응답, "student", "bachelor", "employment", "department", "library"); + assertAll( + () -> assertThat(카테고리_조회_요청_응답.statusCode()).isEqualTo(HttpStatus.OK.value()), + () -> assertThat(카테고리_조회_요청_응답.jsonPath().getInt("code")).isEqualTo(200), + () -> assertThat(카테고리_조회_요청_응답.jsonPath().getList("data.name")) + .containsExactly("bachelor", + "scholarship", + "library", + "employment", + "national", + "student", + "industry_university", + "normal", + "department") + ); } /** diff --git a/src/test/java/com/kustacks/kuring/notice/adapter/out/persistence/NoticeRepositoryTest.java b/src/test/java/com/kustacks/kuring/notice/adapter/out/persistence/NoticeRepositoryTest.java index 2966a37a..1b61200c 100644 --- a/src/test/java/com/kustacks/kuring/notice/adapter/out/persistence/NoticeRepositoryTest.java +++ b/src/test/java/com/kustacks/kuring/notice/adapter/out/persistence/NoticeRepositoryTest.java @@ -35,15 +35,15 @@ class NoticeRepositoryTest extends IntegrationTestSupport { @Test void lookupAllNoticeByIds() { // given - Notice notice1 = new Notice("1", "2024-01-19", "updatedDate", + Notice notice1 = new Notice("1", "2024-01-19 17:27:05", "2023-04-03 17:27:05", "notice1", CategoryName.BACHELOR, false, "https://www.example.com"); - Notice notice2 = new Notice("2", "2024-01-20", "updatedDate", + Notice notice2 = new Notice("2", "2024-01-20 17:27:05", "2023-04-03 17:27:05", "notice2", CategoryName.BACHELOR, false, "https://www.example.com"); noticeCommandPort.saveAllCategoryNotices(List.of(notice1, notice2)); - DepartmentNotice departmentNotice1 = new DepartmentNotice("3", "2024-01-22", "updatedDate", + DepartmentNotice departmentNotice1 = new DepartmentNotice("3", "2024-01-22 17:27:05", "2023-04-03 17:27:05", "departmentNotice1", CategoryName.DEPARTMENT, false, "https://www.example.com", DepartmentName.ADMINISTRATION); - DepartmentNotice departmentNotice2 = new DepartmentNotice("4", "2024-01-24", "updatedDate", + DepartmentNotice departmentNotice2 = new DepartmentNotice("4", "2024-01-24 17:27:05", "2023-04-03 17:27:05", "departmentNotice2", CategoryName.DEPARTMENT, false, "https://www.example.com", DepartmentName.ADMINISTRATION); noticeCommandPort.saveAllDepartmentNotices(List.of(departmentNotice1, departmentNotice2)); @@ -63,10 +63,10 @@ void lookupAllNoticeByIds() { assertThat(bookmarks).hasSize(4) .extracting("articleId", "postedDate", "subject") .containsExactly( - tuple("4", "2024-01-24", "departmentNotice2"), - tuple("3", "2024-01-22", "departmentNotice1"), - tuple("2", "2024-01-20", "notice2"), - tuple("1", "2024-01-19", "notice1") + tuple("4", "2024-01-24 17:27:05", "departmentNotice2"), + tuple("3", "2024-01-22 17:27:05", "departmentNotice1"), + tuple("2", "2024-01-20 17:27:05", "notice2"), + tuple("1", "2024-01-19 17:27:05", "notice1") ); } } diff --git a/src/test/java/com/kustacks/kuring/notice/domain/DepartmentNoticeTest.java b/src/test/java/com/kustacks/kuring/notice/domain/DepartmentNoticeTest.java index 93ce2fae..c4a0a2a1 100644 --- a/src/test/java/com/kustacks/kuring/notice/domain/DepartmentNoticeTest.java +++ b/src/test/java/com/kustacks/kuring/notice/domain/DepartmentNoticeTest.java @@ -20,7 +20,7 @@ class DepartmentNoticeTest { "https://library.konkuk.ac.kr/library-guide/bulletins/notice/7192", "http://www.konkuk.ac.kr/do/MessageBoard/ArticleRead.do?forum=notice&sort=6&id=5b50736&cat=0000300001", "http://mae.konkuk.ac.kr/noticeView.do?siteId=MAE&boardSeq=988&menuSeq=6823&categorySeq=0&curBoardDispType=LIST&curPage=12&pageNum=1&seq=179896"}) void create_member(String url) { - assertThatCode(() -> new DepartmentNotice("artice_id", "postDate", "updatedDate", "subject", CategoryName.DEPARTMENT, false, url, DepartmentName.BIOLOGICAL)) + assertThatCode(() -> new DepartmentNotice("artice_id", "2023-04-03 00:00:12", "2023-04-03 00:01:12", "subject", CategoryName.DEPARTMENT, false, url, DepartmentName.BIOLOGICAL)) .doesNotThrowAnyException(); } @@ -28,7 +28,7 @@ void create_member(String url) { @ParameterizedTest @ValueSource(strings = {"//www.example.com", "https:/www.example.com", "https://"}) void member_invalid_email_id(String url) { - assertThatThrownBy(() -> new DepartmentNotice("artice_id", "postDate", "updatedDate", "subject", CategoryName.DEPARTMENT, false, url, DepartmentName.BIOLOGICAL)) + assertThatThrownBy(() -> new DepartmentNotice("artice_id", "2023-04-03 00:00:12", "2023-04-03 00:01:12", "subject", CategoryName.DEPARTMENT, false, url, DepartmentName.BIOLOGICAL)) .isInstanceOf(InternalLogicException.class); } @@ -41,7 +41,7 @@ void notice_equals_test() { } private DepartmentNotice createDepartmentNotice(long id, String url) { - DepartmentNotice departmentNotice = new DepartmentNotice("artice_id", "postDate", "updatedDate", "subject", CategoryName.DEPARTMENT, false, url, DepartmentName.BIOLOGICAL); + DepartmentNotice departmentNotice = new DepartmentNotice("artice_id", "2023-04-03 00:00:12", "2023-04-03 00:01:12", "subject", CategoryName.DEPARTMENT, false, url, DepartmentName.BIOLOGICAL); ReflectionTestUtils.setField(departmentNotice, "id", id); return departmentNotice; } diff --git a/src/test/java/com/kustacks/kuring/notice/domain/NoticeDateTimeTest.java b/src/test/java/com/kustacks/kuring/notice/domain/NoticeDateTimeTest.java new file mode 100644 index 00000000..2931947a --- /dev/null +++ b/src/test/java/com/kustacks/kuring/notice/domain/NoticeDateTimeTest.java @@ -0,0 +1,111 @@ +package com.kustacks.kuring.notice.domain; + +import com.kustacks.kuring.common.exception.InternalLogicException; +import org.assertj.core.api.ThrowableAssert; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.NullAndEmptySource; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; + +class NoticeDateTimeTest { + + @DisplayName("날짜 시간이 있는 경우 yyyy-MM-dd HH:mm:ss 형태를 생성한다") + @Test + void date_time_test() { + // given + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + LocalDateTime dateTime = LocalDateTime.parse("2023-04-03 00:00:12", formatter); + + // when + NoticeDateTime postDateTime = + new NoticeDateTime("2023-04-03 00:00:12", "2023-04-03 00:00:12"); + + // then + assertAll( + () -> assertThat(dateTime.format(formatter)).isEqualTo(postDateTime.postedDateStr()), + () -> assertThat(dateTime.format(formatter)).isEqualTo(postDateTime.updatedDateStr()) + ); + } + + @DisplayName("날짜만 있는 경우에도 시간을 현재 시분초로 설정하여 yyyy-MM-dd HH:mm:ss 형태를 생성한다") + @Test + void date_test() { + // when + NoticeDateTime postDateTime = new NoticeDateTime("2023-04-03", "2023-04-03"); + + // then + assertAll( + () -> assertThat(postDateTime.postedDateStr()) + .containsPattern("^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}$"), + () -> assertThat(postDateTime.updatedDateStr()) + .containsPattern("^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}$") + ); + } + + @DisplayName("날짜만 yyyy.MM.dd 처럼 있는 경우에도 yyyy-MM-dd HH:mm:ss 형태를 생성한다") + @Test + void date_dot_test() { + // when + NoticeDateTime postDateTime = new NoticeDateTime("2023.04.03", "2023.04.03"); + + // then + assertAll( + () -> assertThat(postDateTime.postedDateStr()) + .containsPattern("^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}$"), + () -> assertThat(postDateTime.updatedDateStr()) + .containsPattern("^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}$") + ); + } + + @DisplayName("업데이트 날자가 공지에 없어 null인 경우에도 yyyy-MM-dd HH:mm:ss 형태를 생성한다") + @NullAndEmptySource + @ParameterizedTest + void date_both_null_test(String dateTime) { + // when + NoticeDateTime postDateTime = new NoticeDateTime(dateTime, dateTime); + + // then + assertAll( + () -> assertThat(postDateTime.postedDateStr()) + .containsPattern("^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}$"), + () -> assertThat(postDateTime.updatedDateStr()) + .containsPattern("^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}$") + ); + } + + @DisplayName("업데이트 날자가 공지에 없어 null인 경우에도 yyyy-MM-dd HH:mm:ss 형태를 생성한다") + @Test + void update_date_null_test() { + // when + NoticeDateTime postDateTime = new NoticeDateTime("2023.04.03", null); + + // then + assertAll( + () -> assertThat(postDateTime.postedDateStr()) + .containsPattern("^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}$"), + () -> assertThat(postDateTime.updatedDateStr()) + .containsPattern("^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}$") + ); + } + + @DisplayName("지정된 형식의 날짜 구조가 아닌 경우 예외가 발생한다") + @CsvSource({"2023:04:03", "2023-4-3", "04-03", "-04-03", + "2023-04-03 00:00", "2023-04-03 00", "2023-04-03 00:00:00:00", "00:00:00"}) + @ParameterizedTest + void fromName(String dateTime) { + // when + ThrowableAssert.ThrowingCallable actual = () -> new NoticeDateTime(dateTime, dateTime); + + // then + assertThatThrownBy(actual) + .isInstanceOf(InternalLogicException.class); + } +} diff --git a/src/test/java/com/kustacks/kuring/notice/domain/NoticeTest.java b/src/test/java/com/kustacks/kuring/notice/domain/NoticeTest.java index b992697a..3518a070 100644 --- a/src/test/java/com/kustacks/kuring/notice/domain/NoticeTest.java +++ b/src/test/java/com/kustacks/kuring/notice/domain/NoticeTest.java @@ -17,7 +17,7 @@ class NoticeTest { "https://library.konkuk.ac.kr/library-guide/bulletins/notice/7192", "http://www.konkuk.ac.kr/do/MessageBoard/ArticleRead.do?forum=notice&sort=6&id=5b50736&cat=0000300001", "http://mae.konkuk.ac.kr/noticeView.do?siteId=MAE&boardSeq=988&menuSeq=6823&categorySeq=0&curBoardDispType=LIST&curPage=12&pageNum=1&seq=179896"}) void create_member(String url) { - assertThatCode(() -> new Notice("artice_id", "postDate", "updatedDate", "subject", CategoryName.BACHELOR, false, url)) + assertThatCode(() -> new Notice("artice_id", "2024-01-19 17:27:05", "2024-01-19 17:27:05", "subject", CategoryName.BACHELOR, false, url)) .doesNotThrowAnyException(); } @@ -25,7 +25,7 @@ void create_member(String url) { @ParameterizedTest @ValueSource(strings = {"//www.example.com", "https:/www.example.com", "https://"}) void member_invalid_email_id(String url) { - assertThatThrownBy(() -> new Notice("artice_id", "postDate", "updatedDate", "subject", CategoryName.BACHELOR, false, url)) + assertThatThrownBy(() -> new Notice("artice_id", "2024-01-19 17:27:05", "2024-01-19 17:27:05", "subject", CategoryName.BACHELOR, false, url)) .isInstanceOf(InternalLogicException.class); } diff --git a/src/test/java/com/kustacks/kuring/support/DatabaseConfigurator.java b/src/test/java/com/kustacks/kuring/support/DatabaseConfigurator.java index 1658c96f..c0bf25cf 100644 --- a/src/test/java/com/kustacks/kuring/support/DatabaseConfigurator.java +++ b/src/test/java/com/kustacks/kuring/support/DatabaseConfigurator.java @@ -1,17 +1,18 @@ package com.kustacks.kuring.support; -import com.kustacks.kuring.admin.domain.Admin; import com.kustacks.kuring.admin.adapter.out.persistence.AdminRepository; +import com.kustacks.kuring.admin.domain.Admin; import com.kustacks.kuring.admin.domain.AdminRole; import com.kustacks.kuring.notice.adapter.out.persistence.NoticePersistenceAdapter; import com.kustacks.kuring.notice.domain.CategoryName; import com.kustacks.kuring.notice.domain.DepartmentName; import com.kustacks.kuring.notice.domain.DepartmentNotice; import com.kustacks.kuring.notice.domain.Notice; -import com.kustacks.kuring.staff.domain.Staff; import com.kustacks.kuring.staff.adapter.out.persistence.StaffRepository; +import com.kustacks.kuring.staff.domain.Staff; import com.kustacks.kuring.user.adapter.out.persistence.UserPersistenceAdapter; import com.kustacks.kuring.user.domain.User; +import jakarta.transaction.Transactional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; @@ -20,7 +21,6 @@ import org.springframework.stereotype.Component; import javax.sql.DataSource; -import jakarta.transaction.Transactional; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.ResultSet; @@ -182,21 +182,21 @@ private void initStaff() { private List buildImportantDepartmentNotice(int cnt, DepartmentName departmentName, CategoryName categoryName, boolean important) { return Stream.iterate(0, i -> i + 1) .limit(cnt) - .map(i -> new DepartmentNotice("depart_import_article_" + i, "post_date_" + i, "update_date_" + i, "subject_" + i, categoryName, important, "https://www.example.com", departmentName)) + .map(i -> new DepartmentNotice("depart_import_article_" + i, "2023-04-03 00:00:1"+i, "2023-04-03 00:00:1"+i, "subject_" + i, categoryName, important, "https://www.example.com", departmentName)) .toList(); } private List buildNormalDepartmentNotice(int cnt, DepartmentName departmentName, CategoryName categoryName, boolean important) { return Stream.iterate(0, i -> i + 1) .limit(cnt) - .map(i -> new DepartmentNotice("depart_normal_article_" + i, "post_date_" + i, "update_date_" + i, "subject_" + i, categoryName, important, "https://www.example.com", departmentName)) + .map(i -> new DepartmentNotice("depart_normal_article_" + i, "2023-04-03 00:00:1"+i, "2023-04-03 00:00:1"+i, "subject_" + i, categoryName, important, "https://www.example.com", departmentName)) .toList(); } private static List buildNotices(int cnt, CategoryName categoryName) { return Stream.iterate(0, i -> i + 1) .limit(cnt) - .map(i -> new Notice("article_" + i, "post_date_" + i, "update_date_" + i, "subject_" + i, categoryName, false, "https://www.example.com")) + .map(i -> new Notice("article_" + i, "2023-04-03 00:00:1"+i, "2023-04-03 00:00:1"+i, "subject_" + i, categoryName, false, "https://www.example.com")) .toList(); } diff --git a/src/test/java/com/kustacks/kuring/worker/parser/NoticeHtmlParserTemplateTest.java b/src/test/java/com/kustacks/kuring/worker/parser/NoticeHtmlParserTemplateTest.java index 1f3564d8..0e00f37b 100644 --- a/src/test/java/com/kustacks/kuring/worker/parser/NoticeHtmlParserTemplateTest.java +++ b/src/test/java/com/kustacks/kuring/worker/parser/NoticeHtmlParserTemplateTest.java @@ -51,7 +51,7 @@ void KuisHomepageNoticeHtmlParser() throws IOException { ); } - @DisplayName("오래된 학과의 홈페이지 공지를 분석한다") + @DisplayName("신규 개편된 학과의 홈페이지 공지를 분석한다") @Test void LatestPageNoticeHtmlParser() throws IOException { // given @@ -103,21 +103,33 @@ void LatestPageNoticeHtmlParser() throws IOException { ); } - @DisplayName("신규 개편된 학과의 홈페이지 공지를 분석한다") + @DisplayName("신규 2024 홈페이지의 학생 공지에서 noticeId가 성공적으로 분석되는지 확인한다") @Test - void LatestPageNoticeHtmlParserTwo() throws IOException { + void LatestPageNoticeHtmlParserNoticeId() throws IOException { // given - Document doc = Jsoup.parse(TestFileLoader.loadHtmlFile("src/test/resources/notice/kbeauty.html")); + Document doc = Jsoup.parse(TestFileLoader.loadHtmlFile("src/test/resources/notice/designid-notice-2024.html")); + String viewUrl = "https://www.konkuk.ac.kr/bbs/konkuk/4017/{noticeId}/artclView.do"; // when - RowsDto rowsDto = new LatestPageNoticeHtmlParserTwo().parse(doc); - List important = rowsDto.buildImportantRowList("important"); - List normal = rowsDto.buildNormalRowList("normal"); + RowsDto rowsDto = new LatestPageNoticeHtmlParser().parse(doc); + List important = rowsDto.buildImportantRowList(viewUrl); + List normal = rowsDto.buildNormalRowList(viewUrl); // then assertAll( - () -> assertThat(important).hasSize(28), - () -> assertThat(normal).hasSize(12) + () -> assertThat(important).hasSize(7), + () -> assertThat(important) + .extracting("articleId", "subject", "postedDate", "fullUrl", "important") + .containsExactlyInAnyOrder( + tuple("5643", "[~8월] 2024 Kyoto Global Design Awards", "2024.02.19", "https://www.konkuk.ac.kr/bbs/konkuk/4017/5643/artclView.do", true), + tuple("915679", "[-3.15(금)까지] 2024년 해외인턴지원사업 인턴 디자이너 모집(한국디자인진흥원)", "2024.02.06", "https://www.konkuk.ac.kr/bbs/konkuk/4017/915679/artclView.do", true), + tuple("915678", "[재학생 및 신입생] ★2024 건국대학교 가이드북★", "2024.01.22", "https://www.konkuk.ac.kr/bbs/konkuk/4017/915678/artclView.do", true), + tuple("915677", "산업디자인학과, 2023 졸업전시 한국디자인진흥원 온라인 특별 기획전 선정", "2023.12.19", "https://www.konkuk.ac.kr/bbs/konkuk/4017/915677/artclView.do", true), + tuple("915674", "2023학년도 1학기 기초교양(외국어영역) 교과목 의무이수 면제 시행 안내", "2023.04.06", "https://www.konkuk.ac.kr/bbs/konkuk/4017/915674/artclView.do", true), + tuple("915666", "휴·복학 개시 및 주의사항 안내", "2022.12.26", "https://www.konkuk.ac.kr/bbs/konkuk/4017/915666/artclView.do", true), + tuple("915663", "[학사팀] 재학생 졸업요건 관리를 위한 필수 확인사항 안내", "2022.11.07", "https://www.konkuk.ac.kr/bbs/konkuk/4017/915663/artclView.do", true) + ), + () -> assertThat(normal).hasSize(10) ); } diff --git a/src/test/resources/notice/designid-notice-2024.html b/src/test/resources/notice/designid-notice-2024.html new file mode 100644 index 00000000..9f4d8b5b --- /dev/null +++ b/src/test/resources/notice/designid-notice-2024.html @@ -0,0 +1,1136 @@ + + + + + + + + + + + 건국대학교 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + + + + + + + + +
+ +
+ + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
학부공지 - 번호, 제목, 작성자, 작성일, 조회수, 첨부파일
번호제목작성자작성일조회수첨부파일
+ + + 일반공지 + + + + + + [~8월] 2024 Kyoto Global Design Awards + + + + + kuids + + + 2024.02.19561 + + +

1

+ + + +
+ + + 일반공지 + + + + + + [-3.15(금)까지] 2024년 해외인턴지원사업 인턴 디자이너 모집(한국디자인진흥원) + + + + + kuids + + + 2024.02.06813 + + +

0

+ + + +
+ + + 일반공지 + + + + + + [재학생 및 신입생] ★2024 건국대학교 가이드북★ + + + + + kuids + + + 2024.01.22540 + + +

1

+ + + +
+ + + 일반공지 + + + + + + 산업디자인학과, 2023 졸업전시 한국디자인진흥원 온라인 특별 기획전 선정 + + + + + kuids + + + 2023.12.19664 + + +

0

+ + + +
+ + + 일반공지 + + + + + + 2023학년도 1학기 기초교양(외국어영역) 교과목 의무이수 면제 시행 안내 + + + + + kuids + + + 2023.04.06725 + + +

3

+ + + +
+ + + 일반공지 + + + + + + 휴·복학 개시 및 주의사항 안내 + + + + + kuids + + + 2022.12.26622 + + +

0

+ + + +
+ + + 일반공지 + + + + + + [학사팀] 재학생 졸업요건 관리를 위한 필수 확인사항 안내 + + + + + kuids + + + 2022.11.07426 + + +

3

+ + + +
115 + + + + + + + + + [~8월] 2024 Kyoto Global Design Awards + + + + + + + + + + kuids + + + 2024.02.19 + + + 561 + + + + + +

1

+ + +
114 + + + + + + + + + [-3.15(금)까지] 2024년 해외인턴지원사업 인턴 디자이너 모집(한국디자인진흥원) + + + + + + + + + + kuids + + + 2024.02.06 + + + 813 + + + + + +

0

+ + +
113 + + + + + + + + + [재학생 및 신입생] ★2024 건국대학교 가이드북★ + + + + + + + + + + kuids + + + 2024.01.22 + + + 540 + + + + + +

1

+ + +
112 + + + + + + + + + 산업디자인학과, 2023 졸업전시 한국디자인진흥원 온라인 특별 기획전 선정 + + + + + + + + + + kuids + + + 2023.12.19 + + + 664 + + + + + +

0

+ + +
111 + + + + + + + + + 2023학년도 2학기 수강바구니(수강신청) 일정 안내 + + + + + + + + + + kuids + + + 2023.07.19 + + + 623 + + + + + +

4

+ + +
110 + + + + + + + + + 2023학년도 하계 계절학기 수강정정(초과과목 신청) 안내 + + + + + + + + + + kuids + + + 2023.05.23 + + + 643 + + + + + +

2

+ + +
109 + + + + + + + + + 2023학년도 1학기 기초교양(외국어영역) 교과목 의무이수 면제 시행 안내 + + + + + + + + + + kuids + + + 2023.04.06 + + + 725 + + + + + +

3

+ + +
108 + + + + + + + + + 2023학년도 하계 계절학기 개설 계획 안내 + + + + + + + + + + kuids + + + 2023.04.03 + + + 682 + + + + + +

2

+ + +
107 + + + + + + + + + ​2023학년도 1학기 수강정정 및 초과과목 신청 기간·방법 안내 + + + + + + + + + + kuids + + + 2023.02.28 + + + 693 + + + + + +

2

+ + +
106 + + + + + + + + + 2023학년도 1학기 취득학점포기 일정 안내 + + + + + + + + + + kuids + + + 2023.02.01 + + + 517 + + + + + +

1

+ + +
+
+
+ +
+ + + + + + + + + +
+
+ 처음 +

112

+ + 다음 페이지 + 다음 + +
+
+ +
+ + +
+
+ +
+
+ +
+
+
+ + + + + + + + + diff --git a/src/test/resources/notice/kbeauty.html b/src/test/resources/notice/kbeauty.html deleted file mode 100644 index 5d4c6bf8..00000000 --- a/src/test/resources/notice/kbeauty.html +++ /dev/null @@ -1,688 +0,0 @@ - - - - - - - - - - - - - - - - - - K뷰티산업융합학과 - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- -
-
-
-
-
-
-
- -
-
-
-
    -
  • -
  • 커뮤니티
  • -
  • 공지사항
  • -
-
-
-

공지사항

-
-
-
-
-
-
-
-
-
게시판 검색 -
    -
  • -
  • -
  • -
-
-
-
-
    -
  • -
  • 100
  • -
  • -
-
-
-
-
- -
-
-
-
-
-
- 마지막 페이지 -
-
-
-
-
-
-
-
-
-
- -
-
- - - - - - - -
-
- - -
- -