From 8ff5e555db1630fd4a857836bd67053516bf8936 Mon Sep 17 00:00:00 2001 From: Jo Seonggyu Date: Fri, 22 Sep 2023 22:06:19 +0900 Subject: [PATCH] =?UTF-8?q?Develop=20->=20Main=20=EB=A8=B8=EC=A7=80=20(#16?= =?UTF-8?q?1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 공지사항 생성, 공지사항 읽기 기능 추가 (#1) * :sparkles: 패키지 및 엔티티 생성 * :sparkles: BaseTimeEntity 생성, PostEntity 기본 내용 작성 * :sparkles: PostController 생성 및 기본 내용 작성 * :sparkles: postService 생성 * :sparkles: Exceptions.kt 생성 * :sparkles: postDto 생성 * :sparkles: postRepository 생성 및 기본 내용 작성 * :green_heart: application.yaml 로컬 환경에서 작동하도록 설정 * feat: createPost 기능 생성 * refactor: 리뷰 주신 거 수정 * refactor: post -> notice 수정 등 * chore: .idea 디렉토리 삭제 * chore: PR 템플릿 생성 (#2) * feat: 로컬 db용 docker-compose 파일 추가 및 application.yaml 수정 (#4) * feat: 공지사항 수정, 삭제, 태그 기능 추가 (#3) * fix: ExceptionHandler 추가 * feat: updateNotice 추가, valid 추가 * feat: deleteNotice 추가 * feat: enrollTag 기능 추가, noticeTag 연관 엔티티 추가 * feat: 공지사항 작성할 때 태그 생성 및 수정 * fix: 로컬 db 없앰 * fix: pr 리뷰 수정 * fix: pr 리뷰 수정 * fix: noticeTag assign * feat: 구성원(교수) 생성 및 조회 API 구현 (#8) * feat: 교수 엔티티 및 DTO 설계 * feat: 교수 생성 및 조회 * Docs: Swagger 추가 (#7) * Docs: Add swagger dependency * Docs: Add basic config for swagger * Docs: Add basic configuration for swagger. * feat: 페이지네이션+검색 기능 추가 (#5) * feat: isPublic, isSlide, isPinned 추가 * feat: queryDsl 적용 위해 gradle 추가 fix: javax -> jakarta 변경 * feat: queryDsl 도입 * feat: 키워드+태그 검색 추가 * feat: search query에 isDeleted, isPublic 추가 및 isPinned 우선순위 설정 * fix: requestBody -> requestParam 수정 * feat: 페이지네이션 추가 * fix: 키워드 booleanBuilder 추가, application.yaml 수정 * fix: searchNotice readOnly 추가 * fix: SearchRequest 삭제 * fix: NoticeDto tags 추가 * fix: pr 리뷰 수정 / feat: 검색 기능 보강 및 수정 * fix:코드 수정 * fix: SearchResponse isPinned 추가 * fix: SearchResponse에 total 추가 * fix: 페이지 개수 수정 * fix: searchNotice queryDsl 오류 수정 * fix: local 설정 변경 * CICD: 배포 자동화 (#6) * CICD: Change expose port and added image tag * CICD: Change ddl-auto to create in prod profile for test * CICD: Added Deploy github action * CICD: Merge jobs to one job * Fix: Change checkout order to first step * CICD: Add context for docker build action * Fix: Change spring profile arg position * CICD: Change openjdk version to 17 * CICD: Change docker compose build image tag to latest * CICD: Change to use ghcr repository. * Fix: change list to string in docker push tags. * Fix: Change registry to ghcr.io * Fix: change env to pass to github action instead of ssh export command * Fix: unwrap bracket. * Fix: wrap Profile with "" * CICD: Add .env file * CICD: Change prod ddl-auto to create (for developing), and add TODO comment. * CICD: Remove cicd/deploy branch for condition. * feat: 구성원(교수) 수정 및 삭제 API (#9) * feat: 교수 조회시 최종학력이 앞으로 오게끔 정렬 * feat: 교수 수정 및 삭제 API * feat: 학력과 경력 엔티티 필드 변경, 수정 API 구현 * feat: 구성원(행정직원) CRUD API (#10) * feat: 행정직원 엔티티 및 DTO 설계 * feat: 행정직원 CRUD * feat: 교수 조회시 이름순 정렬 * fix: 교수 연구 분야 Set -> List 로 변경 * feat: 행정직원 주요업무 업데이트 구현 * feat: news 패키지 추가, 디벨롭 및 프론트에 맞게 엔티티 변경 (#12) * feat: news 패키지 생성 * feat: readNews 생성, news 패키지 추가로 인한 명칭 변경 * feat: createNews, enrollTag(새소식) 추가, news 패키지로 인한 명칭 추가 변경 * feat: updateNews, deleteNews 추가 * fix: searchNotice 관련 명칭 변경 * feat: searchNews 추가 * fix: develop 브랜치 반영, 프론트 요구사항 반영 * feat: readNotice, readNews에 이전글 다음글 추가 * 태그 업데이트 코드 리팩터링중 * refactor: 코드 수정, 이전제목 추가 * fix: 게시글 하나일때 read 가능 * fix: prevNext null 없애기 * fix: 이전글 다음글 null 수정 * fix: main에서 develop으로 pr (#16) * feat: merge develop to main (#13) * feat: 공지사항 생성, 공지사항 읽기 기능 추가 (#1) * :sparkles: 패키지 및 엔티티 생성 * :sparkles: BaseTimeEntity 생성, PostEntity 기본 내용 작성 * :sparkles: PostController 생성 및 기본 내용 작성 * :sparkles: postService 생성 * :sparkles: Exceptions.kt 생성 * :sparkles: postDto 생성 * :sparkles: postRepository 생성 및 기본 내용 작성 * :green_heart: application.yaml 로컬 환경에서 작동하도록 설정 * feat: createPost 기능 생성 * refactor: 리뷰 주신 거 수정 * refactor: post -> notice 수정 등 * chore: .idea 디렉토리 삭제 * chore: PR 템플릿 생성 (#2) * feat: 로컬 db용 docker-compose 파일 추가 및 application.yaml 수정 (#4) * feat: 공지사항 수정, 삭제, 태그 기능 추가 (#3) * fix: ExceptionHandler 추가 * feat: updateNotice 추가, valid 추가 * feat: deleteNotice 추가 * feat: enrollTag 기능 추가, noticeTag 연관 엔티티 추가 * feat: 공지사항 작성할 때 태그 생성 및 수정 * fix: 로컬 db 없앰 * fix: pr 리뷰 수정 * fix: pr 리뷰 수정 * fix: noticeTag assign * feat: 구성원(교수) 생성 및 조회 API 구현 (#8) * feat: 교수 엔티티 및 DTO 설계 * feat: 교수 생성 및 조회 * Docs: Swagger 추가 (#7) * Docs: Add swagger dependency * Docs: Add basic config for swagger * Docs: Add basic configuration for swagger. * feat: 페이지네이션+검색 기능 추가 (#5) * feat: isPublic, isSlide, isPinned 추가 * feat: queryDsl 적용 위해 gradle 추가 fix: javax -> jakarta 변경 * feat: queryDsl 도입 * feat: 키워드+태그 검색 추가 * feat: search query에 isDeleted, isPublic 추가 및 isPinned 우선순위 설정 * fix: requestBody -> requestParam 수정 * feat: 페이지네이션 추가 * fix: 키워드 booleanBuilder 추가, application.yaml 수정 * fix: searchNotice readOnly 추가 * fix: SearchRequest 삭제 * fix: NoticeDto tags 추가 * fix: pr 리뷰 수정 / feat: 검색 기능 보강 및 수정 * fix:코드 수정 * fix: SearchResponse isPinned 추가 * fix: SearchResponse에 total 추가 * fix: 페이지 개수 수정 * fix: searchNotice queryDsl 오류 수정 * fix: local 설정 변경 * CICD: 배포 자동화 (#6) * CICD: Change expose port and added image tag * CICD: Change ddl-auto to create in prod profile for test * CICD: Added Deploy github action * CICD: Merge jobs to one job * Fix: Change checkout order to first step * CICD: Add context for docker build action * Fix: Change spring profile arg position * CICD: Change openjdk version to 17 * CICD: Change docker compose build image tag to latest * CICD: Change to use ghcr repository. * Fix: change list to string in docker push tags. * Fix: Change registry to ghcr.io * Fix: change env to pass to github action instead of ssh export command * Fix: unwrap bracket. * Fix: wrap Profile with "" * CICD: Add .env file * CICD: Change prod ddl-auto to create (for developing), and add TODO comment. * CICD: Remove cicd/deploy branch for condition. * feat: 구성원(교수) 수정 및 삭제 API (#9) * feat: 교수 조회시 최종학력이 앞으로 오게끔 정렬 * feat: 교수 수정 및 삭제 API * feat: 학력과 경력 엔티티 필드 변경, 수정 API 구현 * feat: 구성원(행정직원) CRUD API (#10) * feat: 행정직원 엔티티 및 DTO 설계 * feat: 행정직원 CRUD * feat: 교수 조회시 이름순 정렬 * fix: 교수 연구 분야 Set -> List 로 변경 * feat: 행정직원 주요업무 업데이트 구현 * feat: news 패키지 추가, 디벨롭 및 프론트에 맞게 엔티티 변경 (#12) * feat: news 패키지 생성 * feat: readNews 생성, news 패키지 추가로 인한 명칭 변경 * feat: createNews, enrollTag(새소식) 추가, news 패키지로 인한 명칭 추가 변경 * feat: updateNews, deleteNews 추가 * fix: searchNotice 관련 명칭 변경 * feat: searchNews 추가 * fix: develop 브랜치 반영, 프론트 요구사항 반영 * feat: readNotice, readNews에 이전글 다음글 추가 * 태그 업데이트 코드 리팩터링중 * refactor: 코드 수정, 이전제목 추가 * fix: 게시글 하나일때 read 가능 * fix: prevNext null 없애기 * fix: 이전글 다음글 null 수정 --------- Co-authored-by: Jo Seonggyu Co-authored-by: 우혁준 (HyukJoon Woo) * hotfix: 사용하지않는 Dto 및 엔티티 삭제 (#14) --------- Co-authored-by: Junhyeong Kim Co-authored-by: 우혁준 (HyukJoon Woo) * feat: seminar 패키지 추가 (#17) * feat: createSeminar, readSeminar, updateSeminar 추가 * feat: deleteSeminar, searchSeminar 추가 * fix: distinct 삭제 * hotfix: 불필요한 dto 삭제 (#20) * hotfix: 불필요한 dto 삭제 * build.gradle 수정 * fix: 이미지 uri 필드 추가 및 프론트 요구사항 반영 (#21) * feat: 이미지 uri 필드 추가 및 isActive 대신 status 추가 * fix: 행정직원 전체 조회 응답에서 task 삭제 * fix: 교수진 페이지 응답 수정 * feat: introduction 패키지, undergraduate 패키지 추가 (#22) * feat: createUndergraduate, readUndergraduate 추가 * feat: readAllCourses, createCourse, readCourse 추가 * introduction 패키지 추가 * fix: dto에서 postType 삭제 * fix: postType pathVariable->requestBody 수정, 오타 수정 * fix: 프론트와 협의하여 이름 등 변경 * feat: academics 패키지 대학원도 가능하도록 추가 * feat: 장학제도 세부 장학금 create, read 추가 * fix: 수정 * fix:수정 * feat: admissions, research 패키지 추가 (#23) * feat: admissions-학부 에서 create, read 추가 * feat: admissions-대학원도 작성 가능하도록 추가 * feat: createResearch 추가 * feat: createLab 추가 * fix: 다른 패키지에 맞게 수정 * research-groups, research-centers에서 read, update 추가 * fix: admissions, research에서 프론트와 협의하여 이름 등 수정 * fix: 오타 수정 * fix: enum 추가, pr 리뷰 반영 * feat: oidc 로그인 (#27) * feat: oidc 로그인 * feat: TaskEntity name 추가 * feat: 배포 설정 * feat: 유저 정보에 학번 추가, 로그인 시 sub claim 확인 * feat: 배포 테스트 위해 redirect-uri 변경 * fix: groups claim 소문자로 수정 * feat: idsnucse 다운 되었을때 에러 처리 * feat: idsnucse 다운 되었을때 에러 처리 * feat: cors 설정 (#30) * fix: cors 추가 설정 (#32) * feat: cors 설정 * feat: cors 설정 * fix: CORS (#34) * feat: cors 설정 * feat: cors 설정 * feat: cors 설정 * fix: about, academics, admissions 패키지 수정 (#25) * fix: admissions, about 컨트롤러 변경 * fix: academics 컨트롤러 수정 * 커밋중 * fix: pr 리뷰 반영 * feat: readMain 추가 * pr 리뷰 반영 * feat: 일반 예약 및 정기 예약 API (#28) * feat: oidc 로그인 * feat: TaskEntity name 추가 * feat: 배포 설정 * feat: 유저 정보에 학번 추가, 로그인 시 sub claim 확인 * feat: 배포 테스트 위해 redirect-uri 변경 * fix: groups claim 소문자로 수정 * feat: 예약 엔티티 및 DTO 설계 * feat: 일반 예약 및 정기 예약 * feat: 예약 조회 API (#39) * feat: 권한 관리 위해 커스텀 어노테이션 생성 * feat: 예약 단건, 주별, 월별 조회 API * fix: 로컬 DB 설정 수정 * feat: 유저 조회 중복 쿼리 방지 * feat: 예약 응답에 recurrenceId 추가 * feat: 동시 예약 방지 * fix: 시큐리티 로그 다시 로컬에서만 보이도록 변경 * feat: 목데이터 날라가지 않도록 ddl-auto 수정 * feat: about, member, news, seminar 메인 이미지 업로드 추가 (#38) * feat: uploadImage 추가 * feat: about, member 사진 업로드 추가 * feat: news, seminar 사진 업로드 추가 * refactor: imageEntity 추가 리팩토링 * fix: gif 삭제 * fix: application.yaml 수정 * fix: newsService 태그 -> 이미지로 순서 변경 * fix: pr 리뷰 수정 * fix: extension 없애고, mainImage로 바꾸고, uuid 없애기 * fix: var 삭제 --------- Co-authored-by: Junhyeong Kim * CICD: Change deploy port to 8080 (#40) * [Merge] (#41) (#42) * feat: 공지사항 생성, 공지사항 읽기 기능 추가 (#1) * :sparkles: 패키지 및 엔티티 생성 * :sparkles: BaseTimeEntity 생성, PostEntity 기본 내용 작성 * :sparkles: PostController 생성 및 기본 내용 작성 * :sparkles: postService 생성 * :sparkles: Exceptions.kt 생성 * :sparkles: postDto 생성 * :sparkles: postRepository 생성 및 기본 내용 작성 * :green_heart: application.yaml 로컬 환경에서 작동하도록 설정 * feat: createPost 기능 생성 * refactor: 리뷰 주신 거 수정 * refactor: post -> notice 수정 등 * chore: .idea 디렉토리 삭제 * chore: PR 템플릿 생성 (#2) * feat: 로컬 db용 docker-compose 파일 추가 및 application.yaml 수정 (#4) * feat: 공지사항 수정, 삭제, 태그 기능 추가 (#3) * fix: ExceptionHandler 추가 * feat: updateNotice 추가, valid 추가 * feat: deleteNotice 추가 * feat: enrollTag 기능 추가, noticeTag 연관 엔티티 추가 * feat: 공지사항 작성할 때 태그 생성 및 수정 * fix: 로컬 db 없앰 * fix: pr 리뷰 수정 * fix: pr 리뷰 수정 * fix: noticeTag assign * feat: 구성원(교수) 생성 및 조회 API 구현 (#8) * feat: 교수 엔티티 및 DTO 설계 * feat: 교수 생성 및 조회 * Docs: Swagger 추가 (#7) * Docs: Add swagger dependency * Docs: Add basic config for swagger * Docs: Add basic configuration for swagger. * feat: 페이지네이션+검색 기능 추가 (#5) * feat: isPublic, isSlide, isPinned 추가 * feat: queryDsl 적용 위해 gradle 추가 fix: javax -> jakarta 변경 * feat: queryDsl 도입 * feat: 키워드+태그 검색 추가 * feat: search query에 isDeleted, isPublic 추가 및 isPinned 우선순위 설정 * fix: requestBody -> requestParam 수정 * feat: 페이지네이션 추가 * fix: 키워드 booleanBuilder 추가, application.yaml 수정 * fix: searchNotice readOnly 추가 * fix: SearchRequest 삭제 * fix: NoticeDto tags 추가 * fix: pr 리뷰 수정 / feat: 검색 기능 보강 및 수정 * fix:코드 수정 * fix: SearchResponse isPinned 추가 * fix: SearchResponse에 total 추가 * fix: 페이지 개수 수정 * fix: searchNotice queryDsl 오류 수정 * fix: local 설정 변경 * CICD: 배포 자동화 (#6) * CICD: Change expose port and added image tag * CICD: Change ddl-auto to create in prod profile for test * CICD: Added Deploy github action * CICD: Merge jobs to one job * Fix: Change checkout order to first step * CICD: Add context for docker build action * Fix: Change spring profile arg position * CICD: Change openjdk version to 17 * CICD: Change docker compose build image tag to latest * CICD: Change to use ghcr repository. * Fix: change list to string in docker push tags. * Fix: Change registry to ghcr.io * Fix: change env to pass to github action instead of ssh export command * Fix: unwrap bracket. * Fix: wrap Profile with "" * CICD: Add .env file * CICD: Change prod ddl-auto to create (for developing), and add TODO comment. * CICD: Remove cicd/deploy branch for condition. * feat: 구성원(교수) 수정 및 삭제 API (#9) * feat: 교수 조회시 최종학력이 앞으로 오게끔 정렬 * feat: 교수 수정 및 삭제 API * feat: 학력과 경력 엔티티 필드 변경, 수정 API 구현 * feat: 구성원(행정직원) CRUD API (#10) * feat: 행정직원 엔티티 및 DTO 설계 * feat: 행정직원 CRUD * feat: 교수 조회시 이름순 정렬 * fix: 교수 연구 분야 Set -> List 로 변경 * feat: 행정직원 주요업무 업데이트 구현 * feat: news 패키지 추가, 디벨롭 및 프론트에 맞게 엔티티 변경 (#12) * feat: news 패키지 생성 * feat: readNews 생성, news 패키지 추가로 인한 명칭 변경 * feat: createNews, enrollTag(새소식) 추가, news 패키지로 인한 명칭 추가 변경 * feat: updateNews, deleteNews 추가 * fix: searchNotice 관련 명칭 변경 * feat: searchNews 추가 * fix: develop 브랜치 반영, 프론트 요구사항 반영 * feat: readNotice, readNews에 이전글 다음글 추가 * 태그 업데이트 코드 리팩터링중 * refactor: 코드 수정, 이전제목 추가 * fix: 게시글 하나일때 read 가능 * fix: prevNext null 없애기 * fix: 이전글 다음글 null 수정 * fix: main에서 develop으로 pr (#16) * feat: merge develop to main (#13) * feat: 공지사항 생성, 공지사항 읽기 기능 추가 (#1) * :sparkles: 패키지 및 엔티티 생성 * :sparkles: BaseTimeEntity 생성, PostEntity 기본 내용 작성 * :sparkles: PostController 생성 및 기본 내용 작성 * :sparkles: postService 생성 * :sparkles: Exceptions.kt 생성 * :sparkles: postDto 생성 * :sparkles: postRepository 생성 및 기본 내용 작성 * :green_heart: application.yaml 로컬 환경에서 작동하도록 설정 * feat: createPost 기능 생성 * refactor: 리뷰 주신 거 수정 * refactor: post -> notice 수정 등 * chore: .idea 디렉토리 삭제 * chore: PR 템플릿 생성 (#2) * feat: 로컬 db용 docker-compose 파일 추가 및 application.yaml 수정 (#4) * feat: 공지사항 수정, 삭제, 태그 기능 추가 (#3) * fix: ExceptionHandler 추가 * feat: updateNotice 추가, valid 추가 * feat: deleteNotice 추가 * feat: enrollTag 기능 추가, noticeTag 연관 엔티티 추가 * feat: 공지사항 작성할 때 태그 생성 및 수정 * fix: 로컬 db 없앰 * fix: pr 리뷰 수정 * fix: pr 리뷰 수정 * fix: noticeTag assign * feat: 구성원(교수) 생성 및 조회 API 구현 (#8) * feat: 교수 엔티티 및 DTO 설계 * feat: 교수 생성 및 조회 * Docs: Swagger 추가 (#7) * Docs: Add swagger dependency * Docs: Add basic config for swagger * Docs: Add basic configuration for swagger. * feat: 페이지네이션+검색 기능 추가 (#5) * feat: isPublic, isSlide, isPinned 추가 * feat: queryDsl 적용 위해 gradle 추가 fix: javax -> jakarta 변경 * feat: queryDsl 도입 * feat: 키워드+태그 검색 추가 * feat: search query에 isDeleted, isPublic 추가 및 isPinned 우선순위 설정 * fix: requestBody -> requestParam 수정 * feat: 페이지네이션 추가 * fix: 키워드 booleanBuilder 추가, application.yaml 수정 * fix: searchNotice readOnly 추가 * fix: SearchRequest 삭제 * fix: NoticeDto tags 추가 * fix: pr 리뷰 수정 / feat: 검색 기능 보강 및 수정 * fix:코드 수정 * fix: SearchResponse isPinned 추가 * fix: SearchResponse에 total 추가 * fix: 페이지 개수 수정 * fix: searchNotice queryDsl 오류 수정 * fix: local 설정 변경 * CICD: 배포 자동화 (#6) * CICD: Change expose port and added image tag * CICD: Change ddl-auto to create in prod profile for test * CICD: Added Deploy github action * CICD: Merge jobs to one job * Fix: Change checkout order to first step * CICD: Add context for docker build action * Fix: Change spring profile arg position * CICD: Change openjdk version to 17 * CICD: Change docker compose build image tag to latest * CICD: Change to use ghcr repository. * Fix: change list to string in docker push tags. * Fix: Change registry to ghcr.io * Fix: change env to pass to github action instead of ssh export command * Fix: unwrap bracket. * Fix: wrap Profile with "" * CICD: Add .env file * CICD: Change prod ddl-auto to create (for developing), and add TODO comment. * CICD: Remove cicd/deploy branch for condition. * feat: 구성원(교수) 수정 및 삭제 API (#9) * feat: 교수 조회시 최종학력이 앞으로 오게끔 정렬 * feat: 교수 수정 및 삭제 API * feat: 학력과 경력 엔티티 필드 변경, 수정 API 구현 * feat: 구성원(행정직원) CRUD API (#10) * feat: 행정직원 엔티티 및 DTO 설계 * feat: 행정직원 CRUD * feat: 교수 조회시 이름순 정렬 * fix: 교수 연구 분야 Set -> List 로 변경 * feat: 행정직원 주요업무 업데이트 구현 * feat: news 패키지 추가, 디벨롭 및 프론트에 맞게 엔티티 변경 (#12) * feat: news 패키지 생성 * feat: readNews 생성, news 패키지 추가로 인한 명칭 변경 * feat: createNews, enrollTag(새소식) 추가, news 패키지로 인한 명칭 추가 변경 * feat: updateNews, deleteNews 추가 * fix: searchNotice 관련 명칭 변경 * feat: searchNews 추가 * fix: develop 브랜치 반영, 프론트 요구사항 반영 * feat: readNotice, readNews에 이전글 다음글 추가 * 태그 업데이트 코드 리팩터링중 * refactor: 코드 수정, 이전제목 추가 * fix: 게시글 하나일때 read 가능 * fix: prevNext null 없애기 * fix: 이전글 다음글 null 수정 --------- * hotfix: 사용하지않는 Dto 및 엔티티 삭제 (#14) --------- * feat: seminar 패키지 추가 (#17) * feat: createSeminar, readSeminar, updateSeminar 추가 * feat: deleteSeminar, searchSeminar 추가 * fix: distinct 삭제 * hotfix: 불필요한 dto 삭제 (#20) * hotfix: 불필요한 dto 삭제 * build.gradle 수정 * fix: 이미지 uri 필드 추가 및 프론트 요구사항 반영 (#21) * feat: 이미지 uri 필드 추가 및 isActive 대신 status 추가 * fix: 행정직원 전체 조회 응답에서 task 삭제 * fix: 교수진 페이지 응답 수정 * feat: introduction 패키지, undergraduate 패키지 추가 (#22) * feat: createUndergraduate, readUndergraduate 추가 * feat: readAllCourses, createCourse, readCourse 추가 * introduction 패키지 추가 * fix: dto에서 postType 삭제 * fix: postType pathVariable->requestBody 수정, 오타 수정 * fix: 프론트와 협의하여 이름 등 변경 * feat: academics 패키지 대학원도 가능하도록 추가 * feat: 장학제도 세부 장학금 create, read 추가 * fix: 수정 * fix:수정 * feat: admissions, research 패키지 추가 (#23) * feat: admissions-학부 에서 create, read 추가 * feat: admissions-대학원도 작성 가능하도록 추가 * feat: createResearch 추가 * feat: createLab 추가 * fix: 다른 패키지에 맞게 수정 * research-groups, research-centers에서 read, update 추가 * fix: admissions, research에서 프론트와 협의하여 이름 등 수정 * fix: 오타 수정 * fix: enum 추가, pr 리뷰 반영 * feat: oidc 로그인 (#27) * feat: oidc 로그인 * feat: TaskEntity name 추가 * feat: 배포 설정 * feat: 유저 정보에 학번 추가, 로그인 시 sub claim 확인 * feat: 배포 테스트 위해 redirect-uri 변경 * fix: groups claim 소문자로 수정 * feat: idsnucse 다운 되었을때 에러 처리 * feat: idsnucse 다운 되었을때 에러 처리 * feat: cors 설정 (#30) * fix: cors 추가 설정 (#32) * feat: cors 설정 * feat: cors 설정 * fix: CORS (#34) * feat: cors 설정 * feat: cors 설정 * feat: cors 설정 * fix: about, academics, admissions 패키지 수정 (#25) * fix: admissions, about 컨트롤러 변경 * fix: academics 컨트롤러 수정 * 커밋중 * fix: pr 리뷰 반영 * feat: readMain 추가 * pr 리뷰 반영 * feat: 일반 예약 및 정기 예약 API (#28) * feat: oidc 로그인 * feat: TaskEntity name 추가 * feat: 배포 설정 * feat: 유저 정보에 학번 추가, 로그인 시 sub claim 확인 * feat: 배포 테스트 위해 redirect-uri 변경 * fix: groups claim 소문자로 수정 * feat: 예약 엔티티 및 DTO 설계 * feat: 일반 예약 및 정기 예약 * feat: 예약 조회 API (#39) * feat: 권한 관리 위해 커스텀 어노테이션 생성 * feat: 예약 단건, 주별, 월별 조회 API * fix: 로컬 DB 설정 수정 * feat: 유저 조회 중복 쿼리 방지 * feat: 예약 응답에 recurrenceId 추가 * feat: 동시 예약 방지 * fix: 시큐리티 로그 다시 로컬에서만 보이도록 변경 * feat: 목데이터 날라가지 않도록 ddl-auto 수정 * feat: about, member, news, seminar 메인 이미지 업로드 추가 (#38) * feat: uploadImage 추가 * feat: about, member 사진 업로드 추가 * feat: news, seminar 사진 업로드 추가 * refactor: imageEntity 추가 리팩토링 * fix: gif 삭제 * fix: application.yaml 수정 * fix: newsService 태그 -> 이미지로 순서 변경 * fix: pr 리뷰 수정 * fix: extension 없애고, mainImage로 바꾸고, uuid 없애기 * fix: var 삭제 --------- * CICD: Change deploy port to 8080 (#40) --------- Co-authored-by: 우혁준 (HyukJoon Woo) Co-authored-by: Jo Seonggyu * feat: 장학제도 GET API 및 장학제도 페이지 응답 수정 (#44) * feat: 장학제도 GET API * feat: 장학제도 메인페이지 GET API 응답 수정 * feat: custom metadata 설정 + 리다이렉트 엔드포인트 변경 (#45) * feat: yml 파일에 custom metadata endpoint 추가 * feat: 로그인/로그아웃 성공 리다이렉트 엔드포인트 변경 * feat: attachments 패키지 추가, news와 seminar request에 attachments 추가 (#43) * feat: attachments 추가, news request에 attachments 추가 * feat: seminar request에 attachments 추가 * fix: mappedBy 추가 + bringAttachments null 없앰 * fix: 경로 분리 * fix: isDeleted false로 수정 --------- Co-authored-by: Junhyeong Kim * feat: Top Conference List GET API (#47) * feat: Top Conference List GET API * feat: db에서 가져올때 정렬 * feat: 파일 서빙, 다운로드, 삭제 API (#48) * feat: 파일 서빙, 다운로드, 삭제 API * fix: extension 중복, 경로 수정 * feat: 공지사항 글쓴이 추가 (#49) * CI/CD: Https 적용 및 백엔드, 데이터베이스, 프록시 서버 배포 분리 (#50) * Config: Add gitignore for caddy files. * CICD: Add caddy config file * CICD: Seperate docker compose file for db and backend server. * CICD: Docker Compose file for caddy proxy server. * CICD: Add path condition for only act when source file has changed. * CICD: Add workflow for deploying database changes. * CICD: Add workflow for proxy server changes. * Refactor: Add /api/v1 in front of all endpoints (except swagger-ui) * Refactor: Add v1 to Main Image url. * Refactor: change backend, frontend, login redirect uri to get from env. * CICD: Add url to .env * Fix: Change job name of database workflow, fix indent level. * Fix: Remove duplicated restcontroller mapping. * Fix: Set reverse proxy for swagger-ui/* for swagger. * Docs: Set swagger to only scan /api/** * Fix: add caddy to reverse api-docs/* for springdoc. * Fix: Add login uris for reverse proxying to backend server. * CICD: Remove testing branch condition. * CICD: Add logout uri to reverse proxy to backend server. * CICD: Remove test branch * fix: 프론트랑 협의하여 내용 변경 + news, seminar에 image, attachments update 추가 (#51) * feat: attachments 추가, news request에 attachments 추가 * feat: seminar request에 attachments 추가 * feat: about request에 attachments 추가 * feat: academics request에 attachments 추가 * fix: isPinned 삭제 * fix: image -> mainImage 변경, about에 greetings 추가 * fix: news, seminar update 수정 * fix: 전체적으로 image -> mainImage 변경 * fix: research 패키지 프론트에 맞춰 협의 (#52) * fix: professor, staff에 uploadImage 추가 * fix: research에서 isPublic 제거, feat: lab에서 attachments 추가 * fix: attachments -> attachmentResponses 변경 * feat: LabProfessorResponse 추가 * fix: pdf를 list가 아닌 단일항목으로 수정 * feat: readLab 추가 * feat: ResearchLabReponse 추가 및 주석 삭제 * fix: researchDetail에 사진, 첨부파일 업로드 추가 * fix: pr 리뷰 수정 * fix: 오타 수정 * [Refactor] 로그인, file uri 배포 환경에 맞게 수정 (#53) * Refactor: Change mapping of file to append /api/v1 in front. * Refactor: Change login, logout redirect uri * fix: https 적용 관련 로그인 수정 (#56) * fix: notice 패키지 프론트에 맞게 협의 (#54) * fix: professor, staff에 uploadImage 추가 * fix: research에서 isPublic 제거, feat: lab에서 attachments 추가 * fix: attachments -> attachmentResponses 변경 * feat: LabProfessorResponse 추가 * fix: pdf를 list가 아닌 단일항목으로 수정 * feat: readLab 추가 * feat: ResearchLabReponse 추가 및 주석 삭제 * fix: researchDetail에 사진, 첨부파일 업로드 추가 * feat: notice에 attachments 추가 * fix: fix_with_front1 변경 사항에 맞게 수정 * feat: isImportant 추가 * feat: NoticeSearchDto에 hasAttachment 추가 * fix: update에서 attachmetnts가 null일 때 빈 목록 반환 * feat: 공지사항 선택 고정해제, 선택 삭제 추가 * fix: news, seminar에 isImportant 추가 * fix: pr 리뷰 수정 * fix: 수정했던거 다시 복구 * feat: 신임교수초빙 (#59) * feat: 행정실 권한 체크 API (#60) * [CICD] mainImage, attachment 경로 mount 설정 (#55) * CICD: Add mainImage, attachment directory for mounting * CICD: Add volumes to mount with server directory. (for attachment) * [Feat] 예전 url과 비슷하게 이전 파일(사진)들 접근할 수 있도록 controller 생성 (#58) * Config: Add oldFiles path for properties. * Feat: Add deprecated file controller for getting old files temporarily. * CICD: Add volume mounting for old files. * CICD: Add dir to mount for old files. * Refactor: Change api to /sites/default/files/{PATH} * CICD: Add reverse proxy for old file serving. * fix: 예약 dto에 지도교수, 반복 횟수 추가 (#61) * fix: 로그아웃 엔드포인트 변경 (#64) * fix: 로그아웃 엔드포인트 변경 * fix: 로그아웃 성공 리다이렉트 엔드포인트 변경 * [Fix] Notice, News Description TEXT type으로 변환 (#65) * Fix: Change notice and news entity description type as TEXT. * Fix: Change to mediumtext * fix: newsSearchResponse, seminarSearchResponse 수정 (#66) * fix: newsSearchResponse 수정 * fix: SeminarSearchResponse 수정 * fix: 오타수정 * fix: 파일 업로드 경로 통일 (#68) * fix: 갯수만 fetch하도록 최적화 (#70) * Fix: Change type of additionalNote column on SeminarEntity to "TEXT" (#71) * fix: academics 패키지 프론트에 맞춰 협의, admin 패키지 추가 (#69) * fix: professor, staff에 uploadImage 추가 * fix: research에서 isPublic 제거, feat: lab에서 attachments 추가 * fix: attachments -> attachmentResponses 변경 * feat: LabProfessorResponse 추가 * fix: pdf를 list가 아닌 단일항목으로 수정 * feat: readLab 추가 * feat: ResearchLabReponse 추가 및 주석 삭제 * fix: researchDetail에 사진, 첨부파일 업로드 추가 * feat: notice에 attachments 추가 * fix: fix_with_front1 변경 사항에 맞게 수정 * feat: isImportant 추가 * feat: NoticeSearchDto에 hasAttachment 추가 * fix: update에서 attachmetnts가 null일 때 빈 목록 반환 * feat: 공지사항 선택 고정해제, 선택 삭제 추가 * fix: news, seminar에 isImportant 추가 * fix: pr 리뷰 수정 * fix: 수정했던거 다시 복구 * fix: course response 수정 * feat: academics에서 연도별로 주는 response 추가 * feat: academics YearResponses 추가 * fix: createScholarship 합치기 * fix: scholarship 패키지의 내용을 academics 하위로 옮김 * feat: createScholarshipDetail 추가 * fix: 필수 교양과목 response 변경 * feat: admin 패키지에서 readAllSlides 추가 * feat: admin 패키지 readAllImportants 추가 * feat: admin 패키지 중요안내 추가 * Refactor: HTML로부터 텍스트 추출 함수 Utils로 이동 (#73) * Feat: Add cleanTextFromHtml. * Refactor: Remove private clean method, replace to Utils.cleanTextFromHtml. * Refactor: Remove unused imports. * Feat: Add plain text description for notice (#74) * Feat: Add cleanTextFromHtml. * Refactor: Remove private clean method, replace to Utils.cleanTextFromHtml. * Refactor: Remove unused imports. * Feat: Add plainTextDescription. * Feat: Add plainTextDescription in createNotice. * Feat: add updating plainTextDescription when updating description. * fix: admissions 패키지 프론트에 맞게 협의 (#76) * fix: admissions 프론트에 맞게 협의 * fix: seminarEntity time 삭제 * fix: admissionsEntity pageName 추가 * fix: 불필요한 파일 삭제 * 커밋 * fix: 이전 글 다음 글만 fetch 하도록 쿼리 최적화 (#75) * fix: 이전 글 다음 글만 fetch 하도록 쿼리 최적화 * fix: distinct 수정 * fix: 쿼리 최적화 * fix: total Long 타입으로 수정 * CI/CD: Test 설정 (#77) * Config: Add kotest, mockk, h2 db for testing. * Config: Add test profile. * Config: Add testconfig for jpaqueryfactory. * CICD: Add build, test workflow * Fix: Revert local datasource url change. * [Test] Notice create, update 시 plainTextDescription 동작 테스트 (#78) * Config: Add test profile. * Test: Add Tests for Create, Update Notice, in perspective of plainTextDescription * Fix: Change life cycle hook resolution to container. * [Feat] Seminar plain text field 추가 (#79) * Feat: Add plain text column for description, introduction, additionalnote. * Feat: Add description, introduction, additionalnote for dto -> entity. * Feat: Change to update plain description, introduction, additional note. * Feat: Use plainTextDesription for searchdto. * Test: Test for create, update of plain texts. * Test: Fix seminar service test. * Feat: News plain text field 추가 (#80) * Feat: Add plainTextDescription field for news entity * Feat: Add to create, and update plainTextDescription. * Feat: Change to use plainTextDescription when making newssearchdto. * Test: Test for create/update plainTextDescription. * Feat: 구성원 검색 위한 table 추가. (#81) * Feat: MemberSearchEntity 추가. * Feat: MemberSearchRepository 추가 * Feat: MemberSearchService 추가 * Feat: ProfessorEntity, StaffEntity에 MemberSearch 추가. * Feat: ProfessorStatus에 한글 값 추가. * Feat: professor create, update 시 memberSearch도 반영하도록 추가. * Feat: seminar create, update 시 memberSearch도 반영하도록 추가. * Test: ProfessorService 테스트 추가. * Test: StaffServiceTest 추가. * fix: 새소식 세미나 쿼리 수정 (#82) * refactor: 유틸 패키지 생성 및 FixedPageRequest 분리 * feat: 새소식 세미나 이전 다음 글 및 total 쿼리 최적화 * fix: pageNum 값으로 usePageBtn 값 설정 * fix: import 경로 수정 * fix: local db port 3306 * 중복 삭제 * fix: prod endpoint https로 변경 * fix: main 프론트에 맞게 협의, notice 태그 enum 추가 (#83) * fix: mainResponse 다시 작성 * feat: TagInNoticeEnum 추가 * feat: notice enum 추가 * fix: 코드 수정 * fix: isPublic -> isPrivate으로 정리 (#86) * fix: rebase 정리 * fix: 오타 수정 * feat: 로컬 로그인 테스트용 엔드포인트로 임시 변경 (#87) * Feat: 구성원 검색 API 추가 (#85) * Feat: Add mysql custom dialect with MATCH AGAINST BOOLEAN MODE function. * CICD: Add config for mysql db * CICD: Add volumes for config file. * CICD: Add db_config for condition for db deploy * Feat: Add search top, and search query. * Feat: Add DTOs * Feat: Add Controller for member search only top, and page. * Feat: Add dialect config for application.yaml * Feat: Add controller for member search. * Refactor: Change exception message to korean. --------- Co-authored-by: Jo Seonggyu * fix: 세미나 시간 타입 변경 (#91) * fix: 세미나 시간 LocalDateTime으로 변경 * test: 테스트 수정 * fix: 태그 항목 수정 (#92) * fix: news tag 추가 (#90) * fix: news tag 추가 * fix: 일반인은 isPrivate = false인 것만, 직원은 모든 게시글 보여주기 * feat: 졸업생 창업 진로, 졸업생 진로 response 추가 (#94) * fix: news tag 추가 * fix: 일반인은 isPrivate = false인 것만, 직원은 모든 게시글 보여주기 * feat: about 졸업생 read 추가 * fix: 오타 수정 * fix: conference 주소 추가 (#95) * Feat: 연구 탭 통합검색 위한 Table 생성. (#97) * Feat: Add Controller for member search only top, and page. * Feat: Add ResearchSearchEntity, and entry for conference, research, lab entity. * Feat: For research create/update, lab create, add updating researchsearch. * Feat: Add empty repository and service for ResearchSearch * Fix: Fix typo, import library. * Feat: Lab Update API 추가 (#98) * Feat: Add Controller for member search only top, and page. * Feat: Add ResearchSearchEntity, and entry for conference, research, lab entity. * Feat: For research create/update, lab create, add updating researchsearch. * Feat: Add empty repository and service for ResearchSearch * Feat: Add update method for labentity. * Feat: Add update lab service. * Feat: Add update lab controller. * Test: Add ResearchServiceTest. * Feat: Add deleteAttachment (marking) * Fix: Add typo, add import library. * Feat: Conference page 수정 API (#99) * Feat: Add Controller for member search only top, and page. * Feat: Add ResearchSearchEntity, and entry for conference, research, lab entity. * Feat: For research create/update, lab create, add updating researchsearch. * Feat: Add empty repository and service for ResearchSearch * Feat: Add update method for labentity. * Feat: Add update lab service. * Feat: Add update lab controller. * Test: Add ResearchServiceTest. * Feat: Add Conference Repository. * Feat: add conferenc eentity method for create, update entity. * Feat: Change variables to mutable. * Feat: Add id in conferencedto. * Feat: Add to sort the conferenceList. * Feat: Add deleteAttachment (marking) * Feat add service to modify conference page (create, modify, delete conferences) * Feat: Add api for modify conference page. * Test: Add test for conference service. --------- Co-authored-by: Junhyeong Kim * fix: 파일 관련 수정 (#100) * fix: 업로드 파일 사이즈 제한 * fix: 원본 첨부파일 이름으로 응답 * refactor: 세미나 업데이트 시 삭제할 첨부파일 id만 받도록 변경 * fix: 파일 api 경로 수정 * fix: deleteAttachment * fix: GET notice 권한 관련 로직 수정 (#101) * Refactor: Extract Full Text Query Template to CommonRepository (#102) * Feat: Extract full text search template to common repository. * Feat: Change full text search to use extracted repository. * Feat: Add for 4 elements. --------- Co-authored-by: Junhyeong Kim * Feat: Notice Seminar News Search to Full Text Query (#105) * Feat: Extract full text search template to common repository. * Feat: Change full text search to use extracted repository. * Feat: Add for 4 elements. * Feat: Add more args cnt. * Feat: Change keyword boolean builder to full text. * Feat: Change keyword boolean builder to full text. * Feat: Change keyword boolean builder to full text. * Hotfix: Change mount volume. (#103) * feat: api 권한 설정 (#84) * fix: GET notice 권한 관련 로직 수정 (#109) * fix: 필요한 column만 select (#111) * refactor: 공지 태그 응답 한국어로 변경 및 리팩토링 (#113) * refactor: 공지 태그 응답 한국어로 수정 및 리팩토링 * refactor: 새소식 태그 리팩토링 * fix: NewsRepository * fix: 공지 새소식 PATCH 로직 수정 (#114) * fix: 공지 새소식 PATCH 로직 수정 * test: 테스트 수정 * fix: news 엔티티에 날짜 추가 (#115) * Feat: 검색 결과에서 keyword 위치, 적절한 substring 추출 위한 method 추가 (#116) * Feat: Add utils substringAroundKeyword * Test: Add test for Utils.kt * fix: total 더미 값 + pageNum null일때만 더미 값 주기 (#117) * fix: 예약 조회 권한 분리 및 응답 단순화 (#119) * fix: 예약 단건 조회 권한 분리 * feat: 월별,주별 조회용 SimpleReservationDto * fix: 로그인 리다이렉트 엔드포인트 복구 (#121) * feat: migrateAbout, migrateFutureCareers 추가 (#118) * feat: AboutRequest, migrateAbout 추가 * feat: future-careers 마이그레이션 추가 * feat: request 패키지 추가 * fix: AboutRequest 간소화 * fix: future-careers description aboutEntity에 저장 * feat: about 패키지 마이그레이션 추가 (#120) * feat: AboutRequest, migrateAbout 추가 * feat: future-careers 마이그레이션 추가 * feat: request 패키지 추가 * fix: 불필요한 dto 삭제 * feat: migrateStudentClubs 추가 * feat: migrateFacilities 추가 * feat: migrateDirections 추가 * fix: 불필요한 dto 삭제 * fix: request 패키지 삭제 * fix: 오타 수정 * fix: news, notice에 titleForMain 추가 (#125) * fix: 소식3총사 titleForMain 데이터 생성 * fix: 테스트에 titleForMain 추가 * Revert "fix: 테스트에 titleForMain 추가" This reverts commit e824ad34e60e0785f1d4c4e187776d4b8bf53dfa. * fix: 테스트에 titleForMain 추가 * fix: seminar titleForMain 추가 * fix: seminar test titleForMain 추가 * feat: LocalDateTime 응답 마지막에 Z 추가 (#126) * refactor: deleteIds request 내부로 이동 (#127) * refactor: deleteIds request 내부로 이동 * test: 테스트 수정 * fix: 확장자 중복 버그 (#128) * feat: 관리자는 새소식, 세미나 목록에서 비공개 글 확인 가능 (#129) * feat: research, member 패키지 migrate 추가 (#130) * feat: migrateProfessors 추가 * feat: migrateResearchDetail 추가 * feat: migrateLabs 추가 * fix: migrate 엔티티 description에 mediumText 추가 * feat: migrateStaff 추가 * fix: 빠진거 수정 * fix: 중복 패키지 삭제 * fix: 너무 안긴거 text로 수정 * fix: noticeSearchDto에도 private 추가 (#131) * fix: noticeSearchDto에도 private 추가 * fix: json 추가 * fix: 수정 및 추가 * fix: 수정 * Fix: 키워드 추출 함수 수정 (#122) * Fix: Fix IndexOutOfBoundException, change to return index based on substring. * Fix: Change to check index exclude case, and set front, back index to better contains keyword. * Test: Update Test. * Feat: 통합검색 Notice API 구현 (#123) * Feat: Add dtos for notice total search. * Feat: Add repository for notice total search. * Feat: Add service, controller for notice total search * Feat: News 통합 검색 API 구현 (#124) * Feat: Add dto for total search of news. * Feat: Add repository for total search. * Feat: Add service, controller for news total search. * Fix: Fix conflict issues. * fix: 기존 objectMapper 설정 오버라이드 (#132) * feat: 파일 업로드 컨트롤러 구현 (#133) * Feat: Change titleForMain column to TEXT type. (#134) Co-authored-by: Junhyeong Kim * hotfix: mixed content issue (#136) * hotfix: loginPage to https (#139) * hotfix: mixed content issue * hotfix: loginPage * fix: false로 수정 (#141) * fix: 이전 다음 글에서 비공개 글 제외 (#143) * fix: 이전 다음글 비공개 제외 * fix: prev-next * CBT 위해서 예약 GET 권한체크 주석처리 * Feat: 뉴스 날짜로 정렬 (#144) * Feat: Change date field to NOT NULLABLE. * Feat: Change to sort by date, not createdat. * Test: Fix test. * Test: Move news test dir. --------- Co-authored-by: Junhyeong Kim * Feat: Add description for slide. (#146) * style: ktlint (#145) * style: ktlint * Feat: change to ktlint_disabled_rules * Refactor: fix to lint using ktlint. * Refactor: After merge. * Fix: After merge. * Refactor: ReReLint --------- Co-authored-by: huGgW * fix: 텍스트 인코딩 (#147) * fix: content-dispostion * fix: 텍스트 인코딩 * fix: 강제 다운로드 * style: ktlint --------- Co-authored-by: 우혁준 (HyukJoon Woo) * Update ktlint-check.yml * fix: 중요 안내 (#149) * fix: 삭제된 글 중요 안내에서 보이지 않도록 수정 * fix: 중요 안내 최대 2개만 보내도록 + plainTextDescription * style: ktlint * fix: 첨부파일 다운로드 (#154) * Feat: 관리자 슬라이드에 total 추가, refactoring (#153) * Feat: Change dto name, and add total to AdminSlidesResponse. * Feat: Add pageSize as parameter. * Feat: move readAllSlides to news repository, add total query. * Feat: Extract getNewsEntityByIdOrThrow * Feat: Move readAllSlides and unSlideManyNews logic handling to news service. * Comment: Add comment for refactoring. * Test: Add test for read all slides. * Refactor: Linting. * Feat: Change pageNum to start at 1. --------- Co-authored-by: Junhyeong Kim * fix: 세미나 isYearLast 로직 수정 (#155) * fix: 세미나 isYearFirst 로직 수정 * fix: isYearLast로 재변경 및 로직 수정 * fix: 이전글 다음 글 isDelete,isPrivate 조건 추가 (#151) * fix: 이전글 다음 글 isDelete 추가 * fix: ktlint 수정 --------- Co-authored-by: 우혁준 (HyukJoon Woo) Co-authored-by: Junhyeong Kim * fix: 메인 공지 정렬 (#158) * fix: 예약 조회 권한 수정 (#157) * Feat: 통합검색 정렬 최신순 (#156) * Feat: Admin Important에 pagination 추가, total 추가. (#159) * fix: titleForMain이 없어도 isImportant 등록 가능 (#160) Co-authored-by: 우혁준 (HyukJoon Woo) --------- Co-authored-by: Junhyeong Kim Co-authored-by: 우혁준 (HyukJoon Woo) --- .../csereal/core/admin/api/AdminController.kt | 16 ++-- .../core/admin/database/AdminRepository.kt | 86 +++++++++++++------ ...ntResponse.kt => AdminImportantElement.kt} | 2 +- .../core/admin/dto/AdminImportantResponse.kt | 6 ++ ...{SlideResponse.kt => AdminSlideElement.kt} | 2 +- .../core/admin/dto/AdminSlidesResponse.kt | 6 ++ .../core/admin/service/AdminService.kt | 74 +++++----------- .../core/main/database/MainRepository.kt | 4 +- .../core/news/database/NewsRepository.kt | 47 +++++++++- .../csereal/core/news/service/NewsService.kt | 41 ++++++--- .../core/notice/database/NoticeRepository.kt | 17 ++-- .../core/notice/service/NoticeService.kt | 12 +-- .../reservation/api/ReservationController.kt | 22 +++-- .../reservation/database/ReservationEntity.kt | 2 +- .../core/reservation/dto/ReservationDto.kt | 3 +- .../reservation/service/ReservationService.kt | 19 ++-- .../common/api/DeprecatedFileController.kt | 15 ++-- .../resource/common/api/FileController.kt | 15 ++-- .../seminar/database/SeminarRepository.kt | 12 ++- .../core/seminar/service/SeminarService.kt | 12 +-- .../csereal/core/news/NewsServiceTest.kt | 86 +++++++++++++++++++ 21 files changed, 342 insertions(+), 157 deletions(-) rename src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/{ImportantResponse.kt => AdminImportantElement.kt} (84%) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/AdminImportantResponse.kt rename src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/{SlideResponse.kt => AdminSlideElement.kt} (84%) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/AdminSlidesResponse.kt diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admin/api/AdminController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admin/api/AdminController.kt index 5f2cce16..276ece56 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admin/api/AdminController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admin/api/AdminController.kt @@ -19,9 +19,10 @@ class AdminController( @AuthenticatedStaff @GetMapping("/slide") fun readAllSlides( - @RequestParam(required = false, defaultValue = "0") pageNum: Long - ): ResponseEntity> { - return ResponseEntity.ok(adminService.readAllSlides(pageNum)) + @RequestParam(required = false, defaultValue = "1") pageNum: Long, + @RequestParam(required = false, defaultValue = "40") pageSize: Int + ): ResponseEntity { + return ResponseEntity.ok(adminService.readAllSlides(pageNum - 1, pageSize)) } @AuthenticatedStaff @@ -35,9 +36,12 @@ class AdminController( @AuthenticatedStaff @GetMapping("/important") fun readAllImportants( - @RequestParam(required = false, defaultValue = "0") pageNum: Long - ): ResponseEntity> { - return ResponseEntity.ok(adminService.readAllImportants(pageNum)) + @RequestParam(required = false, defaultValue = "1") pageNum: Int, + @RequestParam(required = false, defaultValue = "40") pageSize: Int + ): ResponseEntity { + return ResponseEntity.ok( + adminService.readAllImportants(pageNum - 1, pageSize) + ) } @AuthenticatedStaff diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admin/database/AdminRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admin/database/AdminRepository.kt index 25d8d024..caf68c3e 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admin/database/AdminRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admin/database/AdminRepository.kt @@ -1,32 +1,68 @@ package com.wafflestudio.csereal.core.admin.database -import com.querydsl.core.types.Projections -import com.querydsl.jpa.impl.JPAQueryFactory -import com.wafflestudio.csereal.core.admin.dto.SlideResponse -import com.wafflestudio.csereal.core.news.database.QNewsEntity.newsEntity -import org.springframework.stereotype.Component +import com.wafflestudio.csereal.core.admin.dto.AdminImportantElement +import jakarta.persistence.EntityManagerFactory +import org.springframework.stereotype.Repository +import java.sql.Timestamp -interface AdminRepository { - fun readAllSlides(pageNum: Long): List -} +@Repository +class AdminRepository( + private val emf: EntityManagerFactory +) { + fun readImportantsPagination(pageSize: Int, offset: Int): List { + val em = emf.createEntityManager() + val query = em.createNativeQuery( + """ + ( + SELECT id, title, created_at, 'notice' AS type + FROM notice + WHERE (is_deleted = false AND is_important = TRUE) + UNION ALL + SELECT news.id, title, date, 'news' AS type + FROM news + WHERE (is_deleted = false AND is_important = TRUE) + UNION ALL + SELECT seminar.id, title, created_at, 'seminar' AS type + FROM seminar + WHERE (is_deleted = false AND is_important = TRUE) + ) ORDER BY created_at DESC + LIMIT :pageSize OFFSET :offset + """.trimIndent() + ) + query.setParameter("pageSize", pageSize) + query.setParameter("offset", offset) -@Component -class AdminRepositoryImpl( - private val queryFactory: JPAQueryFactory -) : AdminRepository { - override fun readAllSlides(pageNum: Long): List { - return queryFactory.select( - Projections.constructor( - SlideResponse::class.java, - newsEntity.id, - newsEntity.title, - newsEntity.createdAt + val result = query.resultList as List> + val formattedResult = result.map { + AdminImportantElement( + id = it[0] as Long, + title = it[1] as String, + createdAt = (it[2] as Timestamp).toLocalDateTime(), + category = it[3] as String ) - ).from(newsEntity) - .where(newsEntity.isDeleted.eq(false), newsEntity.isPrivate.eq(false), newsEntity.isSlide.eq(true)) - .orderBy(newsEntity.createdAt.desc()) - .offset(40 * pageNum) - .limit(40) - .fetch() + } + return formattedResult + } + + fun getTotalImportantsCnt(): Long { + val em = emf.createEntityManager() + val query = em.createNativeQuery( + """ + SELECT COUNT(*) FROM ( + SELECT id, title, created_at, 'notice' AS type + FROM notice + WHERE (is_deleted = false AND is_important = TRUE) + UNION ALL + SELECT news.id, title, created_at, 'news' AS type + FROM news + WHERE (is_deleted = false AND is_important = TRUE) + UNION ALL + SELECT seminar.id, title, created_at, 'seminar' AS type + FROM seminar + WHERE (is_deleted = false AND is_important = TRUE) + ) as nn + """.trimIndent() + ) + return query.resultList.first() as Long } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/ImportantResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/AdminImportantElement.kt similarity index 84% rename from src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/ImportantResponse.kt rename to src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/AdminImportantElement.kt index f2fe3586..f706b852 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/ImportantResponse.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/AdminImportantElement.kt @@ -2,7 +2,7 @@ package com.wafflestudio.csereal.core.admin.dto import java.time.LocalDateTime -class ImportantResponse( +data class AdminImportantElement( val id: Long, val title: String, val createdAt: LocalDateTime?, diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/AdminImportantResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/AdminImportantResponse.kt new file mode 100644 index 00000000..794adfd0 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/AdminImportantResponse.kt @@ -0,0 +1,6 @@ +package com.wafflestudio.csereal.core.admin.dto + +data class AdminImportantResponse( + val total: Long, + val importants: List = listOf() +) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/SlideResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/AdminSlideElement.kt similarity index 84% rename from src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/SlideResponse.kt rename to src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/AdminSlideElement.kt index cea89ab9..77e90b06 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/SlideResponse.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/AdminSlideElement.kt @@ -2,7 +2,7 @@ package com.wafflestudio.csereal.core.admin.dto import java.time.LocalDateTime -class SlideResponse( +data class AdminSlideElement( val id: Long, val title: String, val createdAt: LocalDateTime? diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/AdminSlidesResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/AdminSlidesResponse.kt new file mode 100644 index 00000000..61d7c00c --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admin/dto/AdminSlidesResponse.kt @@ -0,0 +1,6 @@ +package com.wafflestudio.csereal.core.admin.dto + +data class AdminSlidesResponse( + val total: Long, + val slides: List = listOf() +) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admin/service/AdminService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admin/service/AdminService.kt index b831309d..f5715465 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admin/service/AdminService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admin/service/AdminService.kt @@ -3,10 +3,10 @@ package com.wafflestudio.csereal.core.admin.service import com.wafflestudio.csereal.common.CserealException import com.wafflestudio.csereal.core.admin.database.AdminRepository import com.wafflestudio.csereal.core.admin.dto.ImportantDto -import com.wafflestudio.csereal.core.admin.dto.ImportantResponse -import com.wafflestudio.csereal.core.admin.dto.SlideResponse -import com.wafflestudio.csereal.core.news.database.NewsEntity +import com.wafflestudio.csereal.core.admin.dto.AdminImportantResponse +import com.wafflestudio.csereal.core.admin.dto.AdminSlidesResponse import com.wafflestudio.csereal.core.news.database.NewsRepository +import com.wafflestudio.csereal.core.news.service.NewsService import com.wafflestudio.csereal.core.notice.database.NoticeRepository import com.wafflestudio.csereal.core.seminar.database.SeminarRepository import org.springframework.data.repository.findByIdOrNull @@ -14,73 +14,41 @@ import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional interface AdminService { - fun readAllSlides(pageNum: Long): List + fun readAllSlides(pageNum: Long, pageSize: Int): AdminSlidesResponse fun unSlideManyNews(request: List) - fun readAllImportants(pageNum: Long): List + fun readAllImportants(pageNum: Int, pageSize: Int): AdminImportantResponse fun makeNotImportants(request: List) } @Service class AdminServiceImpl( + private val newsService: NewsService, private val adminRepository: AdminRepository, private val noticeRepository: NoticeRepository, private val newsRepository: NewsRepository, private val seminarRepository: SeminarRepository ) : AdminService { - @Transactional - override fun readAllSlides(pageNum: Long): List { - return adminRepository.readAllSlides(pageNum) - } - - @Transactional - override fun unSlideManyNews(request: List) { - for (newsId in request) { - val news: NewsEntity = newsRepository.findByIdOrNull(newsId) - ?: throw CserealException.Csereal404("존재하지 않는 새소식입니다.(newsId=$newsId)") - news.isSlide = false - } - } + @Transactional(readOnly = true) + override fun readAllSlides(pageNum: Long, pageSize: Int): AdminSlidesResponse = + newsService.readAllSlides(pageNum, pageSize) @Transactional - override fun readAllImportants(pageNum: Long): List { - val importantResponses: MutableList = mutableListOf() - noticeRepository.findAllByIsImportantTrueAndIsDeletedFalse().forEach { - importantResponses.add( - ImportantResponse( - id = it.id, - title = it.title, - createdAt = it.createdAt, - category = "notice" - ) - ) - } + override fun unSlideManyNews(request: List) = + newsService.unSlideManyNews(request) - newsRepository.findAllByIsImportantTrueAndIsDeletedFalse().forEach { - importantResponses.add( - ImportantResponse( - id = it.id, - title = it.title, - createdAt = it.createdAt, - category = "news" - ) - ) - } - - seminarRepository.findAllByIsImportantTrueAndIsDeletedFalse().forEach { - importantResponses.add( - ImportantResponse( - id = it.id, - title = it.title, - createdAt = it.createdAt, - category = "seminar" - ) - ) - } - importantResponses.sortByDescending { it.createdAt } + @Transactional(readOnly = true) + override fun readAllImportants(pageNum: Int, pageSize: Int): AdminImportantResponse { + val offset = pageNum * pageSize + val importantList = adminRepository.readImportantsPagination(pageSize, offset) + val importantTotal = adminRepository.getTotalImportantsCnt() - return importantResponses + return AdminImportantResponse( + total = importantTotal, + importants = importantList + ) } + // TODO: 각 도메인의 Service로 구현, Service method 이용하기 @Transactional override fun makeNotImportants(request: List) { for (important in request) { diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/main/database/MainRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/main/database/MainRepository.kt index e7bda26d..776f13f5 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/main/database/MainRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/main/database/MainRepository.kt @@ -64,7 +64,7 @@ class MainRepositoryImpl( ) ).from(noticeEntity) .where(noticeEntity.isDeleted.eq(false), noticeEntity.isPrivate.eq(false)) - .orderBy(noticeEntity.isPinned.desc()).orderBy(noticeEntity.createdAt.desc()) + .orderBy(noticeEntity.createdAt.desc()) .limit(6).fetch() } @@ -82,7 +82,7 @@ class MainRepositoryImpl( .rightJoin(tagInNoticeEntity).on(noticeTagEntity.tag.eq(tagInNoticeEntity)) .where(noticeTagEntity.tag.name.eq(tagEnum)) .where(noticeEntity.isDeleted.eq(false), noticeEntity.isPrivate.eq(false)) - .orderBy(noticeEntity.isPinned.desc()).orderBy(noticeTagEntity.notice.createdAt.desc()) + .orderBy(noticeTagEntity.notice.createdAt.desc()) .limit(6).distinct().fetch() } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt index d9ba9b8d..a27805d1 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt @@ -4,6 +4,8 @@ import com.querydsl.core.BooleanBuilder import com.querydsl.jpa.impl.JPAQueryFactory import com.wafflestudio.csereal.common.repository.CommonRepository import com.wafflestudio.csereal.common.utils.FixedPageRequest +import com.wafflestudio.csereal.core.admin.dto.AdminSlideElement +import com.wafflestudio.csereal.core.admin.dto.AdminSlidesResponse import com.wafflestudio.csereal.core.news.database.QNewsEntity.newsEntity import com.wafflestudio.csereal.core.news.database.QNewsTagEntity.newsTagEntity import com.wafflestudio.csereal.core.news.database.QTagInNewsEntity.tagInNewsEntity @@ -16,14 +18,19 @@ import com.wafflestudio.csereal.core.resource.mainImage.database.QMainImageEntit import com.wafflestudio.csereal.core.resource.mainImage.service.MainImageService import org.springframework.data.domain.Pageable import org.springframework.data.jpa.repository.JpaRepository -import org.springframework.stereotype.Component +import org.springframework.stereotype.Repository import java.time.LocalDateTime interface NewsRepository : JpaRepository, CustomNewsRepository { fun findAllByIsPrivateFalseAndIsImportantTrueAndIsDeletedFalse(): List fun findAllByIsImportantTrueAndIsDeletedFalse(): List - fun findFirstByCreatedAtLessThanOrderByCreatedAtDesc(timestamp: LocalDateTime): NewsEntity? - fun findFirstByCreatedAtGreaterThanOrderByCreatedAtAsc(timestamp: LocalDateTime): NewsEntity? + fun findFirstByIsDeletedFalseAndIsPrivateFalseAndCreatedAtLessThanOrderByCreatedAtDesc( + timestamp: LocalDateTime + ): NewsEntity? + + fun findFirstByIsDeletedFalseAndIsPrivateFalseAndCreatedAtGreaterThanOrderByCreatedAtAsc( + timestamp: LocalDateTime + ): NewsEntity? } interface CustomNewsRepository { @@ -41,9 +48,11 @@ interface CustomNewsRepository { amount: Int, imageUrlCreator: (MainImageEntity?) -> String? ): NewsTotalSearchDto + + fun readAllSlides(pageNum: Long, pageSize: Int): AdminSlidesResponse } -@Component +@Repository class NewsRepositoryImpl( private val queryFactory: JPAQueryFactory, private val mainImageService: MainImageService, @@ -143,6 +152,7 @@ class NewsRepositoryImpl( ).from(newsEntity) .leftJoin(mainImageEntity) .where(doubleTemplate.gt(0.0)) + .orderBy(newsEntity.date.desc()) .limit(number.toLong()) .fetch() @@ -181,4 +191,33 @@ class NewsRepositoryImpl( } ) } + + override fun readAllSlides(pageNum: Long, pageSize: Int): AdminSlidesResponse { + val tuple = queryFactory.select( + newsEntity.id, + newsEntity.title, + newsEntity.createdAt + ).from(newsEntity) + .where(newsEntity.isDeleted.eq(false), newsEntity.isPrivate.eq(false), newsEntity.isSlide.eq(true)) + .orderBy(newsEntity.createdAt.desc()) + .offset(pageSize * pageNum) + .limit(pageSize.toLong()) + .fetch() + + val total = queryFactory.select(newsEntity.count()) + .from(newsEntity) + .where(newsEntity.isDeleted.eq(false), newsEntity.isPrivate.eq(false), newsEntity.isSlide.eq(true)) + .fetchOne()!! + + return AdminSlidesResponse( + total, + tuple.map { + AdminSlideElement( + id = it[newsEntity.id]!!, + title = it[newsEntity.title]!!, + createdAt = it[newsEntity.createdAt]!! + ) + } + ) + } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt index b09cb72d..e45a4169 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt @@ -1,6 +1,7 @@ package com.wafflestudio.csereal.core.news.service import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.core.admin.dto.AdminSlidesResponse import com.wafflestudio.csereal.core.news.database.* import com.wafflestudio.csereal.core.news.dto.NewsDto import com.wafflestudio.csereal.core.news.dto.NewsSearchResponse @@ -34,6 +35,8 @@ interface NewsService { fun deleteNews(newsId: Long) fun enrollTag(tagName: String) fun searchTotalNews(keyword: String, number: Int, amount: Int): NewsTotalSearchDto + fun readAllSlides(pageNum: Long, pageSize: Int): AdminSlidesResponse + fun unSlideManyNews(request: List) } @Service @@ -77,8 +80,14 @@ class NewsServiceImpl( val imageURL = mainImageService.createImageURL(news.mainImage) val attachmentResponses = attachmentService.createAttachmentResponses(news.attachments) - val prevNews = newsRepository.findFirstByCreatedAtLessThanOrderByCreatedAtDesc(news.createdAt!!) - val nextNews = newsRepository.findFirstByCreatedAtGreaterThanOrderByCreatedAtAsc(news.createdAt!!) + val prevNews = + newsRepository.findFirstByIsDeletedFalseAndIsPrivateFalseAndCreatedAtLessThanOrderByCreatedAtDesc( + news.createdAt!! + ) + val nextNews = + newsRepository.findFirstByIsDeletedFalseAndIsPrivateFalseAndCreatedAtGreaterThanOrderByCreatedAtAsc( + news.createdAt!! + ) return NewsDto.of(news, imageURL, attachmentResponses, prevNews, nextNews) } @@ -101,10 +110,6 @@ class NewsServiceImpl( attachmentService.uploadAllAttachments(newNews, attachments) } - if (request.isImportant && request.titleForMain.isNullOrEmpty()) { - throw CserealException.Csereal400("중요 제목이 입력되어야 합니다") - } - newsRepository.save(newNews) val imageURL = mainImageService.createImageURL(newNews.mainImage) @@ -120,8 +125,7 @@ class NewsServiceImpl( newMainImage: MultipartFile?, newAttachments: List? ): NewsDto { - val news: NewsEntity = newsRepository.findByIdOrNull(newsId) - ?: throw CserealException.Csereal404("존재하지 않는 새소식입니다. (newsId: $newsId)") + val news: NewsEntity = getNewsEntityByIdOrThrow(newsId) if (news.isDeleted) throw CserealException.Csereal404("삭제된 새소식입니다.") news.update(request) @@ -161,8 +165,7 @@ class NewsServiceImpl( @Transactional override fun deleteNews(newsId: Long) { - val news: NewsEntity = newsRepository.findByIdOrNull(newsId) - ?: throw CserealException.Csereal404("존재하지 않는 새소식입니다.(newsId: $newsId") + val news: NewsEntity = getNewsEntityByIdOrThrow(newsId) news.isDeleted = true } @@ -173,4 +176,22 @@ class NewsServiceImpl( ) tagInNewsRepository.save(newTag) } + + @Transactional(readOnly = true) + override fun readAllSlides(pageNum: Long, pageSize: Int): AdminSlidesResponse { + return newsRepository.readAllSlides(pageNum, pageSize) + } + + @Transactional + override fun unSlideManyNews(request: List) { + for (newsId in request) { + val news = getNewsEntityByIdOrThrow(newsId) + news.isSlide = false + } + } + + fun getNewsEntityByIdOrThrow(newsId: Long): NewsEntity { + return newsRepository.findByIdOrNull(newsId) + ?: throw CserealException.Csereal404("존재하지 않는 새소식입니다.(newsId: $newsId)") + } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt index a4a531dd..17269405 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt @@ -19,8 +19,13 @@ import java.time.LocalDateTime interface NoticeRepository : JpaRepository, CustomNoticeRepository { fun findAllByIsPrivateFalseAndIsImportantTrueAndIsDeletedFalse(): List fun findAllByIsImportantTrueAndIsDeletedFalse(): List - fun findFirstByCreatedAtLessThanOrderByCreatedAtDesc(timestamp: LocalDateTime): NoticeEntity? - fun findFirstByCreatedAtGreaterThanOrderByCreatedAtAsc(timestamp: LocalDateTime): NoticeEntity? + fun findFirstByIsDeletedFalseAndIsPrivateFalseAndCreatedAtLessThanOrderByCreatedAtDesc( + timestamp: LocalDateTime + ): NoticeEntity? + + fun findFirstByIsDeletedFalseAndIsPrivateFalseAndCreatedAtGreaterThanOrderByCreatedAtAsc( + timestamp: LocalDateTime + ): NoticeEntity? } interface CustomNoticeRepository { @@ -61,7 +66,10 @@ class NoticeRepositoryImpl( val total = query.clone().select(noticeEntity.countDistinct()).fetchOne()!! - val searchResult = query.limit(number.toLong()).fetch() + val searchResult = query + .orderBy(noticeEntity.createdAt.desc()) + .limit(number.toLong()) + .fetch() return NoticeTotalSearchResponse( total.toInt(), @@ -123,8 +131,7 @@ class NoticeRepositoryImpl( noticeEntity.attachments.isNotEmpty, noticeEntity.isPrivate ) - ) - .from(noticeEntity) + ).from(noticeEntity) .leftJoin(noticeTagEntity).on(noticeTagEntity.notice.eq(noticeEntity)) .where(noticeEntity.isDeleted.eq(false)) .where(keywordBooleanBuilder, tagsBooleanBuilder, isPrivateBooleanBuilder) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt index 59b62670..b12ea9c2 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt @@ -75,9 +75,13 @@ class NoticeServiceImpl( val attachmentResponses = attachmentService.createAttachmentResponses(notice.attachments) val prevNotice = - noticeRepository.findFirstByCreatedAtLessThanOrderByCreatedAtDesc(notice.createdAt!!) + noticeRepository.findFirstByIsDeletedFalseAndIsPrivateFalseAndCreatedAtLessThanOrderByCreatedAtDesc( + notice.createdAt!! + ) val nextNotice = - noticeRepository.findFirstByCreatedAtGreaterThanOrderByCreatedAtAsc(notice.createdAt!!) + noticeRepository.findFirstByIsDeletedFalseAndIsPrivateFalseAndCreatedAtGreaterThanOrderByCreatedAtAsc( + notice.createdAt!! + ) return NoticeDto.of(notice, attachmentResponses, prevNotice, nextNotice) } @@ -110,10 +114,6 @@ class NoticeServiceImpl( attachmentService.uploadAllAttachments(newNotice, attachments) } - if (request.isImportant && request.titleForMain.isNullOrEmpty()) { - throw CserealException.Csereal400("중요 제목이 입력되어야 합니다") - } - noticeRepository.save(newNotice) val attachmentResponses = attachmentService.createAttachmentResponses(newNotice.attachments) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/api/ReservationController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/api/ReservationController.kt index 7bc843c1..c48efc8e 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/api/ReservationController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/api/ReservationController.kt @@ -5,7 +5,11 @@ import com.wafflestudio.csereal.core.reservation.dto.ReservationDto import com.wafflestudio.csereal.core.reservation.dto.ReserveRequest import com.wafflestudio.csereal.core.reservation.dto.SimpleReservationDto import com.wafflestudio.csereal.core.reservation.service.ReservationService +import com.wafflestudio.csereal.core.user.database.Role +import com.wafflestudio.csereal.core.user.database.UserRepository import org.springframework.http.ResponseEntity +import org.springframework.security.core.annotation.AuthenticationPrincipal +import org.springframework.security.oauth2.core.oidc.user.OidcUser import org.springframework.web.bind.annotation.DeleteMapping import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable @@ -20,10 +24,10 @@ import java.util.UUID @RequestMapping("/api/v1/reservation") @RestController class ReservationController( - private val reservationService: ReservationService + private val reservationService: ReservationService, + private val userRepository: UserRepository ) { - // @AuthenticatedForReservation TODO: CBT 끝나면 주석 제거 @GetMapping("/month") fun getMonthlyReservations( @RequestParam roomId: Long, @@ -35,7 +39,6 @@ class ReservationController( return ResponseEntity.ok(reservationService.getRoomReservationsBetween(roomId, start, end)) } - // @AuthenticatedForReservation @GetMapping("/week") fun getWeeklyReservations( @RequestParam roomId: Long, @@ -48,10 +51,17 @@ class ReservationController( return ResponseEntity.ok(reservationService.getRoomReservationsBetween(roomId, start, end)) } - // @AuthenticatedForReservation @GetMapping("/{reservationId}") - fun getReservation(@PathVariable reservationId: Long): ResponseEntity { - return ResponseEntity.ok(reservationService.getReservation(reservationId)) + fun getReservation( + @PathVariable reservationId: Long, + @AuthenticationPrincipal oidcUser: OidcUser? + ): ResponseEntity { + val isStaff = oidcUser?.let { + val username = it.idToken.getClaim("username") + val user = userRepository.findByUsername(username) + user?.role == Role.ROLE_STAFF + } ?: false + return ResponseEntity.ok(reservationService.getReservation(reservationId, isStaff)) } @PostMapping diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/database/ReservationEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/database/ReservationEntity.kt index 2b08ae95..95a7e22f 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/database/ReservationEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/database/ReservationEntity.kt @@ -33,7 +33,7 @@ class ReservationEntity( val professor: String, val recurringWeeks: Int = 1, - val recurrenceId: UUID? = null + val recurrenceId: UUID ) : BaseTimeEntity() { diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/dto/ReservationDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/dto/ReservationDto.kt index 39d22019..f6820e63 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/dto/ReservationDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/dto/ReservationDto.kt @@ -6,7 +6,7 @@ import java.util.UUID data class ReservationDto( val id: Long, - val recurrenceId: UUID? = null, + val recurrenceId: UUID, val title: String, val purpose: String, val startTime: LocalDateTime, @@ -41,6 +41,7 @@ data class ReservationDto( fun forNormalUser(reservationEntity: ReservationEntity): ReservationDto { return ReservationDto( id = reservationEntity.id, + recurrenceId = reservationEntity.recurrenceId, title = reservationEntity.title, purpose = reservationEntity.purpose, startTime = reservationEntity.startTime, diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/service/ReservationService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/service/ReservationService.kt index 59b4ce35..30fda4fe 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/reservation/service/ReservationService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/reservation/service/ReservationService.kt @@ -17,7 +17,7 @@ import java.util.* interface ReservationService { fun reserveRoom(reserveRequest: ReserveRequest): List fun getRoomReservationsBetween(roomId: Long, start: LocalDateTime, end: LocalDateTime): List - fun getReservation(reservationId: Long): ReservationDto + fun getReservation(reservationId: Long, isStaff: Boolean): ReservationDto fun cancelSpecific(reservationId: Long) fun cancelRecurring(recurrenceId: UUID) } @@ -78,20 +78,15 @@ class ReservationServiceImpl( } @Transactional(readOnly = true) - override fun getReservation(reservationId: Long): ReservationDto { + override fun getReservation(reservationId: Long, isStaff: Boolean): ReservationDto { val reservationEntity = reservationRepository.findByIdOrNull(reservationId) ?: throw CserealException.Csereal404("예약을 찾을 수 없습니다.") -// val user = RequestContextHolder.getRequestAttributes()?.getAttribute( -// "loggedInUser", -// RequestAttributes.SCOPE_REQUEST -// ) as UserEntity -// -// if (user.role == Role.ROLE_STAFF) { -// return ReservationDto.of(reservationEntity) -// } else { - return ReservationDto.forNormalUser(reservationEntity) -// } + return if (isStaff) { + ReservationDto.of(reservationEntity) + } else { + ReservationDto.forNormalUser(reservationEntity) + } } override fun cancelSpecific(reservationId: Long) { diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/common/api/DeprecatedFileController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/common/api/DeprecatedFileController.kt index 7040757f..2103ca8c 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/resource/common/api/DeprecatedFileController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/common/api/DeprecatedFileController.kt @@ -6,13 +6,16 @@ import org.springframework.core.io.Resource import org.springframework.core.io.UrlResource import org.springframework.http.HttpHeaders import org.springframework.http.HttpStatus +import org.springframework.http.MediaType import org.springframework.http.ResponseEntity import org.springframework.util.AntPathMatcher import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController +import java.net.URLEncoder import java.nio.file.Paths +import kotlin.text.Charsets.UTF_8 @RestController @RequestMapping("/sites/default/files") @@ -34,17 +37,15 @@ class DeprecatedFileController( val resource = UrlResource(file.toUri()) return if (resource.exists() || resource.isReadable) { - var contentType: String? = request.servletContext.getMimeType(resource.file.absolutePath) + val contentType: String? = request.servletContext.getMimeType(resource.file.absolutePath) val headers = HttpHeaders() - contentType = contentType ?: "application/octet-stream" + headers.contentType = MediaType.parseMediaType(contentType ?: "application/octet-stream") - if (contentType.startsWith("text")) { - contentType += ";charset=UTF-8" - } + val originalFilename = fileSubDir.substringAfterLast("/") + val encodedFilename = URLEncoder.encode(originalFilename, UTF_8.toString()).replace("+", "%20") - headers.contentType = - org.springframework.http.MediaType.parseMediaType(contentType) + headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''$encodedFilename") ResponseEntity.ok() .headers(headers) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/common/api/FileController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/common/api/FileController.kt index f738d736..79dacaa1 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/resource/common/api/FileController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/common/api/FileController.kt @@ -10,11 +10,14 @@ import org.springframework.core.io.Resource import org.springframework.core.io.UrlResource import org.springframework.http.HttpHeaders import org.springframework.http.HttpStatus +import org.springframework.http.MediaType import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* import org.springframework.web.multipart.MultipartFile +import java.net.URLEncoder import java.nio.file.Files import java.nio.file.Paths +import kotlin.text.Charsets.UTF_8 @RequestMapping("/api/v1/file") @RestController @@ -33,17 +36,15 @@ class FileController( val resource = UrlResource(file.toUri()) if (resource.exists() || resource.isReadable) { - var contentType: String? = request.servletContext.getMimeType(resource.file.absolutePath) + val contentType: String? = request.servletContext.getMimeType(resource.file.absolutePath) val headers = HttpHeaders() - contentType = contentType ?: "application/octet-stream" + headers.contentType = MediaType.parseMediaType(contentType ?: "application/octet-stream") - if (contentType.startsWith("text")) { - contentType += ";charset=UTF-8" - } + val originalFilename = filename.substringAfter("_") + val encodedFilename = URLEncoder.encode(originalFilename, UTF_8.toString()).replace("+", "%20") - headers.contentType = - org.springframework.http.MediaType.parseMediaType(contentType) + headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''$encodedFilename") return ResponseEntity.ok() .headers(headers) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt index 98545085..f7431267 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt @@ -16,8 +16,12 @@ import java.time.LocalDateTime interface SeminarRepository : JpaRepository, CustomSeminarRepository { fun findAllByIsPrivateFalseAndIsImportantTrueAndIsDeletedFalse(): List fun findAllByIsImportantTrueAndIsDeletedFalse(): List - fun findFirstByCreatedAtLessThanAndIsPrivateFalseOrderByCreatedAtDesc(timestamp: LocalDateTime): SeminarEntity? - fun findFirstByCreatedAtGreaterThanAndIsPrivateFalseOrderByCreatedAtAsc(timestamp: LocalDateTime): SeminarEntity? + fun findFirstByIsDeletedFalseAndIsPrivateFalseAndCreatedAtLessThanOrderByCreatedAtDesc( + timestamp: LocalDateTime + ): SeminarEntity? + fun findFirstByIsDeletedFalseAndIsPrivateFalseAndCreatedAtGreaterThanOrderByCreatedAtAsc( + timestamp: LocalDateTime + ): SeminarEntity? } interface CustomSeminarRepository { @@ -89,9 +93,9 @@ class SeminarRepositoryImpl( for (i: Int in 0 until seminarEntityList.size) { var isYearLast = false - if (i == seminarEntityList.size - 1) { + if (i == 0) { isYearLast = true - } else if (seminarEntityList[i].startDate?.year != seminarEntityList[i + 1].startDate?.year) { + } else if (seminarEntityList[i].startDate?.year != seminarEntityList[i - 1].startDate?.year) { isYearLast = true } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt index d8348849..a3cccb30 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt @@ -83,9 +83,13 @@ class SeminarServiceImpl( val attachmentResponses = attachmentService.createAttachmentResponses(seminar.attachments) val prevSeminar = - seminarRepository.findFirstByCreatedAtLessThanAndIsPrivateFalseOrderByCreatedAtDesc(seminar.createdAt!!) + seminarRepository.findFirstByIsDeletedFalseAndIsPrivateFalseAndCreatedAtLessThanOrderByCreatedAtDesc( + seminar.createdAt!! + ) val nextSeminar = - seminarRepository.findFirstByCreatedAtGreaterThanAndIsPrivateFalseOrderByCreatedAtAsc(seminar.createdAt!!) + seminarRepository.findFirstByIsDeletedFalseAndIsPrivateFalseAndCreatedAtGreaterThanOrderByCreatedAtAsc( + seminar.createdAt!! + ) return SeminarDto.of(seminar, imageURL, attachmentResponses, prevSeminar, nextSeminar) } @@ -114,10 +118,6 @@ class SeminarServiceImpl( attachmentService.uploadAllAttachments(seminar, newAttachments) } - if (request.isImportant && request.titleForMain.isNullOrEmpty()) { - throw CserealException.Csereal400("중요 제목이 입력되어야 합니다") - } - val attachmentResponses = attachmentService.createAttachmentResponses(seminar.attachments) val imageURL = mainImageService.createImageURL(seminar.mainImage) diff --git a/src/test/kotlin/com/wafflestudio/csereal/core/news/NewsServiceTest.kt b/src/test/kotlin/com/wafflestudio/csereal/core/news/NewsServiceTest.kt index 0b280e91..348a64d8 100644 --- a/src/test/kotlin/com/wafflestudio/csereal/core/news/NewsServiceTest.kt +++ b/src/test/kotlin/com/wafflestudio/csereal/core/news/NewsServiceTest.kt @@ -5,6 +5,8 @@ import com.wafflestudio.csereal.core.news.database.NewsRepository import com.wafflestudio.csereal.core.news.dto.NewsDto import com.wafflestudio.csereal.core.news.service.NewsService import io.kotest.core.spec.style.BehaviorSpec +import io.kotest.extensions.spring.SpringTestExtension +import io.kotest.extensions.spring.SpringTestLifecycleMode import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe import org.springframework.boot.test.context.SpringBootTest @@ -17,6 +19,7 @@ class NewsServiceTest( private val newsRepository: NewsRepository ) : BehaviorSpec() { init { + extensions(SpringTestExtension(SpringTestLifecycleMode.Root)) afterSpec { newsRepository.deleteAll() @@ -113,5 +116,88 @@ class NewsServiceTest( } } } + + Given("Slide된 뉴스 2개, Slide되지 않은 뉴스가 2개 있을 때") { + val newsEntitySlide1 = newsRepository.save( + NewsEntity( + title = "title", + titleForMain = null, + description = """ +

Hello, World!

+

This is news description.

+

Goodbye, World!

+ """.trimIndent(), + plainTextDescription = "Hello, World! This is news description. Goodbye, World!", + date = LocalDateTime.now(), + isPrivate = false, + isSlide = true, + isImportant = false + ) + ) + + val newsEntitySlide2 = newsRepository.save( + NewsEntity( + title = "title", + titleForMain = null, + description = """ +

Hello, World!

+

This is news description.

+

Goodbye, World!

+ """.trimIndent(), + plainTextDescription = "Hello, World! This is news description. Goodbye, World!", + date = LocalDateTime.now(), + isPrivate = false, + isSlide = true, + isImportant = false + ) + ) + + val newsEntityNoSlide1 = newsRepository.save( + NewsEntity( + title = "title", + titleForMain = null, + description = """ +

Hello, World!

+

This is news description.

+

Goodbye, World!

+ """.trimIndent(), + plainTextDescription = "Hello, World! This is news description. Goodbye, World!", + date = LocalDateTime.now(), + isPrivate = false, + isSlide = false, + isImportant = false + ) + ) + + val newsEntityNoSlide2 = newsRepository.save( + NewsEntity( + title = "title", + titleForMain = null, + description = """ +

Hello, World!

+

This is news description.

+

Goodbye, World!

+ """.trimIndent(), + plainTextDescription = "Hello, World! This is news description. Goodbye, World!", + date = LocalDateTime.now(), + isPrivate = false, + isSlide = false, + isImportant = false + ) + ) + + When("Slide한 뉴스 2페이지를 가져오면") { + val response = newsService.readAllSlides(1, 1) + + Then("Slide된 뉴스 2개 중 먼저 생성된 1개가 나와야 한다.") { + response.slides.size shouldBe 1 + response.slides.first().id shouldBe newsEntitySlide1.id + } + + Then("총 개수가 2개가 나와야 한다.") { + response.total shouldBe 2 + } + } + } } }