diff --git a/build.gradle b/build.gradle index c3e44697c..c26da7be3 100644 --- a/build.gradle +++ b/build.gradle @@ -1,111 +1,111 @@ plugins { - id 'java' - id 'org.springframework.boot' version '3.2.1' - id 'io.spring.dependency-management' version '1.1.4' + id 'java' + id 'org.springframework.boot' version '3.2.1' + id 'io.spring.dependency-management' version '1.1.4' } group = 'page.clab' version = '0.0.1-SNAPSHOT' jar { - enabled = false + enabled = false } bootJar { - archivesBaseName = "clab" - archiveFileName = "clab.jar" - archiveVersion = "1.0.0" - - if (project.hasProperty('prod')) { - archiveFileName = "clab-prod.jar" - } else if (project.hasProperty('stage')) { - archiveFileName = "clab-stage.jar" - } else { - archiveFileName = "clab.jar" - } + archivesBaseName = "clab" + archiveFileName = "clab.jar" + archiveVersion = "1.0.0" + + if (project.hasProperty('prod')) { + archiveFileName = "clab-prod.jar" + } else if (project.hasProperty('stage')) { + archiveFileName = "clab-stage.jar" + } else { + archiveFileName = "clab.jar" + } } test { - systemProperty 'spring.profiles.active', findProperty('env') ?: 'dev' + systemProperty 'spring.profiles.active', findProperty('env') ?: 'dev' } java { - sourceCompatibility = '21' + sourceCompatibility = '21' } repositories { - mavenCentral() - maven { url 'https://jitpack.io' } + mavenCentral() + maven { url 'https://jitpack.io' } } dependencies { - // Spring Project - implementation 'org.springframework.boot:spring-boot-starter-actuator' // 모니터링 - implementation 'org.springframework.boot:spring-boot-starter-web' // 웹 MVC - implementation 'org.springframework.boot:spring-boot-starter-validation' // 유효성 검사 - implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' // 템플릿 엔진 - implementation 'org.springframework.boot:spring-boot-starter-webflux' // WebFlux - developmentOnly 'org.springframework.boot:spring-boot-devtools' // 개발 도구 - - // Util - compileOnly 'org.projectlombok:lombok' // 롬복 - annotationProcessor 'org.projectlombok:lombok' // 롬복 - implementation 'com.google.code.gson:gson:2.10.1' // JSON 라이브러리 - implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0' // Swagger - implementation 'commons-io:commons-io:2.15.1' // Apache Commons IO - implementation 'com.google.guava:guava:33.0.0-jre' // Google Core Libraries For Java - implementation 'org.springframework.boot:spring-boot-starter-mail' // Spring Mail - implementation 'com.google.zxing:core:3.4.1' // QR 코드 - implementation 'com.google.zxing:javase:3.4.1' // QR 코드 - implementation 'com.slack.api:slack-api-client:1.38.0' // Slack API - implementation 'io.ipinfo:ipinfo-spring:0.3.1' // IPInfo - - - // DB - implementation 'org.postgresql:postgresql:42.7.1' // PostgreSQL JDBC Driver - implementation 'org.springframework.boot:spring-boot-starter-data-redis' // Redis - implementation 'org.springframework.boot:spring-boot-starter-data-jpa' // Spring Data JPA - implementation 'org.springframework.boot:spring-boot-starter-validation' // Hibernate Validator - implementation 'org.hibernate.validator:hibernate-validator:8.0.1.Final' // Bean Validation - implementation 'jakarta.validation:jakarta.validation-api:3.0.2' // Jakarta Bean Validation - - // QueryDSL - implementation 'com.querydsl:querydsl-jpa:5.1.0:jakarta' - annotationProcessor "com.querydsl:querydsl-apt:5.1.0:jakarta" - annotationProcessor "jakarta.annotation:jakarta.annotation-api" - annotationProcessor "jakarta.persistence:jakarta.persistence-api" - - // Security - implementation 'org.springframework.boot:spring-boot-starter-security' // Spring Security - implementation 'com.warrenstrange:googleauth:1.5.0' // Google Authenticator - implementation 'io.jsonwebtoken:jjwt-api:0.11.5' // JWT 라이브러리 - runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' // JWT 구현체 - runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' // JWT Jackson 모듈 - - // XSS Filter - implementation 'com.navercorp.lucy:lucy-xss-servlet:2.0.1' // Lucy XSS Servlet Filter - implementation 'com.navercorp.lucy:lucy-xss:1.6.3' // Lucy XSS Filter - implementation 'org.apache.commons:commons-text:1.11.0' // Apache Commons Text - - // Test - testImplementation 'org.springframework.boot:spring-boot-starter-test' // Spring Boot Test - testImplementation 'org.springframework.security:spring-security-test' // Spring Security Test + // Spring Project + implementation 'org.springframework.boot:spring-boot-starter-actuator' // 모니터링 + implementation 'org.springframework.boot:spring-boot-starter-web' // 웹 MVC + implementation 'org.springframework.boot:spring-boot-starter-validation' // 유효성 검사 + implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' // 템플릿 엔진 + implementation 'org.springframework.boot:spring-boot-starter-webflux' // WebFlux + developmentOnly 'org.springframework.boot:spring-boot-devtools' // 개발 도구 + + // Util + compileOnly 'org.projectlombok:lombok' // 롬복 + annotationProcessor 'org.projectlombok:lombok' // 롬복 + implementation 'com.google.code.gson:gson:2.10.1' // JSON 라이브러리 + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0' // Swagger + implementation 'commons-io:commons-io:2.15.1' // Apache Commons IO + implementation 'com.google.guava:guava:33.0.0-jre' // Google Core Libraries For Java + implementation 'org.springframework.boot:spring-boot-starter-mail' // Spring Mail + implementation 'com.google.zxing:core:3.4.1' // QR 코드 + implementation 'com.google.zxing:javase:3.4.1' // QR 코드 + implementation 'com.slack.api:slack-api-client:1.38.0' // Slack API + implementation 'io.ipinfo:ipinfo-spring:0.3.1' // IPInfo + + + // DB + implementation 'org.postgresql:postgresql:42.7.1' // PostgreSQL JDBC Driver + implementation 'org.springframework.boot:spring-boot-starter-data-redis' // Redis + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' // Spring Data JPA + implementation 'org.springframework.boot:spring-boot-starter-validation' // Hibernate Validator + implementation 'org.hibernate.validator:hibernate-validator:8.0.1.Final' // Bean Validation + implementation 'jakarta.validation:jakarta.validation-api:3.0.2' // Jakarta Bean Validation + + // QueryDSL + implementation 'com.querydsl:querydsl-jpa:5.1.0:jakarta' + annotationProcessor "com.querydsl:querydsl-apt:5.1.0:jakarta" + annotationProcessor "jakarta.annotation:jakarta.annotation-api" + annotationProcessor "jakarta.persistence:jakarta.persistence-api" + + // Security + implementation 'org.springframework.boot:spring-boot-starter-security' // Spring Security + implementation 'com.warrenstrange:googleauth:1.5.0' // Google Authenticator + implementation 'io.jsonwebtoken:jjwt-api:0.11.5' // JWT 라이브러리 + runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' // JWT 구현체 + runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' // JWT Jackson 모듈 + + // XSS Filter + implementation 'com.navercorp.lucy:lucy-xss-servlet:2.0.1' // Lucy XSS Servlet Filter + implementation 'com.navercorp.lucy:lucy-xss:1.6.3' // Lucy XSS Filter + implementation 'org.apache.commons:commons-text:1.11.0' // Apache Commons Text + + // Test + testImplementation 'org.springframework.boot:spring-boot-starter-test' // Spring Boot Test + testImplementation 'org.springframework.security:spring-security-test' // Spring Security Test } tasks.named('test') { - useJUnitPlatform() + useJUnitPlatform() } def querydslDir = "$buildDir/generated/querydsl" sourceSets { - main.java.srcDirs += [ querydslDir ] + main.java.srcDirs += [ querydslDir ] } tasks.withType(JavaCompile) { - options.generatedSourceOutputDirectory = file(querydslDir) + options.generatedSourceOutputDirectory = file(querydslDir) } clean.doLast { - file(querydslDir).deleteDir() + file(querydslDir).deleteDir() } \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..1ef6dbf5d --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,52 @@ +version: '3.8' + +services: + postgresql: + image: postgres + restart: always + environment: + POSTGRES_DB: postgres + POSTGRES_USER: clab + POSTGRES_PASSWORD: clab@1362! + ports: + - "4000:5432" + volumes: + - postgres-data:/var/lib/postgresql/data + + redis: + image: redis + restart: always + command: redis-server --requirepass clab@redis1362! + ports: + - "4001:6379" + volumes: + - redis-data:/data + + jenkins: + image: jenkins/jenkins:lts + user: root + restart: always + ports: + - "5010:8080" + - "5000:5000" + volumes: + - jenkins-data:/var/jenkins_home + + openjdk21: + image: openjdk:21-jdk + command: "/bin/bash" + volumes: + - /var/jenkins_home/workspace/clab-server:/app + + goofy_perlman: + image: openjdk:21-jdk + command: "/bin/bash" + +volumes: + postgres-data: + redis-data: + jenkins-data: + +networks: + default: + driver: bridge \ No newline at end of file diff --git a/src/main/java/page/clab/api/domain/activityGroup/api/ActivityGroupAdminController.java b/src/main/java/page/clab/api/domain/activityGroup/api/ActivityGroupAdminController.java index 87e92a33a..be4fd70f3 100644 --- a/src/main/java/page/clab/api/domain/activityGroup/api/ActivityGroupAdminController.java +++ b/src/main/java/page/clab/api/domain/activityGroup/api/ActivityGroupAdminController.java @@ -24,6 +24,8 @@ import page.clab.api.domain.activityGroup.dto.request.ActivityGroupRequestDto; import page.clab.api.domain.activityGroup.dto.request.ActivityGroupUpdateRequestDto; import page.clab.api.domain.activityGroup.dto.response.ActivityGroupMemberWithApplyReasonResponseDto; +import page.clab.api.domain.activityGroup.dto.response.ActivityGroupResponseDto; +import page.clab.api.domain.award.dto.response.AwardResponseDto; import page.clab.api.global.common.dto.PagedResponseDto; import page.clab.api.global.common.dto.ApiResponse; import page.clab.api.global.exception.PermissionDeniedException; @@ -130,4 +132,16 @@ public ApiResponse acceptGroupMember( return ApiResponse.success(id); } + @GetMapping("/deleted") + @Operation(summary = "[S] 삭제된 활동그룹 조회하기", description = "ROLE_SUPER 이상의 권한이 필요함") + @Secured({"ROLE_SUPER"}) + public ApiResponse> getDeletedActivityGroups( + @RequestParam(name = "page", defaultValue = "0") int page, + @RequestParam(name = "size", defaultValue = "20") int size + ) { + Pageable pageable = PageRequest.of(page, size); + PagedResponseDto activityGroups = activityGroupAdminService.getDeletedActivityGroups(pageable); + return ApiResponse.success(activityGroups); + } + } diff --git a/src/main/java/page/clab/api/domain/activityGroup/api/ActivityGroupBoardController.java b/src/main/java/page/clab/api/domain/activityGroup/api/ActivityGroupBoardController.java index e462a9c32..35c7e3d1f 100644 --- a/src/main/java/page/clab/api/domain/activityGroup/api/ActivityGroupBoardController.java +++ b/src/main/java/page/clab/api/domain/activityGroup/api/ActivityGroupBoardController.java @@ -24,6 +24,7 @@ import page.clab.api.domain.activityGroup.dto.response.ActivityGroupBoardResponseDto; import page.clab.api.domain.activityGroup.dto.response.ActivityGroupBoardUpdateResponseDto; import page.clab.api.domain.activityGroup.dto.response.AssignmentSubmissionWithFeedbackResponseDto; +import page.clab.api.domain.award.dto.response.AwardResponseDto; import page.clab.api.global.common.dto.PagedResponseDto; import page.clab.api.global.common.dto.ApiResponse; import page.clab.api.global.exception.PermissionDeniedException; @@ -137,4 +138,16 @@ public ApiResponse deleteActivityGroupBoard( return ApiResponse.success(id); } + @GetMapping("/deleted") + @Operation(summary = "[S] 삭제된 활동 그룹 게시판 조회하기", description = "ROLE_SUPER 이상의 권한이 필요함") + @Secured({"ROLE_SUPER"}) + public ApiResponse> getDeletedActivityGroupBoards( + @RequestParam(name = "page", defaultValue = "0") int page, + @RequestParam(name = "size", defaultValue = "20") int size + ) { + Pageable pageable = PageRequest.of(page, size); + PagedResponseDto activityBoards = activityGroupBoardService.getDeletedActivityGroupBoards(pageable); + return ApiResponse.success(activityBoards); + } + } \ No newline at end of file diff --git a/src/main/java/page/clab/api/domain/activityGroup/api/ActivityGroupReportController.java b/src/main/java/page/clab/api/domain/activityGroup/api/ActivityGroupReportController.java index 5c63ab1f9..5c7f9af70 100644 --- a/src/main/java/page/clab/api/domain/activityGroup/api/ActivityGroupReportController.java +++ b/src/main/java/page/clab/api/domain/activityGroup/api/ActivityGroupReportController.java @@ -21,8 +21,8 @@ import page.clab.api.domain.activityGroup.dto.request.ActivityGroupReportRequestDto; import page.clab.api.domain.activityGroup.dto.request.ActivityGroupReportUpdateRequestDto; import page.clab.api.domain.activityGroup.dto.response.ActivityGroupReportResponseDto; -import page.clab.api.global.common.dto.PagedResponseDto; import page.clab.api.global.common.dto.ApiResponse; +import page.clab.api.global.common.dto.PagedResponseDto; import page.clab.api.global.exception.PermissionDeniedException; @RestController @@ -90,4 +90,17 @@ public ApiResponse deleteAward( return ApiResponse.success(id); } + + @GetMapping("/deleted") + @Operation(summary = "[S] 삭제된 활동보고서 조회하기", description = "ROLE_SUPER 이상의 권한이 필요함") + @Secured({"ROLE_SUPER"}) + public ApiResponse> getDeletedActivityGroupReports( + @RequestParam(name = "page", defaultValue = "0") int page, + @RequestParam(name = "size", defaultValue = "20") int size + ) { + Pageable pageable = PageRequest.of(page, size); + PagedResponseDto activityGroupReports = activityGroupReportService.getDeletedActivityGroupReports(pageable); + return ApiResponse.success(activityGroupReports); + } + } diff --git a/src/main/java/page/clab/api/domain/activityGroup/application/ActivityGroupAdminService.java b/src/main/java/page/clab/api/domain/activityGroup/application/ActivityGroupAdminService.java index d68c0dd7b..1c716c78b 100644 --- a/src/main/java/page/clab/api/domain/activityGroup/application/ActivityGroupAdminService.java +++ b/src/main/java/page/clab/api/domain/activityGroup/application/ActivityGroupAdminService.java @@ -20,6 +20,7 @@ import page.clab.api.domain.activityGroup.dto.request.ActivityGroupRequestDto; import page.clab.api.domain.activityGroup.dto.request.ActivityGroupUpdateRequestDto; import page.clab.api.domain.activityGroup.dto.response.ActivityGroupMemberWithApplyReasonResponseDto; +import page.clab.api.domain.activityGroup.dto.response.ActivityGroupResponseDto; import page.clab.api.domain.member.application.MemberService; import page.clab.api.domain.member.domain.Member; import page.clab.api.domain.notification.application.NotificationService; @@ -91,11 +92,17 @@ public Long manageActivityGroup(Long activityGroupId, ActivityGroupStatus status return activityGroup.getId(); } + @Transactional(readOnly = true) + public PagedResponseDto getDeletedActivityGroups(Pageable pageable) { + Page activityGroups = activityGroupRepository.findAllByIsDeletedTrue(pageable); + return new PagedResponseDto<>(activityGroups.map(ActivityGroupResponseDto::toDto)); + } + @Transactional public Long deleteActivityGroup(Long activityGroupId) { + ActivityGroup activityGroup = getActivityGroupByIdOrThrow(activityGroupId); List groupMembers = activityGroupMemberService.getGroupMemberByActivityGroupId(activityGroupId); List groupSchedules = groupScheduleRepository.findAllByActivityGroupIdOrderByIdDesc(activityGroupId); - ActivityGroup activityGroup = getActivityGroupByIdOrThrow(activityGroupId); GroupMember groupLeader = activityGroupMemberService.getGroupMemberByActivityGroupIdAndRole(activityGroupId, ActivityGroupRole.LEADER); activityGroupMemberService.deleteAll(groupMembers); diff --git a/src/main/java/page/clab/api/domain/activityGroup/application/ActivityGroupBoardService.java b/src/main/java/page/clab/api/domain/activityGroup/application/ActivityGroupBoardService.java index 7119beaf5..1baba30db 100644 --- a/src/main/java/page/clab/api/domain/activityGroup/application/ActivityGroupBoardService.java +++ b/src/main/java/page/clab/api/domain/activityGroup/application/ActivityGroupBoardService.java @@ -21,6 +21,8 @@ import page.clab.api.domain.activityGroup.dto.response.AssignmentSubmissionWithFeedbackResponseDto; import page.clab.api.domain.activityGroup.dto.response.FeedbackResponseDto; import page.clab.api.domain.activityGroup.exception.InvalidParentBoardException; +import page.clab.api.domain.award.domain.Award; +import page.clab.api.domain.award.dto.response.AwardResponseDto; import page.clab.api.domain.member.application.MemberService; import page.clab.api.domain.member.domain.Member; import page.clab.api.domain.notification.application.NotificationService; @@ -145,6 +147,12 @@ public Long deleteActivityGroupBoard(Long activityGroupBoardId) throws Permissio return board.getId(); } + @Transactional(readOnly = true) + public PagedResponseDto getDeletedActivityGroupBoards(Pageable pageable) { + Page activityGroupBoards = activityGroupBoardRepository.findAllByIsDeletedTrue(pageable); + return new PagedResponseDto<>(activityGroupBoards.map(ActivityGroupBoardResponseDto::toDto)); + } + private ActivityGroupBoard getActivityGroupBoardByIdOrThrow(Long activityGroupBoardId) { return activityGroupBoardRepository.findById(activityGroupBoardId) .orElseThrow(() -> new NotFoundException("해당 게시글을 찾을 수 없습니다.")); diff --git a/src/main/java/page/clab/api/domain/activityGroup/application/ActivityGroupReportService.java b/src/main/java/page/clab/api/domain/activityGroup/application/ActivityGroupReportService.java index 567113bce..d8cf467e3 100644 --- a/src/main/java/page/clab/api/domain/activityGroup/application/ActivityGroupReportService.java +++ b/src/main/java/page/clab/api/domain/activityGroup/application/ActivityGroupReportService.java @@ -75,6 +75,12 @@ public Long deleteReport(Long reportId) throws PermissionDeniedException { return report.getId(); } + @Transactional(readOnly = true) + public PagedResponseDto getDeletedActivityGroupReports(Pageable pageable) { + Page activityGroupReports = activityGroupReportRepository.findAllByIsDeletedTrue(pageable); + return new PagedResponseDto<>(activityGroupReports.map(ActivityGroupReportResponseDto::toDto)); + } + public ActivityGroupReport getReportByIdOrThrow(Long reportId) { return activityGroupReportRepository.findById(reportId) .orElseThrow(() -> new NotFoundException("활동 보고서를 찾을 수 없습니다.")); diff --git a/src/main/java/page/clab/api/domain/activityGroup/dao/ActivityGroupBoardRepository.java b/src/main/java/page/clab/api/domain/activityGroup/dao/ActivityGroupBoardRepository.java index 5b0ca23b0..9476ebffa 100644 --- a/src/main/java/page/clab/api/domain/activityGroup/dao/ActivityGroupBoardRepository.java +++ b/src/main/java/page/clab/api/domain/activityGroup/dao/ActivityGroupBoardRepository.java @@ -3,6 +3,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import org.springframework.data.querydsl.QuerydslPredicateExecutor; import org.springframework.stereotype.Repository; import page.clab.api.domain.activityGroup.domain.ActivityGroupBoard; @@ -21,4 +22,7 @@ public interface ActivityGroupBoardRepository extends JpaRepository findAllChildrenByParentId(Long activityGroupBoardId); + @Query(value = "SELECT a.* FROM activity_group_board a WHERE a.is_deleted = true", nativeQuery = true) + Page findAllByIsDeletedTrue(Pageable pageable); + } \ No newline at end of file diff --git a/src/main/java/page/clab/api/domain/activityGroup/dao/ActivityGroupReportRepository.java b/src/main/java/page/clab/api/domain/activityGroup/dao/ActivityGroupReportRepository.java index 2bdac0bee..3b2ac3eb0 100644 --- a/src/main/java/page/clab/api/domain/activityGroup/dao/ActivityGroupReportRepository.java +++ b/src/main/java/page/clab/api/domain/activityGroup/dao/ActivityGroupReportRepository.java @@ -3,6 +3,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; import page.clab.api.domain.activityGroup.domain.ActivityGroup; import page.clab.api.domain.activityGroup.domain.ActivityGroupReport; @@ -16,4 +17,7 @@ public interface ActivityGroupReportRepository extends JpaRepository findAllByIsDeletedTrue(Pageable pageable); + } diff --git a/src/main/java/page/clab/api/domain/activityGroup/dao/ActivityGroupRepository.java b/src/main/java/page/clab/api/domain/activityGroup/dao/ActivityGroupRepository.java index 9db96c827..bcf619698 100644 --- a/src/main/java/page/clab/api/domain/activityGroup/dao/ActivityGroupRepository.java +++ b/src/main/java/page/clab/api/domain/activityGroup/dao/ActivityGroupRepository.java @@ -3,6 +3,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import org.springframework.data.querydsl.QuerydslPredicateExecutor; import org.springframework.stereotype.Repository; import page.clab.api.domain.activityGroup.domain.ActivityGroup; @@ -15,4 +16,7 @@ public interface ActivityGroupRepository extends JpaRepository findAllByCategoryOrderByCreatedAtDesc(ActivityGroupCategory category, Pageable pageable); + @Query(value = "SELECT a.* FROM activity_group a WHERE a.is_deleted = true", nativeQuery = true) + Page findAllByIsDeletedTrue(Pageable pageable); + } diff --git a/src/main/java/page/clab/api/domain/activityGroup/domain/ActivityGroup.java b/src/main/java/page/clab/api/domain/activityGroup/domain/ActivityGroup.java index b941d24e6..a439a3c05 100644 --- a/src/main/java/page/clab/api/domain/activityGroup/domain/ActivityGroup.java +++ b/src/main/java/page/clab/api/domain/activityGroup/domain/ActivityGroup.java @@ -14,6 +14,8 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.SQLRestriction; import org.hibernate.validator.constraints.Range; import org.hibernate.validator.constraints.URL; import page.clab.api.domain.activityGroup.dto.request.ActivityGroupUpdateRequestDto; @@ -29,6 +31,8 @@ @Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor(access = AccessLevel.PRIVATE) +@SQLDelete(sql = "UPDATE activity_group SET is_deleted = true WHERE id = ?") +@SQLRestriction("is_deleted = false") public class ActivityGroup extends BaseEntity { @Id diff --git a/src/main/java/page/clab/api/domain/activityGroup/domain/ActivityGroupBoard.java b/src/main/java/page/clab/api/domain/activityGroup/domain/ActivityGroupBoard.java index 75fac6880..8f7843b22 100644 --- a/src/main/java/page/clab/api/domain/activityGroup/domain/ActivityGroupBoard.java +++ b/src/main/java/page/clab/api/domain/activityGroup/domain/ActivityGroupBoard.java @@ -18,6 +18,8 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.SQLRestriction; import page.clab.api.domain.activityGroup.dto.request.ActivityGroupBoardUpdateRequestDto; import page.clab.api.domain.member.domain.Member; import page.clab.api.global.common.domain.BaseEntity; @@ -36,6 +38,8 @@ @Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor(access = AccessLevel.PRIVATE) +@SQLDelete(sql = "UPDATE activity_group_board SET is_deleted = true WHERE id = ?") +@SQLRestriction("is_deleted = false") public class ActivityGroupBoard extends BaseEntity { @Id diff --git a/src/main/java/page/clab/api/domain/activityGroup/domain/ActivityGroupReport.java b/src/main/java/page/clab/api/domain/activityGroup/domain/ActivityGroupReport.java index 64526da5a..f98918023 100644 --- a/src/main/java/page/clab/api/domain/activityGroup/domain/ActivityGroupReport.java +++ b/src/main/java/page/clab/api/domain/activityGroup/domain/ActivityGroupReport.java @@ -13,6 +13,8 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.SQLRestriction; import page.clab.api.domain.activityGroup.dto.request.ActivityGroupReportUpdateRequestDto; import page.clab.api.global.common.domain.BaseEntity; @@ -24,6 +26,8 @@ @Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor(access = AccessLevel.PRIVATE) +@SQLDelete(sql = "UPDATE activity_group_report SET is_deleted = true WHERE id = ?") +@SQLRestriction("is_deleted = false") public class ActivityGroupReport extends BaseEntity { @Id diff --git a/src/main/java/page/clab/api/domain/application/api/ApplicationController.java b/src/main/java/page/clab/api/domain/application/api/ApplicationController.java index 7b5e6c8d3..c5ff1a9de 100644 --- a/src/main/java/page/clab/api/domain/application/api/ApplicationController.java +++ b/src/main/java/page/clab/api/domain/application/api/ApplicationController.java @@ -22,6 +22,7 @@ import page.clab.api.domain.application.dto.request.ApplicationRequestDto; import page.clab.api.domain.application.dto.response.ApplicationPassResponseDto; import page.clab.api.domain.application.dto.response.ApplicationResponseDto; +import page.clab.api.domain.board.dto.response.BoardListResponseDto; import page.clab.api.global.common.dto.PagedResponseDto; import page.clab.api.global.common.dto.ApiResponse; @@ -94,4 +95,16 @@ public ApiResponse deleteApplication( return ApiResponse.success(id); } + @GetMapping("/deleted") + @Operation(summary = "[S] 삭제된 지원서 조회하기", description = "ROLE_SUPER 이상의 권한이 필요함") + @Secured({"ROLE_SUPER"}) + public ApiResponse> getDeletedBoards( + @RequestParam(name = "page", defaultValue = "0") int page, + @RequestParam(name = "size", defaultValue = "20") int size + ) { + Pageable pageable = PageRequest.of(page, size); + PagedResponseDto applications = applicationService.getDeletedApplications(pageable); + return ApiResponse.success(applications); + } + } diff --git a/src/main/java/page/clab/api/domain/application/application/ApplicationService.java b/src/main/java/page/clab/api/domain/application/application/ApplicationService.java index 9fb70c264..c3b5e989c 100644 --- a/src/main/java/page/clab/api/domain/application/application/ApplicationService.java +++ b/src/main/java/page/clab/api/domain/application/application/ApplicationService.java @@ -43,7 +43,7 @@ public String createApplication(HttpServletRequest request, ApplicationRequestDt validationService.checkValid(application); notificationService.sendNotificationToAdmins(requestDto.getStudentId() + " " + - requestDto.getName() + "님이 동아리에 지원하였습니다."); + requestDto.getName() + "님이 동아리에 지원하였습니다."); slackService.sendApplicationNotification(request, requestDto); return applicationRepository.save(application).getStudentId(); } @@ -78,6 +78,11 @@ public String deleteApplication(Long recruitmentId, String studentId) { return application.getStudentId(); } + public PagedResponseDto getDeletedApplications(Pageable pageable) { + Page applications = applicationRepository.findAllByIsDeletedTrue(pageable); + return new PagedResponseDto<>(applications.map(ApplicationResponseDto::toDto)); + } + private Application getApplicationByIdOrThrow(String studentId, Long recruitmentId) { ApplicationId id = ApplicationId.create(studentId, recruitmentId); return applicationRepository.findById(id) diff --git a/src/main/java/page/clab/api/domain/application/dao/ApplicationRepository.java b/src/main/java/page/clab/api/domain/application/dao/ApplicationRepository.java index e3e31e29b..464260c32 100644 --- a/src/main/java/page/clab/api/domain/application/dao/ApplicationRepository.java +++ b/src/main/java/page/clab/api/domain/application/dao/ApplicationRepository.java @@ -3,6 +3,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import org.springframework.data.querydsl.QuerydslPredicateExecutor; import page.clab.api.domain.application.domain.Application; import page.clab.api.domain.application.domain.ApplicationId; @@ -18,4 +19,7 @@ public interface ApplicationRepository extends JpaRepository findByRecruitmentIdAndStudentId(Long recruitmentId, String studentId); + @Query(value = "SELECT a.* FROM application a WHERE a.is_deleted = true", nativeQuery = true) + Page findAllByIsDeletedTrue(Pageable pageable); + } diff --git a/src/main/java/page/clab/api/domain/application/domain/Application.java b/src/main/java/page/clab/api/domain/application/domain/Application.java index a6d6d0a94..5726a6bb9 100644 --- a/src/main/java/page/clab/api/domain/application/domain/Application.java +++ b/src/main/java/page/clab/api/domain/application/domain/Application.java @@ -17,6 +17,8 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.SQLRestriction; import org.hibernate.validator.constraints.URL; import page.clab.api.domain.member.domain.Member; import page.clab.api.domain.member.domain.Role; @@ -32,6 +34,8 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor(access = AccessLevel.PRIVATE) @IdClass(ApplicationId.class) +@SQLDelete(sql = "UPDATE application SET is_deleted = true WHERE recruitment_id = ? AND student_id = ?") +@SQLRestriction("is_deleted = false") public class Application extends BaseEntity { @Id diff --git a/src/main/java/page/clab/api/domain/award/api/AwardController.java b/src/main/java/page/clab/api/domain/award/api/AwardController.java index 054ef2818..0f8e954cd 100644 --- a/src/main/java/page/clab/api/domain/award/api/AwardController.java +++ b/src/main/java/page/clab/api/domain/award/api/AwardController.java @@ -21,8 +21,8 @@ import page.clab.api.domain.award.dto.request.AwardRequestDto; import page.clab.api.domain.award.dto.request.AwardUpdateRequestDto; import page.clab.api.domain.award.dto.response.AwardResponseDto; -import page.clab.api.global.common.dto.PagedResponseDto; import page.clab.api.global.common.dto.ApiResponse; +import page.clab.api.global.common.dto.PagedResponseDto; import page.clab.api.global.exception.PermissionDeniedException; @RestController @@ -95,4 +95,16 @@ public ApiResponse deleteAward( return ApiResponse.success(id); } + @GetMapping("/deleted") + @Operation(summary = "[S] 삭제된 수상이력 조회하기", description = "ROLE_SUPER 이상의 권한이 필요함") + @Secured({"ROLE_SUPER"}) + public ApiResponse> getDeletedAwards( + @RequestParam(name = "page", defaultValue = "0") int page, + @RequestParam(name = "size", defaultValue = "20") int size + ) { + Pageable pageable = PageRequest.of(page, size); + PagedResponseDto awards = awardService.getDeletedAwards(pageable); + return ApiResponse.success(awards); + } + } \ No newline at end of file diff --git a/src/main/java/page/clab/api/domain/award/application/AwardService.java b/src/main/java/page/clab/api/domain/award/application/AwardService.java index 20016de58..3ade96d03 100644 --- a/src/main/java/page/clab/api/domain/award/application/AwardService.java +++ b/src/main/java/page/clab/api/domain/award/application/AwardService.java @@ -66,6 +66,12 @@ public Long deleteAward(Long awardId) throws PermissionDeniedException { return award.getId(); } + @Transactional(readOnly = true) + public PagedResponseDto getDeletedAwards(Pageable pageable) { + Page awards = awardRepository.findAllByIsDeletedTrue(pageable); + return new PagedResponseDto<>(awards.map(AwardResponseDto::toDto)); + } + private Award getAwardByIdOrThrow(Long awardId) { return awardRepository.findById(awardId) .orElseThrow(() -> new NotFoundException("해당 수상 이력이 존재하지 않습니다.")); diff --git a/src/main/java/page/clab/api/domain/award/dao/AwardRepository.java b/src/main/java/page/clab/api/domain/award/dao/AwardRepository.java index 5a82420ee..bdcc318b3 100644 --- a/src/main/java/page/clab/api/domain/award/dao/AwardRepository.java +++ b/src/main/java/page/clab/api/domain/award/dao/AwardRepository.java @@ -3,6 +3,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import org.springframework.data.querydsl.QuerydslPredicateExecutor; import org.springframework.stereotype.Repository; import page.clab.api.domain.award.domain.Award; @@ -10,6 +11,10 @@ @Repository public interface AwardRepository extends JpaRepository, AwardRepositoryCustom, QuerydslPredicateExecutor { + Page findAllByMemberOrderByAwardDateDesc(Member member, Pageable pageable); + @Query(value = "SELECT a.* FROM award a WHERE a.is_deleted = true", nativeQuery = true) + Page findAllByIsDeletedTrue(Pageable pageable); + } diff --git a/src/main/java/page/clab/api/domain/award/domain/Award.java b/src/main/java/page/clab/api/domain/award/domain/Award.java index 9c7ca28a8..d9f7646e5 100644 --- a/src/main/java/page/clab/api/domain/award/domain/Award.java +++ b/src/main/java/page/clab/api/domain/award/domain/Award.java @@ -14,6 +14,8 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.SQLRestriction; import page.clab.api.domain.award.dto.request.AwardUpdateRequestDto; import page.clab.api.domain.member.domain.Member; import page.clab.api.global.common.domain.BaseEntity; @@ -28,6 +30,8 @@ @Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor(access = AccessLevel.PRIVATE) +@SQLDelete(sql = "UPDATE award SET is_deleted = true WHERE id = ?") +@SQLRestriction("is_deleted = false") public class Award extends BaseEntity { @Id diff --git a/src/main/java/page/clab/api/domain/blog/api/BlogController.java b/src/main/java/page/clab/api/domain/blog/api/BlogController.java index 3483acbf0..9b9ca209e 100644 --- a/src/main/java/page/clab/api/domain/blog/api/BlogController.java +++ b/src/main/java/page/clab/api/domain/blog/api/BlogController.java @@ -22,8 +22,8 @@ import page.clab.api.domain.blog.dto.request.BlogUpdateRequestDto; import page.clab.api.domain.blog.dto.response.BlogDetailsResponseDto; import page.clab.api.domain.blog.dto.response.BlogResponseDto; -import page.clab.api.global.common.dto.PagedResponseDto; import page.clab.api.global.common.dto.ApiResponse; +import page.clab.api.global.common.dto.PagedResponseDto; import page.clab.api.global.exception.PermissionDeniedException; @RestController @@ -92,4 +92,16 @@ public ApiResponse deleteBlog( return ApiResponse.success(id); } + @GetMapping("/deleted") + @Operation(summary = "[S] 삭제된 블로그 조회하기", description = "ROLE_SUPER 이상의 권한이 필요함") + @Secured({"ROLE_SUPER"}) + public ApiResponse> getDeletedBlogs( + @RequestParam(name = "page", defaultValue = "0") int page, + @RequestParam(name = "size", defaultValue = "20") int size + ) { + Pageable pageable = PageRequest.of(page, size); + PagedResponseDto blogs = blogService.getDeletedBlogs(pageable); + return ApiResponse.success(blogs); + } + } diff --git a/src/main/java/page/clab/api/domain/blog/application/BlogService.java b/src/main/java/page/clab/api/domain/blog/application/BlogService.java index 445277f4c..0abc22dac 100644 --- a/src/main/java/page/clab/api/domain/blog/application/BlogService.java +++ b/src/main/java/page/clab/api/domain/blog/application/BlogService.java @@ -50,6 +50,14 @@ public BlogDetailsResponseDto getBlogDetails(Long blogId) { return BlogDetailsResponseDto.toDto(blog, isOwner); } + @Transactional(readOnly = true) + public PagedResponseDto getDeletedBlogs(Pageable pageable) { + Member currentMember = memberService.getCurrentMember(); + Page blogs = blogRepository.findAllByIsDeletedTrue(pageable); + return new PagedResponseDto<>(blogs + .map(blog -> BlogDetailsResponseDto.toDto(blog, blog.isOwner(currentMember)))); + } + @Transactional public Long updateBlog(Long blogId, BlogUpdateRequestDto requestDto) throws PermissionDeniedException { Member currentMember = memberService.getCurrentMember(); diff --git a/src/main/java/page/clab/api/domain/blog/dao/BlogRepository.java b/src/main/java/page/clab/api/domain/blog/dao/BlogRepository.java index b49e33e3a..57c5f8720 100644 --- a/src/main/java/page/clab/api/domain/blog/dao/BlogRepository.java +++ b/src/main/java/page/clab/api/domain/blog/dao/BlogRepository.java @@ -3,6 +3,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import org.springframework.data.querydsl.QuerydslPredicateExecutor; import org.springframework.stereotype.Repository; import page.clab.api.domain.blog.domain.Blog; @@ -12,4 +13,7 @@ public interface BlogRepository extends JpaRepository, BlogRepositor Page findAllByOrderByCreatedAtDesc(Pageable pageable); + @Query(value = "SELECT b.* FROM blog b WHERE b.is_deleted = true", nativeQuery = true) + Page findAllByIsDeletedTrue(Pageable pageable); + } diff --git a/src/main/java/page/clab/api/domain/blog/domain/Blog.java b/src/main/java/page/clab/api/domain/blog/domain/Blog.java index 5b85ed340..02f1cea59 100644 --- a/src/main/java/page/clab/api/domain/blog/domain/Blog.java +++ b/src/main/java/page/clab/api/domain/blog/domain/Blog.java @@ -14,6 +14,8 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.SQLRestriction; import page.clab.api.domain.blog.dto.request.BlogUpdateRequestDto; import page.clab.api.domain.member.domain.Member; import page.clab.api.global.common.domain.BaseEntity; @@ -27,6 +29,8 @@ @Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor(access = AccessLevel.PRIVATE) +@SQLDelete(sql = "UPDATE blog SET is_deleted = true WHERE id = ?") +@SQLRestriction("is_deleted = false") public class Blog extends BaseEntity { @Id diff --git a/src/main/java/page/clab/api/domain/board/api/BoardController.java b/src/main/java/page/clab/api/domain/board/api/BoardController.java index c3ec4c831..1e633370f 100644 --- a/src/main/java/page/clab/api/domain/board/api/BoardController.java +++ b/src/main/java/page/clab/api/domain/board/api/BoardController.java @@ -25,8 +25,8 @@ import page.clab.api.domain.board.dto.response.BoardDetailsResponseDto; import page.clab.api.domain.board.dto.response.BoardListResponseDto; import page.clab.api.domain.board.dto.response.BoardMyResponseDto; -import page.clab.api.global.common.dto.PagedResponseDto; import page.clab.api.global.common.dto.ApiResponse; +import page.clab.api.global.common.dto.PagedResponseDto; import page.clab.api.global.exception.PermissionDeniedException; @RestController @@ -126,4 +126,16 @@ public ApiResponse toggleLikeStatus( return ApiResponse.success(id); } + @GetMapping("/deleted") + @Operation(summary = "[S] 삭제된 커뮤니티 게시글 조회하기", description = "ROLE_SUPER 이상의 권한이 필요함") + @Secured({"ROLE_SUPER"}) + public ApiResponse> getDeletedBoards( + @RequestParam(name = "page", defaultValue = "0") int page, + @RequestParam(name = "size", defaultValue = "20") int size + ) { + Pageable pageable = PageRequest.of(page, size); + PagedResponseDto boards = boardService.getDeletedBoards(pageable); + return ApiResponse.success(boards); + } + } diff --git a/src/main/java/page/clab/api/domain/board/application/BoardService.java b/src/main/java/page/clab/api/domain/board/application/BoardService.java index 1811120a5..143e111a0 100644 --- a/src/main/java/page/clab/api/domain/board/application/BoardService.java +++ b/src/main/java/page/clab/api/domain/board/application/BoardService.java @@ -117,6 +117,12 @@ public Long toggleLikeStatus(Long boardId) { return board.getLikes(); } + @Transactional(readOnly = true) + public PagedResponseDto getDeletedBoards(Pageable pageable) { + Page boards = boardRepository.findAllByIsDeletedTrue(pageable); + return new PagedResponseDto<>(boards.map(this::mapToBoardListResponseDto)); + } + public Long deleteBoard(Long boardId) throws PermissionDeniedException { Member currentMember = memberService.getCurrentMember(); Board board = getBoardByIdOrThrow(boardId); diff --git a/src/main/java/page/clab/api/domain/board/dao/BoardRepository.java b/src/main/java/page/clab/api/domain/board/dao/BoardRepository.java index 1d81140e6..2bf12a880 100644 --- a/src/main/java/page/clab/api/domain/board/dao/BoardRepository.java +++ b/src/main/java/page/clab/api/domain/board/dao/BoardRepository.java @@ -3,6 +3,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; import page.clab.api.domain.board.domain.Board; import page.clab.api.domain.board.domain.BoardCategory; @@ -17,4 +18,7 @@ public interface BoardRepository extends JpaRepository { Page findAllByCategoryOrderByCreatedAtDesc(BoardCategory category, Pageable pageable); + @Query(value = "SELECT b.* FROM board b WHERE b.is_deleted = true", nativeQuery = true) + Page findAllByIsDeletedTrue(Pageable pageable); + } diff --git a/src/main/java/page/clab/api/domain/board/domain/Board.java b/src/main/java/page/clab/api/domain/board/domain/Board.java index 385bae250..99847c588 100644 --- a/src/main/java/page/clab/api/domain/board/domain/Board.java +++ b/src/main/java/page/clab/api/domain/board/domain/Board.java @@ -18,6 +18,8 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.SQLRestriction; import page.clab.api.domain.board.dto.request.BoardUpdateRequestDto; import page.clab.api.domain.member.domain.Member; import page.clab.api.domain.member.domain.Role; @@ -34,6 +36,8 @@ @Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor(access = AccessLevel.PRIVATE) +@SQLDelete(sql = "UPDATE board SET is_deleted = true WHERE id = ?") +@SQLRestriction("is_deleted = false") public class Board extends BaseEntity { @Id diff --git a/src/main/java/page/clab/api/domain/book/api/BookController.java b/src/main/java/page/clab/api/domain/book/api/BookController.java index 22c189f34..2279b3c66 100644 --- a/src/main/java/page/clab/api/domain/book/api/BookController.java +++ b/src/main/java/page/clab/api/domain/book/api/BookController.java @@ -22,8 +22,8 @@ import page.clab.api.domain.book.dto.request.BookUpdateRequestDto; import page.clab.api.domain.book.dto.response.BookDetailsResponseDto; import page.clab.api.domain.book.dto.response.BookResponseDto; -import page.clab.api.global.common.dto.PagedResponseDto; import page.clab.api.global.common.dto.ApiResponse; +import page.clab.api.global.common.dto.PagedResponseDto; @RestController @RequestMapping("/api/v1/books") @@ -94,4 +94,16 @@ public ApiResponse deleteBook( return ApiResponse.success(id); } + @GetMapping("/deleted") + @Operation(summary = "[S] 삭제된 도서 조회하기", description = "ROLE_SUPER 이상의 권한이 필요함") + @Secured({"ROLE_SUPER"}) + public ApiResponse> getDeletedBooks( + @RequestParam(name = "page", defaultValue = "0") int page, + @RequestParam(name = "size", defaultValue = "20") int size + ) { + Pageable pageable = PageRequest.of(page, size); + PagedResponseDto books = bookService.getDeletedBooks(pageable); + return ApiResponse.success(books); + } + } diff --git a/src/main/java/page/clab/api/domain/book/application/BookService.java b/src/main/java/page/clab/api/domain/book/application/BookService.java index e8f6722aa..224104e2c 100644 --- a/src/main/java/page/clab/api/domain/book/application/BookService.java +++ b/src/main/java/page/clab/api/domain/book/application/BookService.java @@ -46,6 +46,12 @@ public BookDetailsResponseDto getBookDetails(Long bookId) { return mapToBookDetailsResponseDto(book); } + @Transactional(readOnly = true) + public PagedResponseDto getDeletedBooks(Pageable pageable) { + Page books = bookRepository.findAllByIsDeletedTrue(pageable); + return new PagedResponseDto<>(books.map(this::mapToBookDetailsResponseDto)); + } + @Transactional public Long updateBookInfo(Long bookId, BookUpdateRequestDto bookUpdateRequestDto) { Book book = getBookByIdOrThrow(bookId); @@ -55,7 +61,8 @@ public Long updateBookInfo(Long bookId, BookUpdateRequestDto bookUpdateRequestDt public Long deleteBook(Long bookId) { Book book = getBookByIdOrThrow(bookId); - bookRepository.delete(book); + book.updateIsDeleted(true); + bookRepository.save(book); return book.getId(); } diff --git a/src/main/java/page/clab/api/domain/book/dao/BookRepository.java b/src/main/java/page/clab/api/domain/book/dao/BookRepository.java index c25f38ef4..2944b2ec6 100644 --- a/src/main/java/page/clab/api/domain/book/dao/BookRepository.java +++ b/src/main/java/page/clab/api/domain/book/dao/BookRepository.java @@ -1,6 +1,9 @@ package page.clab.api.domain.book.dao; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import org.springframework.data.querydsl.QuerydslPredicateExecutor; import page.clab.api.domain.book.domain.Book; import page.clab.api.domain.member.domain.Member; @@ -13,4 +16,7 @@ public interface BookRepository extends JpaRepository, BookRepositor int countByBorrower(Member member); + @Query(value = "SELECT b.* FROM book b WHERE b.is_deleted = true", nativeQuery = true) + Page findAllByIsDeletedTrue(Pageable pageable); + } diff --git a/src/main/java/page/clab/api/domain/book/domain/Book.java b/src/main/java/page/clab/api/domain/book/domain/Book.java index ab6fd3f49..03e451f53 100644 --- a/src/main/java/page/clab/api/domain/book/domain/Book.java +++ b/src/main/java/page/clab/api/domain/book/domain/Book.java @@ -15,6 +15,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.hibernate.annotations.SQLRestriction; import page.clab.api.domain.book.dto.request.BookUpdateRequestDto; import page.clab.api.domain.book.exception.BookAlreadyBorrowedException; import page.clab.api.domain.book.exception.InvalidBorrowerException; @@ -31,6 +32,7 @@ @Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor(access = AccessLevel.PRIVATE) +@SQLRestriction("is_deleted = false") public class Book extends BaseEntity { @Id @@ -71,6 +73,10 @@ public void update(BookUpdateRequestDto requestDto) { Optional.ofNullable(requestDto.getReviewLinks()).ifPresent(this::setReviewLinks); } + public void updateIsDeleted(Boolean isDeleted) { + this.isDeleted = isDeleted; + } + public void validateBookIsNotBorrowed() { if (this.borrower != null) { throw new BookAlreadyBorrowedException("이미 대출 중인 도서입니다."); diff --git a/src/main/java/page/clab/api/domain/comment/api/CommentController.java b/src/main/java/page/clab/api/domain/comment/api/CommentController.java index 20709019b..6075f765c 100644 --- a/src/main/java/page/clab/api/domain/comment/api/CommentController.java +++ b/src/main/java/page/clab/api/domain/comment/api/CommentController.java @@ -22,8 +22,9 @@ import page.clab.api.domain.comment.dto.request.CommentUpdateRequestDto; import page.clab.api.domain.comment.dto.response.CommentMyResponseDto; import page.clab.api.domain.comment.dto.response.CommentResponseDto; -import page.clab.api.global.common.dto.PagedResponseDto; +import page.clab.api.domain.comment.dto.response.DeletedCommentResponseDto; import page.clab.api.global.common.dto.ApiResponse; +import page.clab.api.global.common.dto.PagedResponseDto; import page.clab.api.global.exception.PermissionDeniedException; @RestController @@ -103,4 +104,17 @@ public ApiResponse toggleLikeStatus( return ApiResponse.success(id); } + @GetMapping("/deleted/{boardId}") + @Operation(summary = "[S] 게시글의 삭제된 댓글 조회하기", description = "ROLE_SUPER 이상의 권한이 필요함") + @Secured({"ROLE_SUPER"}) + public ApiResponse> getDeletedComments( + @PathVariable(name = "boardId") Long boardId, + @RequestParam(name = "page", defaultValue = "0") int page, + @RequestParam(name = "size", defaultValue = "20") int size + ) { + Pageable pageable = PageRequest.of(page, size); + PagedResponseDto comments = commentService.getDeletedComments(boardId, pageable); + return ApiResponse.success(comments); + } + } \ No newline at end of file diff --git a/src/main/java/page/clab/api/domain/comment/application/CommentService.java b/src/main/java/page/clab/api/domain/comment/application/CommentService.java index 18313f8e5..d890ff4d4 100644 --- a/src/main/java/page/clab/api/domain/comment/application/CommentService.java +++ b/src/main/java/page/clab/api/domain/comment/application/CommentService.java @@ -17,6 +17,7 @@ import page.clab.api.domain.comment.dto.request.CommentUpdateRequestDto; import page.clab.api.domain.comment.dto.response.CommentMyResponseDto; import page.clab.api.domain.comment.dto.response.CommentResponseDto; +import page.clab.api.domain.comment.dto.response.DeletedCommentResponseDto; import page.clab.api.domain.member.application.MemberService; import page.clab.api.domain.member.domain.Member; import page.clab.api.domain.notification.application.NotificationService; @@ -68,6 +69,13 @@ public PagedResponseDto getMyComments(Pageable pageable) { return new PagedResponseDto<>(commentDtos); } + @Transactional(readOnly = true) + public PagedResponseDto getDeletedComments(Long boardId, Pageable pageable) { + Member currentMember = memberService.getCurrentMember(); + Page comments = commentRepository.findAllByIsDeletedTrueAndBoardId(boardId, pageable); + return new PagedResponseDto<>(comments.map(comment -> DeletedCommentResponseDto.toDto(comment, currentMember.getId()))); + } + @Transactional public Long updateComment(Long commentId, CommentUpdateRequestDto requestDto) throws PermissionDeniedException { Member currentMember = memberService.getCurrentMember(); @@ -82,7 +90,8 @@ public Long deleteComment(Long commentId) throws PermissionDeniedException { Member currentMember = memberService.getCurrentMember(); Comment comment = getCommentByIdOrThrow(commentId); comment.validateAccessPermission(currentMember); - commentRepository.delete(comment); + comment.updateIsDeleted(); + commentRepository.save(comment); return comment.getId(); } @@ -139,10 +148,12 @@ private void sendNotificationForNewComment(Comment comment) { } private CommentResponseDto toCommentResponseDto(Comment comment, Member currentMember) { - boolean hasLikeByMe = checkLikeStatus(comment.getId(), currentMember.getId()); + Boolean hasLikeByMe = checkLikeStatus(comment.getId(), currentMember.getId()); CommentResponseDto responseDto = CommentResponseDto.toDto(comment, currentMember.getId()); - responseDto.setHasLikeByMe(hasLikeByMe); responseDto.getChildren().forEach(childDto -> setLikeStatusForChildren(childDto, currentMember)); + if (!responseDto.getIsDeleted()) { + responseDto.setHasLikeByMe(hasLikeByMe); + } return responseDto; } @@ -156,7 +167,7 @@ private void setLikeStatusForChildren(CommentResponseDto responseDto, Member mem } private CommentMyResponseDto toCommentMyResponseDto(Comment comment, Member currentMember) { - boolean hasLikeByMe = checkLikeStatus(comment.getId(), currentMember.getId()); + Boolean hasLikeByMe = checkLikeStatus(comment.getId(), currentMember.getId()); return CommentMyResponseDto.toDto(comment, hasLikeByMe); } diff --git a/src/main/java/page/clab/api/domain/comment/dao/CommentRepository.java b/src/main/java/page/clab/api/domain/comment/dao/CommentRepository.java index 5c2bd30f9..8e9868df9 100644 --- a/src/main/java/page/clab/api/domain/comment/dao/CommentRepository.java +++ b/src/main/java/page/clab/api/domain/comment/dao/CommentRepository.java @@ -4,6 +4,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; import page.clab.api.domain.board.domain.Board; import page.clab.api.domain.comment.domain.Comment; @@ -18,4 +19,7 @@ public interface CommentRepository extends JpaRepository { Long countByBoard(Board board); + @Query(value = "SELECT c.* FROM comment c WHERE c.is_deleted = true AND c.board_id = ?", nativeQuery = true) + Page findAllByIsDeletedTrueAndBoardId(Long boardId, Pageable pageable); + } diff --git a/src/main/java/page/clab/api/domain/comment/domain/Comment.java b/src/main/java/page/clab/api/domain/comment/domain/Comment.java index 4ae81f7c9..bf691a2b9 100644 --- a/src/main/java/page/clab/api/domain/comment/domain/Comment.java +++ b/src/main/java/page/clab/api/domain/comment/domain/Comment.java @@ -106,4 +106,8 @@ public void decrementLikes() { } } + public void updateIsDeleted(){ + this.isDeleted = true; + } + } \ No newline at end of file diff --git a/src/main/java/page/clab/api/domain/comment/dto/response/CommentResponseDto.java b/src/main/java/page/clab/api/domain/comment/dto/response/CommentResponseDto.java index e0043e557..1222caece 100644 --- a/src/main/java/page/clab/api/domain/comment/dto/response/CommentResponseDto.java +++ b/src/main/java/page/clab/api/domain/comment/dto/response/CommentResponseDto.java @@ -16,6 +16,8 @@ public class CommentResponseDto { private Long id; + private Boolean isDeleted; + private String writerId; private String writerName; @@ -30,16 +32,26 @@ public class CommentResponseDto { private Long likes; - private boolean hasLikeByMe; + private Boolean hasLikeByMe; @JsonProperty("isOwner") - private boolean isOwner; + private Boolean isOwner; private LocalDateTime createdAt; public static CommentResponseDto toDto(Comment comment, String currentMemberId) { + if (comment.getIsDeleted()) { + return CommentResponseDto.builder() + .id(comment.getId()) + .isDeleted(true) + .children(comment.getChildren().stream() + .map(child -> CommentResponseDto.toDto(child, currentMemberId)) + .toList()) + .build(); + } return CommentResponseDto.builder() .id(comment.getId()) + .isDeleted(false) .writerId(comment.isWantAnonymous() ? null : comment.getWriter().getId()) .writerName(comment.isWantAnonymous() ? comment.getNickname() : comment.getWriter().getName()) .writerImageUrl(comment.isWantAnonymous() ? null : comment.getWriter().getImageUrl()) @@ -52,6 +64,7 @@ public static CommentResponseDto toDto(Comment comment, String currentMemberId) .isOwner(comment.isOwner(currentMemberId)) .createdAt(comment.getCreatedAt()) .build(); + } } diff --git a/src/main/java/page/clab/api/domain/comment/dto/response/DeletedCommentResponseDto.java b/src/main/java/page/clab/api/domain/comment/dto/response/DeletedCommentResponseDto.java new file mode 100644 index 000000000..2a94dfc4f --- /dev/null +++ b/src/main/java/page/clab/api/domain/comment/dto/response/DeletedCommentResponseDto.java @@ -0,0 +1,49 @@ +package page.clab.api.domain.comment.dto.response; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.time.LocalDateTime; +import java.util.List; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import page.clab.api.domain.comment.domain.Comment; + +@Getter +@Setter +@Builder +public class DeletedCommentResponseDto { + + private Long id; + + private String writerId; + + private String writerName; + + private String writerImageUrl; + + private Long writerRoleLevel; + + private String content; + + private Long likes; + + @JsonProperty("isOwner") + private boolean isOwner; + + private LocalDateTime createdAt; + + public static DeletedCommentResponseDto toDto(Comment comment, String currentMemberId) { + return DeletedCommentResponseDto.builder() + .id(comment.getId()) + .writerId(comment.isWantAnonymous() ? null : comment.getWriter().getId()) + .writerName(comment.isWantAnonymous() ? comment.getNickname() : comment.getWriter().getName()) + .writerImageUrl(comment.isWantAnonymous() ? null : comment.getWriter().getImageUrl()) + .writerRoleLevel(comment.isWantAnonymous() ? null : comment.getWriter().getRole().toRoleLevel()) + .content(comment.getContent()) + .likes(comment.getLikes()) + .isOwner(comment.isOwner(currentMemberId)) + .createdAt(comment.getCreatedAt()) + .build(); + } + +} diff --git a/src/main/java/page/clab/api/domain/donation/api/DonationController.java b/src/main/java/page/clab/api/domain/donation/api/DonationController.java index 19a4096df..d86de8788 100644 --- a/src/main/java/page/clab/api/domain/donation/api/DonationController.java +++ b/src/main/java/page/clab/api/domain/donation/api/DonationController.java @@ -21,8 +21,8 @@ import page.clab.api.domain.donation.dto.request.DonationRequestDto; import page.clab.api.domain.donation.dto.request.DonationUpdateRequestDto; import page.clab.api.domain.donation.dto.response.DonationResponseDto; -import page.clab.api.global.common.dto.PagedResponseDto; import page.clab.api.global.common.dto.ApiResponse; +import page.clab.api.global.common.dto.PagedResponseDto; import page.clab.api.global.exception.PermissionDeniedException; import java.time.LocalDate; @@ -97,4 +97,16 @@ public ApiResponse deleteDonation( return ApiResponse.success(id); } + @GetMapping("/deleted") + @Operation(summary = "[S] 삭제된 후원 조회하기", description = "ROLE_SUPER 이상의 권한이 필요함") + @Secured({"ROLE_SUPER"}) + public ApiResponse> getDeletedDonations( + @RequestParam(name = "page", defaultValue = "0") int page, + @RequestParam(name = "size", defaultValue = "20") int size + ) { + Pageable pageable = PageRequest.of(page, size); + PagedResponseDto donations = donationService.getDeletedDonations(pageable); + return ApiResponse.success(donations); + } + } \ No newline at end of file diff --git a/src/main/java/page/clab/api/domain/donation/application/DonationService.java b/src/main/java/page/clab/api/domain/donation/application/DonationService.java index 22993df7e..897fa6e6d 100644 --- a/src/main/java/page/clab/api/domain/donation/application/DonationService.java +++ b/src/main/java/page/clab/api/domain/donation/application/DonationService.java @@ -50,6 +50,12 @@ public PagedResponseDto getMyDonations(Pageable pageable) { return new PagedResponseDto<>(donations.map(DonationResponseDto::toDto)); } + @Transactional(readOnly = true) + public PagedResponseDto getDeletedDonations(Pageable pageable) { + Page donations = donationRepository.findAllByIsDeletedTrue(pageable); + return new PagedResponseDto<>(donations.map(DonationResponseDto::toDto)); + } + @Transactional public Long updateDonation(Long donationId, DonationUpdateRequestDto donationUpdateRequestDto) throws PermissionDeniedException { Member currentMember = memberService.getCurrentMember(); diff --git a/src/main/java/page/clab/api/domain/donation/dao/DonationRepository.java b/src/main/java/page/clab/api/domain/donation/dao/DonationRepository.java index 5b119d7d0..ef2636d32 100644 --- a/src/main/java/page/clab/api/domain/donation/dao/DonationRepository.java +++ b/src/main/java/page/clab/api/domain/donation/dao/DonationRepository.java @@ -4,6 +4,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import org.springframework.data.querydsl.QuerydslPredicateExecutor; import page.clab.api.domain.donation.domain.Donation; import page.clab.api.domain.member.domain.Member; @@ -14,4 +15,7 @@ public interface DonationRepository extends JpaRepository, Donat Page findByDonorOrderByCreatedAtDesc(Member member, Pageable pageable); + @Query(value = "SELECT d.* FROM donation d WHERE d.is_deleted = true", nativeQuery = true) + Page findAllByIsDeletedTrue(Pageable pageable); + } diff --git a/src/main/java/page/clab/api/domain/donation/domain/Donation.java b/src/main/java/page/clab/api/domain/donation/domain/Donation.java index f6f5f585f..26f8c2081 100644 --- a/src/main/java/page/clab/api/domain/donation/domain/Donation.java +++ b/src/main/java/page/clab/api/domain/donation/domain/Donation.java @@ -15,6 +15,8 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.SQLRestriction; import page.clab.api.domain.donation.dto.request.DonationUpdateRequestDto; import page.clab.api.domain.member.domain.Member; import page.clab.api.global.common.domain.BaseEntity; @@ -27,6 +29,8 @@ @Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor(access = AccessLevel.PRIVATE) +@SQLDelete(sql = "UPDATE donation SET is_deleted = true WHERE id = ?") +@SQLRestriction("is_deleted = false") public class Donation extends BaseEntity { @Id diff --git a/src/main/java/page/clab/api/domain/jobPosting/api/JobPostingController.java b/src/main/java/page/clab/api/domain/jobPosting/api/JobPostingController.java index aa06f247a..6e9c2447f 100644 --- a/src/main/java/page/clab/api/domain/jobPosting/api/JobPostingController.java +++ b/src/main/java/page/clab/api/domain/jobPosting/api/JobPostingController.java @@ -23,8 +23,8 @@ import page.clab.api.domain.jobPosting.dto.request.JobPostingUpdateRequestDto; import page.clab.api.domain.jobPosting.dto.response.JobPostingDetailsResponseDto; import page.clab.api.domain.jobPosting.dto.response.JobPostingResponseDto; -import page.clab.api.global.common.dto.PagedResponseDto; import page.clab.api.global.common.dto.ApiResponse; +import page.clab.api.global.common.dto.PagedResponseDto; @RestController @RequestMapping("/api/v1/job-postings") @@ -94,4 +94,16 @@ public ApiResponse deleteJobPosting( return ApiResponse.success(id); } + @GetMapping("/deleted") + @Operation(summary = "[S] 삭제된 채용 공고 조회하기", description = "ROLE_SUPER 이상의 권한이 필요함") + @Secured({"ROLE_SUPER"}) + public ApiResponse> getDeletedJobPostings( + @RequestParam(name = "page", defaultValue = "0") int page, + @RequestParam(name = "size", defaultValue = "20") int size + ) { + Pageable pageable = PageRequest.of(page, size); + PagedResponseDto jobPostings = jobPostingService.getDeletedJobPostings(pageable); + return ApiResponse.success(jobPostings); + } + } diff --git a/src/main/java/page/clab/api/domain/jobPosting/application/JobPostingService.java b/src/main/java/page/clab/api/domain/jobPosting/application/JobPostingService.java index 7a5370c37..f35f59c31 100644 --- a/src/main/java/page/clab/api/domain/jobPosting/application/JobPostingService.java +++ b/src/main/java/page/clab/api/domain/jobPosting/application/JobPostingService.java @@ -46,6 +46,12 @@ public JobPostingDetailsResponseDto getJobPosting(Long jobPostingId) { return JobPostingDetailsResponseDto.toDto(jobPosting); } + @Transactional(readOnly = true) + public PagedResponseDto getDeletedJobPostings(Pageable pageable) { + Page jobPostings = jobPostingRepository.findAllByIsDeletedTrue(pageable); + return new PagedResponseDto<>(jobPostings.map(JobPostingDetailsResponseDto::toDto)); + } + @Transactional public Long updateJobPosting(Long jobPostingId, JobPostingUpdateRequestDto requestDto) { JobPosting jobPosting = getJobPostingByIdOrThrow(jobPostingId); @@ -62,7 +68,7 @@ public Long deleteJobPosting(Long jobPostingId) { public JobPosting getJobPostingByIdOrThrow(Long jobPostingId) { return jobPostingRepository.findById(jobPostingId) - .orElseThrow(() -> new NotFoundException("존재하지 않는 채용 공고입니다.")); + .orElseThrow(() -> new NotFoundException("존재하지 않는 채용 공고입니다.")); } } diff --git a/src/main/java/page/clab/api/domain/jobPosting/dao/JobPostingRepository.java b/src/main/java/page/clab/api/domain/jobPosting/dao/JobPostingRepository.java index 013873755..ea31c12f3 100644 --- a/src/main/java/page/clab/api/domain/jobPosting/dao/JobPostingRepository.java +++ b/src/main/java/page/clab/api/domain/jobPosting/dao/JobPostingRepository.java @@ -3,6 +3,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import org.springframework.data.querydsl.QuerydslPredicateExecutor; import org.springframework.stereotype.Repository; import page.clab.api.domain.jobPosting.domain.JobPosting; @@ -15,5 +16,8 @@ public interface JobPostingRepository extends JpaRepository, J Optional findByJobPostingUrl(String jobPostingUrl); Page findAllByOrderByCreatedAtDesc(Pageable pageable); - + + @Query(value = "SELECT j.* FROM job_posting j WHERE j.is_deleted = true", nativeQuery = true) + Page findAllByIsDeletedTrue(Pageable pageable); + } diff --git a/src/main/java/page/clab/api/domain/jobPosting/domain/JobPosting.java b/src/main/java/page/clab/api/domain/jobPosting/domain/JobPosting.java index fde735856..4fa3c6449 100644 --- a/src/main/java/page/clab/api/domain/jobPosting/domain/JobPosting.java +++ b/src/main/java/page/clab/api/domain/jobPosting/domain/JobPosting.java @@ -14,6 +14,8 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.SQLRestriction; import org.hibernate.validator.constraints.URL; import page.clab.api.domain.jobPosting.dto.request.JobPostingRequestDto; import page.clab.api.domain.jobPosting.dto.request.JobPostingUpdateRequestDto; @@ -27,6 +29,8 @@ @Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor(access = AccessLevel.PRIVATE) +@SQLDelete(sql = "UPDATE job_posting SET is_deleted = true WHERE id = ?") +@SQLRestriction("is_deleted = false") public class JobPosting extends BaseEntity { @Id diff --git a/src/main/java/page/clab/api/domain/login/domain/LoginAttemptLog.java b/src/main/java/page/clab/api/domain/login/domain/LoginAttemptLog.java index dd7acf179..f6566a517 100644 --- a/src/main/java/page/clab/api/domain/login/domain/LoginAttemptLog.java +++ b/src/main/java/page/clab/api/domain/login/domain/LoginAttemptLog.java @@ -48,7 +48,7 @@ public static LoginAttemptLog create(String memberId, HttpServletRequest httpSer .memberId(memberId) .userAgent(httpServletRequest.getHeader("User-Agent")) .ipAddress(ipAddress) - .location(ipResponse == null ? "Unknown" : ipResponse.getCity()) + .location(ipResponse == null ? "Unknown" : ipResponse.getCountryName() + ", " + ipResponse.getCity()) .loginAttemptResult(loginAttemptResult) .loginAttemptTime(LocalDateTime.now()) .build(); diff --git a/src/main/java/page/clab/api/domain/membershipFee/api/MembershipFeeController.java b/src/main/java/page/clab/api/domain/membershipFee/api/MembershipFeeController.java index f48de4d3a..2bd53105d 100644 --- a/src/main/java/page/clab/api/domain/membershipFee/api/MembershipFeeController.java +++ b/src/main/java/page/clab/api/domain/membershipFee/api/MembershipFeeController.java @@ -22,8 +22,8 @@ import page.clab.api.domain.membershipFee.dto.request.MembershipFeeRequestDto; import page.clab.api.domain.membershipFee.dto.request.MembershipFeeUpdateRequestDto; import page.clab.api.domain.membershipFee.dto.response.MembershipFeeResponseDto; -import page.clab.api.global.common.dto.PagedResponseDto; import page.clab.api.global.common.dto.ApiResponse; +import page.clab.api.global.common.dto.PagedResponseDto; import page.clab.api.global.exception.PermissionDeniedException; @RestController @@ -85,4 +85,16 @@ public ApiResponse deleteMembershipFee( return ApiResponse.success(id); } + @GetMapping("/deleted") + @Operation(summary = "[S] 삭제된 회비 조회하기", description = "ROLE_SUPER 이상의 권한이 필요함") + @Secured({"ROLE_SUPER"}) + public ApiResponse> getDeletedMembershipFees( + @RequestParam(name = "page", defaultValue = "0") int page, + @RequestParam(name = "size", defaultValue = "20") int size + ) { + Pageable pageable = PageRequest.of(page, size); + PagedResponseDto membershipFees = membershipFeeService.getDeletedMembershipFees(pageable); + return ApiResponse.success(membershipFees); + } + } diff --git a/src/main/java/page/clab/api/domain/membershipFee/application/MembershipFeeService.java b/src/main/java/page/clab/api/domain/membershipFee/application/MembershipFeeService.java index adde3ae02..292930d26 100644 --- a/src/main/java/page/clab/api/domain/membershipFee/application/MembershipFeeService.java +++ b/src/main/java/page/clab/api/domain/membershipFee/application/MembershipFeeService.java @@ -48,6 +48,14 @@ public PagedResponseDto getMembershipFeesByConditions( return new PagedResponseDto<>(membershipFeesPage.map(membershipFee -> MembershipFeeResponseDto.toDto(membershipFee, isAdminOrSuper))); } + @Transactional(readOnly = true) + public PagedResponseDto getDeletedMembershipFees(Pageable pageable) { + Member currentMember = memberService.getCurrentMember(); + boolean isAdminOrSuper = currentMember.isAdminRole(); + Page membershipFees = membershipFeeRepository.findAllByIsDeletedTrue(pageable); + return new PagedResponseDto<>(membershipFees.map(membershipFee -> MembershipFeeResponseDto.toDto(membershipFee, isAdminOrSuper))); + } + @Transactional public Long updateMembershipFee(Long membershipFeeId, MembershipFeeUpdateRequestDto requestDto) throws PermissionDeniedException { Member currentMember = memberService.getCurrentMember(); diff --git a/src/main/java/page/clab/api/domain/membershipFee/dao/MembershipFeeRepository.java b/src/main/java/page/clab/api/domain/membershipFee/dao/MembershipFeeRepository.java index 31bcfc42f..d15aa92e2 100644 --- a/src/main/java/page/clab/api/domain/membershipFee/dao/MembershipFeeRepository.java +++ b/src/main/java/page/clab/api/domain/membershipFee/dao/MembershipFeeRepository.java @@ -3,6 +3,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import org.springframework.data.querydsl.QuerydslPredicateExecutor; import page.clab.api.domain.membershipFee.domain.MembershipFee; @@ -10,4 +11,7 @@ public interface MembershipFeeRepository extends JpaRepository findAllByOrderByCreatedAtDesc(Pageable pageable); + @Query(value = "SELECT m.* FROM membership_fee m WHERE m.is_deleted = true", nativeQuery = true) + Page findAllByIsDeletedTrue(Pageable pageable); + } diff --git a/src/main/java/page/clab/api/domain/membershipFee/domain/MembershipFee.java b/src/main/java/page/clab/api/domain/membershipFee/domain/MembershipFee.java index 101067870..da52ea1d1 100644 --- a/src/main/java/page/clab/api/domain/membershipFee/domain/MembershipFee.java +++ b/src/main/java/page/clab/api/domain/membershipFee/domain/MembershipFee.java @@ -16,6 +16,8 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.SQLRestriction; import page.clab.api.domain.member.domain.Member; import page.clab.api.domain.membershipFee.dto.request.MembershipFeeUpdateRequestDto; import page.clab.api.global.common.domain.BaseEntity; @@ -29,6 +31,8 @@ @Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor(access = AccessLevel.PRIVATE) +@SQLDelete(sql = "UPDATE membership_fee SET is_deleted = true WHERE id = ?") +@SQLRestriction("is_deleted = false") public class MembershipFee extends BaseEntity { @Id diff --git a/src/main/java/page/clab/api/domain/news/api/NewsController.java b/src/main/java/page/clab/api/domain/news/api/NewsController.java index 72d7eaacf..605c772ff 100644 --- a/src/main/java/page/clab/api/domain/news/api/NewsController.java +++ b/src/main/java/page/clab/api/domain/news/api/NewsController.java @@ -22,8 +22,8 @@ import page.clab.api.domain.news.dto.request.NewsUpdateRequestDto; import page.clab.api.domain.news.dto.response.NewsDetailsResponseDto; import page.clab.api.domain.news.dto.response.NewsResponseDto; -import page.clab.api.global.common.dto.PagedResponseDto; import page.clab.api.global.common.dto.ApiResponse; +import page.clab.api.global.common.dto.PagedResponseDto; @RestController @RequestMapping("/api/v1/news") @@ -91,4 +91,16 @@ public ApiResponse deleteNews( return ApiResponse.success(id); } + @GetMapping("/deleted") + @Operation(summary = "[S] 삭제된 뉴스 조회하기", description = "ROLE_SUPER 이상의 권한이 필요함") + @Secured({"ROLE_SUPER"}) + public ApiResponse> getDeletedNews( + @RequestParam(name = "page", defaultValue = "0") int page, + @RequestParam(name = "size", defaultValue = "20") int size + ) { + Pageable pageable = PageRequest.of(page, size); + PagedResponseDto pagedNews = newsService.getDeletedNews(pageable); + return ApiResponse.success(pagedNews); + } + } diff --git a/src/main/java/page/clab/api/domain/news/application/NewsService.java b/src/main/java/page/clab/api/domain/news/application/NewsService.java index 8edf48bdc..aa0f92981 100644 --- a/src/main/java/page/clab/api/domain/news/application/NewsService.java +++ b/src/main/java/page/clab/api/domain/news/application/NewsService.java @@ -46,6 +46,12 @@ public NewsDetailsResponseDto getNewsDetails(Long newsId) { return NewsDetailsResponseDto.toDto(news); } + @Transactional(readOnly = true) + public PagedResponseDto getDeletedNews(Pageable pageable) { + Page newsPage = newsRepository.findAllByIsDeletedTrue(pageable); + return new PagedResponseDto<>(newsPage.map(NewsDetailsResponseDto::toDto)); + } + @Transactional public Long updateNews(Long newsId, NewsUpdateRequestDto requestDto) { News news = getNewsByIdOrThrow(newsId); diff --git a/src/main/java/page/clab/api/domain/news/dao/NewsRepository.java b/src/main/java/page/clab/api/domain/news/dao/NewsRepository.java index b31895bf9..a248bfb79 100644 --- a/src/main/java/page/clab/api/domain/news/dao/NewsRepository.java +++ b/src/main/java/page/clab/api/domain/news/dao/NewsRepository.java @@ -4,6 +4,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import org.springframework.data.querydsl.QuerydslPredicateExecutor; import org.springframework.stereotype.Repository; import page.clab.api.domain.news.domain.News; @@ -13,4 +14,7 @@ public interface NewsRepository extends JpaRepository, NewsRepositor Page findAllByOrderByCreatedAtDesc(Pageable pageable); + @Query(value = "SELECT n.* FROM news n WHERE n.is_deleted = true", nativeQuery = true) + Page findAllByIsDeletedTrue(Pageable pageable); + } diff --git a/src/main/java/page/clab/api/domain/news/domain/News.java b/src/main/java/page/clab/api/domain/news/domain/News.java index 98b9ea543..fb28b3f04 100644 --- a/src/main/java/page/clab/api/domain/news/domain/News.java +++ b/src/main/java/page/clab/api/domain/news/domain/News.java @@ -17,6 +17,8 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.SQLRestriction; import org.hibernate.validator.constraints.URL; import page.clab.api.domain.news.dto.request.NewsUpdateRequestDto; import page.clab.api.global.common.domain.BaseEntity; @@ -32,6 +34,8 @@ @Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor(access = AccessLevel.PRIVATE) +@SQLDelete(sql = "UPDATE news SET is_deleted = true WHERE id = ?") +@SQLRestriction("is_deleted = false") @Table(indexes = {@Index(name = "idx_article_url", columnList = "articleUrl")}) public class News extends BaseEntity { diff --git a/src/main/java/page/clab/api/domain/notification/api/NotificationController.java b/src/main/java/page/clab/api/domain/notification/api/NotificationController.java index 0e9563595..a29ad3b0c 100644 --- a/src/main/java/page/clab/api/domain/notification/api/NotificationController.java +++ b/src/main/java/page/clab/api/domain/notification/api/NotificationController.java @@ -19,8 +19,8 @@ import page.clab.api.domain.notification.application.NotificationService; import page.clab.api.domain.notification.dto.request.NotificationRequestDto; import page.clab.api.domain.notification.dto.response.NotificationResponseDto; -import page.clab.api.global.common.dto.PagedResponseDto; import page.clab.api.global.common.dto.ApiResponse; +import page.clab.api.global.common.dto.PagedResponseDto; import page.clab.api.global.exception.PermissionDeniedException; @RestController @@ -64,4 +64,16 @@ public ApiResponse deleteNotification( return ApiResponse.success(id); } + @GetMapping("/deleted") + @Operation(summary = "[S] 삭제된 알림 조회하기", description = "ROLE_SUPER 이상의 권한이 필요함") + @Secured({"ROLE_SUPER"}) + public ApiResponse> getDeletedNotifications( + @RequestParam(name = "page", defaultValue = "0") int page, + @RequestParam(name = "size", defaultValue = "20") int size + ) { + Pageable pageable = PageRequest.of(page, size); + PagedResponseDto notifications = notificationService.getDeletedNotifications(pageable); + return ApiResponse.success(notifications); + } + } diff --git a/src/main/java/page/clab/api/domain/notification/application/NotificationService.java b/src/main/java/page/clab/api/domain/notification/application/NotificationService.java index fdd3c6bb3..b649d2c07 100644 --- a/src/main/java/page/clab/api/domain/notification/application/NotificationService.java +++ b/src/main/java/page/clab/api/domain/notification/application/NotificationService.java @@ -51,6 +51,12 @@ public PagedResponseDto getNotifications(Pageable pagea return new PagedResponseDto<>(notifications.map(NotificationResponseDto::toDto)); } + @Transactional(readOnly = true) + public PagedResponseDto getDeletedNotifications(Pageable pageable) { + Page notifications = notificationRepository.findAllByIsDeletedTrue(pageable); + return new PagedResponseDto<>(notifications.map(NotificationResponseDto::toDto)); + } + public Long deleteNotification(Long notificationId) throws PermissionDeniedException { Member currentMember = memberService.getCurrentMember(); Notification notification = getNotificationByIdOrThrow(notificationId); diff --git a/src/main/java/page/clab/api/domain/notification/dao/NotificationRepository.java b/src/main/java/page/clab/api/domain/notification/dao/NotificationRepository.java index 19397c53e..9ff722277 100644 --- a/src/main/java/page/clab/api/domain/notification/dao/NotificationRepository.java +++ b/src/main/java/page/clab/api/domain/notification/dao/NotificationRepository.java @@ -3,6 +3,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import page.clab.api.domain.member.domain.Member; import page.clab.api.domain.notification.domain.Notification; @@ -10,4 +11,7 @@ public interface NotificationRepository extends JpaRepository findByMemberOrderByCreatedAtDesc(Member member, Pageable pageable); + @Query(value = "SELECT n.* FROM notification n WHERE n.is_deleted = true", nativeQuery = true) + Page findAllByIsDeletedTrue(Pageable pageable); + } diff --git a/src/main/java/page/clab/api/domain/notification/domain/Notification.java b/src/main/java/page/clab/api/domain/notification/domain/Notification.java index c81096d57..9892018e3 100644 --- a/src/main/java/page/clab/api/domain/notification/domain/Notification.java +++ b/src/main/java/page/clab/api/domain/notification/domain/Notification.java @@ -14,6 +14,8 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.SQLRestriction; import page.clab.api.domain.member.domain.Member; import page.clab.api.global.common.domain.BaseEntity; import page.clab.api.global.exception.PermissionDeniedException; @@ -24,6 +26,8 @@ @Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor(access = AccessLevel.PRIVATE) +@SQLDelete(sql = "UPDATE notification SET is_deleted = true WHERE id = ?") +@SQLRestriction("is_deleted = false") public class Notification extends BaseEntity { @Id diff --git a/src/main/java/page/clab/api/domain/position/api/PositionController.java b/src/main/java/page/clab/api/domain/position/api/PositionController.java index 788ee02b7..4d56e5a30 100644 --- a/src/main/java/page/clab/api/domain/position/api/PositionController.java +++ b/src/main/java/page/clab/api/domain/position/api/PositionController.java @@ -21,8 +21,8 @@ import page.clab.api.domain.position.dto.request.PositionRequestDto; import page.clab.api.domain.position.dto.response.PositionMyResponseDto; import page.clab.api.domain.position.dto.response.PositionResponseDto; -import page.clab.api.global.common.dto.PagedResponseDto; import page.clab.api.global.common.dto.ApiResponse; +import page.clab.api.global.common.dto.PagedResponseDto; @RestController @RequestMapping("/api/v1/positions") @@ -79,4 +79,16 @@ public ApiResponse deletePosition( return ApiResponse.success(id); } + @GetMapping("/deleted") + @Operation(summary = "[S] 삭제된 직책 조회하기", description = "ROLE_SUPER 이상의 권한이 필요함") + @Secured({"ROLE_SUPER"}) + public ApiResponse> getDeletedPositions( + @RequestParam(name = "page", defaultValue = "0") int page, + @RequestParam(name = "size", defaultValue = "20") int size + ) { + Pageable pageable = PageRequest.of(page, size); + PagedResponseDto positions = positionService.getDeletedPositions(pageable); + return ApiResponse.success(positions); + } + } diff --git a/src/main/java/page/clab/api/domain/position/application/PositionService.java b/src/main/java/page/clab/api/domain/position/application/PositionService.java index 20192433e..8459720d4 100644 --- a/src/main/java/page/clab/api/domain/position/application/PositionService.java +++ b/src/main/java/page/clab/api/domain/position/application/PositionService.java @@ -53,6 +53,12 @@ public PositionMyResponseDto getMyPositionsByYear(String year) { return PositionMyResponseDto.toDto(positions); } + @Transactional(readOnly = true) + public PagedResponseDto getDeletedPositions(Pageable pageable) { + Page positions = positionRepository.findAllByIsDeletedTrue(pageable); + return new PagedResponseDto<>(positions.map(PositionResponseDto::toDto)); + } + public Long deletePosition(Long positionId) { Position position = getPositionsByIdOrThrow(positionId); positionRepository.delete(position); diff --git a/src/main/java/page/clab/api/domain/position/dao/PositionRepository.java b/src/main/java/page/clab/api/domain/position/dao/PositionRepository.java index a854eda53..a4dc9333a 100644 --- a/src/main/java/page/clab/api/domain/position/dao/PositionRepository.java +++ b/src/main/java/page/clab/api/domain/position/dao/PositionRepository.java @@ -1,6 +1,9 @@ package page.clab.api.domain.position.dao; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import org.springframework.data.querydsl.QuerydslPredicateExecutor; import page.clab.api.domain.member.domain.Member; import page.clab.api.domain.position.domain.Position; @@ -15,4 +18,7 @@ public interface PositionRepository extends JpaRepository, Posit List findAllByMemberAndYearOrderByPositionTypeAsc(Member member, String year); + @Query(value = "SELECT p.* FROM \"position\" p WHERE p.is_deleted = true", nativeQuery = true) + Page findAllByIsDeletedTrue(Pageable pageable); + } diff --git a/src/main/java/page/clab/api/domain/position/domain/Position.java b/src/main/java/page/clab/api/domain/position/domain/Position.java index abe8a6f76..16cd0541e 100644 --- a/src/main/java/page/clab/api/domain/position/domain/Position.java +++ b/src/main/java/page/clab/api/domain/position/domain/Position.java @@ -13,6 +13,8 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.SQLRestriction; import page.clab.api.domain.member.domain.Member; import page.clab.api.global.common.domain.BaseEntity; @@ -24,6 +26,8 @@ @Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor(access = AccessLevel.PRIVATE) +@SQLDelete(sql = "UPDATE \"position\" SET is_deleted = true WHERE id = ?") +@SQLRestriction("is_deleted = false") public class Position extends BaseEntity { @Id diff --git a/src/main/java/page/clab/api/domain/product/api/ProductController.java b/src/main/java/page/clab/api/domain/product/api/ProductController.java index 39893c5e5..a7c132c1f 100644 --- a/src/main/java/page/clab/api/domain/product/api/ProductController.java +++ b/src/main/java/page/clab/api/domain/product/api/ProductController.java @@ -21,8 +21,8 @@ import page.clab.api.domain.product.dto.request.ProductRequestDto; import page.clab.api.domain.product.dto.request.ProductUpdateRequestDto; import page.clab.api.domain.product.dto.response.ProductResponseDto; -import page.clab.api.global.common.dto.PagedResponseDto; import page.clab.api.global.common.dto.ApiResponse; +import page.clab.api.global.common.dto.PagedResponseDto; @RestController @RequestMapping("/api/v1/products") @@ -78,4 +78,16 @@ public ApiResponse deleteProduct( return ApiResponse.success(id); } + @GetMapping("/deleted") + @Operation(summary = "[S] 삭제된 서비스 조회하기", description = "ROLE_SUPER 이상의 권한이 필요함") + @Secured({"ROLE_SUPER"}) + public ApiResponse> getDeletedProducts( + @RequestParam(name = "page", defaultValue = "0") int page, + @RequestParam(name = "size", defaultValue = "20") int size + ) { + Pageable pageable = PageRequest.of(page, size); + PagedResponseDto products = productService.getDeletedProducts(pageable); + return ApiResponse.success(products); + } + } diff --git a/src/main/java/page/clab/api/domain/product/application/ProductService.java b/src/main/java/page/clab/api/domain/product/application/ProductService.java index 7adc52ac0..d07005132 100644 --- a/src/main/java/page/clab/api/domain/product/application/ProductService.java +++ b/src/main/java/page/clab/api/domain/product/application/ProductService.java @@ -35,6 +35,12 @@ public PagedResponseDto getProductsByConditions(String produ return new PagedResponseDto<>(products.map(ProductResponseDto::toDto)); } + @Transactional(readOnly = true) + public PagedResponseDto getDeletedProducts(Pageable pageable) { + Page products = productRepository.findAllByIsDeletedTrue(pageable); + return new PagedResponseDto<>(products.map(ProductResponseDto::toDto)); + } + @Transactional public Long updateProduct(Long productId, ProductUpdateRequestDto requestDto) { Product product = getProductByIdOrThrow(productId); diff --git a/src/main/java/page/clab/api/domain/product/dao/ProductRepository.java b/src/main/java/page/clab/api/domain/product/dao/ProductRepository.java index b78eec763..5a1338da8 100644 --- a/src/main/java/page/clab/api/domain/product/dao/ProductRepository.java +++ b/src/main/java/page/clab/api/domain/product/dao/ProductRepository.java @@ -3,6 +3,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import org.springframework.data.querydsl.QuerydslPredicateExecutor; import org.springframework.stereotype.Repository; import page.clab.api.domain.product.domain.Product; @@ -12,4 +13,7 @@ public interface ProductRepository extends JpaRepository, Product Page findAllByOrderByCreatedAtDesc(Pageable pageable); + @Query(value = "SELECT p.* FROM product p WHERE p.is_deleted = true", nativeQuery = true) + Page findAllByIsDeletedTrue(Pageable pageable); + } diff --git a/src/main/java/page/clab/api/domain/product/domain/Product.java b/src/main/java/page/clab/api/domain/product/domain/Product.java index 36cf70d18..2907982aa 100644 --- a/src/main/java/page/clab/api/domain/product/domain/Product.java +++ b/src/main/java/page/clab/api/domain/product/domain/Product.java @@ -12,6 +12,8 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.SQLRestriction; import org.hibernate.validator.constraints.URL; import page.clab.api.domain.product.dto.request.ProductUpdateRequestDto; import page.clab.api.global.common.domain.BaseEntity; @@ -24,6 +26,8 @@ @Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor(access = AccessLevel.PRIVATE) +@SQLDelete(sql = "UPDATE product SET is_deleted = true WHERE id = ?") +@SQLRestriction("is_deleted = false") public class Product extends BaseEntity { @Id diff --git a/src/main/java/page/clab/api/domain/recruitment/api/RecruitmentController.java b/src/main/java/page/clab/api/domain/recruitment/api/RecruitmentController.java index 3eabad922..35a551b05 100644 --- a/src/main/java/page/clab/api/domain/recruitment/api/RecruitmentController.java +++ b/src/main/java/page/clab/api/domain/recruitment/api/RecruitmentController.java @@ -5,6 +5,8 @@ import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.security.access.annotation.Secured; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -13,12 +15,14 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import page.clab.api.domain.recruitment.application.RecruitmentService; import page.clab.api.domain.recruitment.dto.request.RecruitmentRequestDto; import page.clab.api.domain.recruitment.dto.request.RecruitmentUpdateRequestDto; import page.clab.api.domain.recruitment.dto.response.RecruitmentResponseDto; import page.clab.api.global.common.dto.ApiResponse; +import page.clab.api.global.common.dto.PagedResponseDto; import java.util.List; @@ -70,4 +74,16 @@ public ApiResponse deleteRecruitment( return ApiResponse.success(id); } + @GetMapping("/deleted") + @Operation(summary = "[S] 삭제된 모집 공고 조회하기", description = "ROLE_SUPER 이상의 권한이 필요함") + @Secured({"ROLE_SUPER"}) + public ApiResponse> getDeletedRecruitments( + @RequestParam(name = "page", defaultValue = "0") int page, + @RequestParam(name = "size", defaultValue = "20") int size + ) { + Pageable pageable = PageRequest.of(page, size); + PagedResponseDto recruitments = recruitmentService.getDeletedRecruitments(pageable); + return ApiResponse.success(recruitments); + } + } diff --git a/src/main/java/page/clab/api/domain/recruitment/application/RecruitmentService.java b/src/main/java/page/clab/api/domain/recruitment/application/RecruitmentService.java index 9127672d7..a6275550c 100644 --- a/src/main/java/page/clab/api/domain/recruitment/application/RecruitmentService.java +++ b/src/main/java/page/clab/api/domain/recruitment/application/RecruitmentService.java @@ -1,6 +1,8 @@ package page.clab.api.domain.recruitment.application; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import page.clab.api.domain.notification.application.NotificationService; @@ -9,6 +11,7 @@ import page.clab.api.domain.recruitment.dto.request.RecruitmentRequestDto; import page.clab.api.domain.recruitment.dto.request.RecruitmentUpdateRequestDto; import page.clab.api.domain.recruitment.dto.response.RecruitmentResponseDto; +import page.clab.api.global.common.dto.PagedResponseDto; import page.clab.api.global.exception.NotFoundException; import page.clab.api.global.validation.ValidationService; @@ -40,6 +43,13 @@ public List getRecentRecruitments() { .toList(); } + @Transactional(readOnly = true) + public PagedResponseDto getDeletedRecruitments(Pageable pageable) { + Page recruitments = recruitmentRepository.findAllByIsDeletedTrue(pageable); + return new PagedResponseDto<>(recruitments + .map(RecruitmentResponseDto::toDto)); + } + @Transactional public Long updateRecruitment(Long recruitmentId, RecruitmentUpdateRequestDto requestDto) { Recruitment recruitment = getRecruitmentByIdOrThrow(recruitmentId); diff --git a/src/main/java/page/clab/api/domain/recruitment/dao/RecruitmentRepository.java b/src/main/java/page/clab/api/domain/recruitment/dao/RecruitmentRepository.java index 07b9338ab..bd78152ae 100644 --- a/src/main/java/page/clab/api/domain/recruitment/dao/RecruitmentRepository.java +++ b/src/main/java/page/clab/api/domain/recruitment/dao/RecruitmentRepository.java @@ -1,13 +1,20 @@ package page.clab.api.domain.recruitment.dao; -import java.util.List; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; import page.clab.api.domain.recruitment.domain.Recruitment; +import java.util.List; + @Repository public interface RecruitmentRepository extends JpaRepository { List findTop5ByOrderByCreatedAtDesc(); + @Query(value = "SELECT r.* FROM recruitment r WHERE r.is_deleted = true", nativeQuery = true) + Page findAllByIsDeletedTrue(Pageable pageable); + } diff --git a/src/main/java/page/clab/api/domain/recruitment/domain/Recruitment.java b/src/main/java/page/clab/api/domain/recruitment/domain/Recruitment.java index db6406a47..7f689027c 100644 --- a/src/main/java/page/clab/api/domain/recruitment/domain/Recruitment.java +++ b/src/main/java/page/clab/api/domain/recruitment/domain/Recruitment.java @@ -14,6 +14,8 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.SQLRestriction; import page.clab.api.domain.application.domain.ApplicationType; import page.clab.api.domain.recruitment.dto.request.RecruitmentUpdateRequestDto; import page.clab.api.global.common.domain.BaseEntity; @@ -27,6 +29,8 @@ @Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor(access = AccessLevel.PRIVATE) +@SQLDelete(sql = "UPDATE recruitment SET is_deleted = true WHERE id = ?") +@SQLRestriction("is_deleted = false") public class Recruitment extends BaseEntity { @Id diff --git a/src/main/java/page/clab/api/domain/review/api/ReviewController.java b/src/main/java/page/clab/api/domain/review/api/ReviewController.java index ecfab12b4..324cb8bf4 100644 --- a/src/main/java/page/clab/api/domain/review/api/ReviewController.java +++ b/src/main/java/page/clab/api/domain/review/api/ReviewController.java @@ -21,8 +21,8 @@ import page.clab.api.domain.review.dto.request.ReviewRequestDto; import page.clab.api.domain.review.dto.request.ReviewUpdateRequestDto; import page.clab.api.domain.review.dto.response.ReviewResponseDto; -import page.clab.api.global.common.dto.PagedResponseDto; import page.clab.api.global.common.dto.ApiResponse; +import page.clab.api.global.common.dto.PagedResponseDto; import page.clab.api.global.exception.PermissionDeniedException; @RestController @@ -95,4 +95,17 @@ public ApiResponse deleteReview( return ApiResponse.success(id); } + + @GetMapping("/deleted") + @Operation(summary = "[S] 삭제된 리뷰 조회하기", description = "ROLE_SUPER 이상의 권한이 필요함") + @Secured({"ROLE_SUPER"}) + public ApiResponse> getDeletedReviews( + @RequestParam(name = "page", defaultValue = "0") int page, + @RequestParam(name = "size", defaultValue = "20") int size + ) { + Pageable pageable = PageRequest.of(page, size); + PagedResponseDto reviews = reviewService.getDeletedReviews(pageable); + return ApiResponse.success(reviews); + } + } diff --git a/src/main/java/page/clab/api/domain/review/application/ReviewService.java b/src/main/java/page/clab/api/domain/review/application/ReviewService.java index fa971a8be..d9a9717d4 100644 --- a/src/main/java/page/clab/api/domain/review/application/ReviewService.java +++ b/src/main/java/page/clab/api/domain/review/application/ReviewService.java @@ -65,6 +65,13 @@ public PagedResponseDto getMyReviews(Pageable pageable) { return new PagedResponseDto<>(reviews.map(review -> ReviewResponseDto.toDto(review, currentMember))); } + @Transactional(readOnly = true) + public PagedResponseDto getDeletedReviews(Pageable pageable) { + Member currentMember = memberService.getCurrentMember(); + Page reviews = reviewRepository.findAllByIsDeletedTrue(pageable); + return new PagedResponseDto<>(reviews.map(review -> ReviewResponseDto.toDto(review, currentMember))); + } + @Transactional public Long updateReview(Long reviewId, ReviewUpdateRequestDto requestDto) throws PermissionDeniedException { Member currentMember = memberService.getCurrentMember(); diff --git a/src/main/java/page/clab/api/domain/review/dao/ReviewRepository.java b/src/main/java/page/clab/api/domain/review/dao/ReviewRepository.java index 3065c2d0d..8bcfcdac2 100644 --- a/src/main/java/page/clab/api/domain/review/dao/ReviewRepository.java +++ b/src/main/java/page/clab/api/domain/review/dao/ReviewRepository.java @@ -3,6 +3,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import org.springframework.data.querydsl.QuerydslPredicateExecutor; import page.clab.api.domain.activityGroup.domain.ActivityGroup; import page.clab.api.domain.member.domain.Member; @@ -16,4 +17,7 @@ public interface ReviewRepository extends JpaRepository, ReviewRep Page findAllByOrderByCreatedAtDesc(Pageable pageable); + @Query(value = "SELECT r.* FROM review r WHERE r.is_deleted = true", nativeQuery = true) + Page findAllByIsDeletedTrue(Pageable pageable); + } diff --git a/src/main/java/page/clab/api/domain/review/domain/Review.java b/src/main/java/page/clab/api/domain/review/domain/Review.java index eb93adde6..1dce023a0 100644 --- a/src/main/java/page/clab/api/domain/review/domain/Review.java +++ b/src/main/java/page/clab/api/domain/review/domain/Review.java @@ -15,6 +15,8 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.SQLRestriction; import page.clab.api.domain.activityGroup.domain.ActivityGroup; import page.clab.api.domain.member.domain.Member; import page.clab.api.domain.review.dto.request.ReviewUpdateRequestDto; @@ -30,6 +32,8 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor(access = AccessLevel.PRIVATE) @EqualsAndHashCode(callSuper = false) +@SQLDelete(sql = "UPDATE review SET is_deleted = true WHERE id = ?") +@SQLRestriction("is_deleted = false") public class Review extends BaseEntity { @Id diff --git a/src/main/java/page/clab/api/domain/schedule/api/ScheduleController.java b/src/main/java/page/clab/api/domain/schedule/api/ScheduleController.java index e68c84434..77be53ab4 100644 --- a/src/main/java/page/clab/api/domain/schedule/api/ScheduleController.java +++ b/src/main/java/page/clab/api/domain/schedule/api/ScheduleController.java @@ -20,8 +20,8 @@ import page.clab.api.domain.schedule.dto.request.ScheduleRequestDto; import page.clab.api.domain.schedule.dto.response.ScheduleCollectResponseDto; import page.clab.api.domain.schedule.dto.response.ScheduleResponseDto; -import page.clab.api.global.common.dto.PagedResponseDto; import page.clab.api.global.common.dto.ApiResponse; +import page.clab.api.global.common.dto.PagedResponseDto; import page.clab.api.global.exception.PermissionDeniedException; import java.time.LocalDate; @@ -107,4 +107,16 @@ public ApiResponse deleteSchedule( return ApiResponse.success(id); } + @GetMapping("/deleted") + @Operation(summary = "[S] 삭제된 일정 조회하기", description = "ROLE_SUPER 이상의 권한이 필요함") + @Secured({"ROLE_SUPER"}) + public ApiResponse> getDeletedSchedules( + @RequestParam(name = "page", defaultValue = "0") int page, + @RequestParam(name = "size", defaultValue = "20") int size + ) { + Pageable pageable = PageRequest.of(page, size); + PagedResponseDto schedules = scheduleService.getDeletedSchedules(pageable); + return ApiResponse.success(schedules); + } + } diff --git a/src/main/java/page/clab/api/domain/schedule/application/ScheduleService.java b/src/main/java/page/clab/api/domain/schedule/application/ScheduleService.java index d2ce4e6b9..2263bf238 100644 --- a/src/main/java/page/clab/api/domain/schedule/application/ScheduleService.java +++ b/src/main/java/page/clab/api/domain/schedule/application/ScheduleService.java @@ -72,6 +72,12 @@ public PagedResponseDto getActivitySchedules(LocalDate star return new PagedResponseDto<>(schedules.map(ScheduleResponseDto::toDto)); } + @Transactional(readOnly = true) + public PagedResponseDto getDeletedSchedules(Pageable pageable) { + Page schedules = scheduleRepository.findAllByIsDeletedTrue(pageable); + return new PagedResponseDto<>(schedules.map(ScheduleResponseDto::toDto)); + } + public Long deleteSchedule(Long scheduleId) throws PermissionDeniedException { Member currentMember = memberService.getCurrentMember(); Schedule schedule = getScheduleById(scheduleId); diff --git a/src/main/java/page/clab/api/domain/schedule/dao/ScheduleRepository.java b/src/main/java/page/clab/api/domain/schedule/dao/ScheduleRepository.java index 84b67afd0..38c547949 100644 --- a/src/main/java/page/clab/api/domain/schedule/dao/ScheduleRepository.java +++ b/src/main/java/page/clab/api/domain/schedule/dao/ScheduleRepository.java @@ -1,9 +1,15 @@ package page.clab.api.domain.schedule.dao; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import org.springframework.data.querydsl.QuerydslPredicateExecutor; import page.clab.api.domain.schedule.domain.Schedule; public interface ScheduleRepository extends JpaRepository, ScheduleRepositoryCustom, QuerydslPredicateExecutor { + @Query(value = "SELECT s.* FROM schedule s WHERE s.is_deleted = true", nativeQuery = true) + Page findAllByIsDeletedTrue(Pageable pageable); + } diff --git a/src/main/java/page/clab/api/domain/schedule/domain/Schedule.java b/src/main/java/page/clab/api/domain/schedule/domain/Schedule.java index 69028e056..7a6534a84 100644 --- a/src/main/java/page/clab/api/domain/schedule/domain/Schedule.java +++ b/src/main/java/page/clab/api/domain/schedule/domain/Schedule.java @@ -16,6 +16,8 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.SQLRestriction; import page.clab.api.domain.activityGroup.domain.ActivityGroup; import page.clab.api.domain.member.domain.Member; import page.clab.api.global.common.domain.BaseEntity; @@ -30,6 +32,8 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor(access = AccessLevel.PRIVATE) @EqualsAndHashCode(callSuper = false) +@SQLDelete(sql = "UPDATE schedule SET is_deleted = true WHERE id = ?") +@SQLRestriction("is_deleted = false") public class Schedule extends BaseEntity { @Id diff --git a/src/main/java/page/clab/api/domain/sharedAccount/api/SharedAccountController.java b/src/main/java/page/clab/api/domain/sharedAccount/api/SharedAccountController.java index 92cd85742..afd1283b8 100644 --- a/src/main/java/page/clab/api/domain/sharedAccount/api/SharedAccountController.java +++ b/src/main/java/page/clab/api/domain/sharedAccount/api/SharedAccountController.java @@ -25,8 +25,8 @@ import page.clab.api.domain.sharedAccount.dto.request.SharedAccountUsageRequestDto; import page.clab.api.domain.sharedAccount.dto.response.SharedAccountResponseDto; import page.clab.api.domain.sharedAccount.dto.response.SharedAccountUsageResponseDto; -import page.clab.api.global.common.dto.PagedResponseDto; import page.clab.api.global.common.dto.ApiResponse; +import page.clab.api.global.common.dto.PagedResponseDto; import page.clab.api.global.exception.CustomOptimisticLockingFailureException; import page.clab.api.global.exception.PermissionDeniedException; @@ -120,5 +120,16 @@ public ApiResponse updateSharedAccountUsage( return ApiResponse.success(id); } -} + @GetMapping("/deleted") + @Operation(summary = "[S] 삭제된 공동 계정 조회하기", description = "ROLE_SUPER 이상의 권한이 필요함") + @Secured({"ROLE_SUPER"}) + public ApiResponse> getDeletedSharedAccounts( + @RequestParam(name = "page", defaultValue = "0") int page, + @RequestParam(name = "size", defaultValue = "20") int size + ) { + Pageable pageable = PageRequest.of(page, size); + PagedResponseDto sharedAccounts = sharedAccountService.getDeletedSharedAccounts(pageable); + return ApiResponse.success(sharedAccounts); + } +} \ No newline at end of file diff --git a/src/main/java/page/clab/api/domain/sharedAccount/application/SharedAccountService.java b/src/main/java/page/clab/api/domain/sharedAccount/application/SharedAccountService.java index cc5851d0d..cc87c56cb 100644 --- a/src/main/java/page/clab/api/domain/sharedAccount/application/SharedAccountService.java +++ b/src/main/java/page/clab/api/domain/sharedAccount/application/SharedAccountService.java @@ -35,6 +35,12 @@ public PagedResponseDto getSharedAccounts(Pageable pag return new PagedResponseDto<>(sharedAccounts.map(SharedAccountResponseDto::toDto)); } + @Transactional(readOnly = true) + public PagedResponseDto getDeletedSharedAccounts(Pageable pageable) { + Page sharedAccounts = sharedAccountRepository.findAllByIsDeletedTrue(pageable); + return new PagedResponseDto<>(sharedAccounts.map(SharedAccountResponseDto::toDto)); + } + @Transactional public Long updateSharedAccount(Long accountId, SharedAccountUpdateRequestDto requestDto) { SharedAccount sharedAccount = getSharedAccountByIdOrThrow(accountId); diff --git a/src/main/java/page/clab/api/domain/sharedAccount/dao/SharedAccountRepository.java b/src/main/java/page/clab/api/domain/sharedAccount/dao/SharedAccountRepository.java index d99286f3d..1e7efb18c 100644 --- a/src/main/java/page/clab/api/domain/sharedAccount/dao/SharedAccountRepository.java +++ b/src/main/java/page/clab/api/domain/sharedAccount/dao/SharedAccountRepository.java @@ -3,10 +3,14 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import page.clab.api.domain.sharedAccount.domain.SharedAccount; public interface SharedAccountRepository extends JpaRepository { Page findAllByOrderByIdAsc(Pageable pageable); + @Query(value = "SELECT s.* FROM shared_account s WHERE s.is_deleted = true", nativeQuery = true) + Page findAllByIsDeletedTrue(Pageable pageable); + } diff --git a/src/main/java/page/clab/api/domain/sharedAccount/domain/SharedAccount.java b/src/main/java/page/clab/api/domain/sharedAccount/domain/SharedAccount.java index 18ec58c2d..56bb0a8ac 100644 --- a/src/main/java/page/clab/api/domain/sharedAccount/domain/SharedAccount.java +++ b/src/main/java/page/clab/api/domain/sharedAccount/domain/SharedAccount.java @@ -12,6 +12,8 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.SQLRestriction; import org.hibernate.validator.constraints.URL; import page.clab.api.domain.sharedAccount.dto.request.SharedAccountUpdateRequestDto; import page.clab.api.global.common.domain.BaseEntity; @@ -24,6 +26,8 @@ @Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor(access = AccessLevel.PRIVATE) +@SQLDelete(sql = "UPDATE shared_account SET is_deleted = true WHERE id = ?") +@SQLRestriction("is_deleted = false") public class SharedAccount extends BaseEntity { @Id diff --git a/src/main/java/page/clab/api/domain/workExperience/api/WorkExperienceController.java b/src/main/java/page/clab/api/domain/workExperience/api/WorkExperienceController.java index 946fb445f..db5e21ccb 100644 --- a/src/main/java/page/clab/api/domain/workExperience/api/WorkExperienceController.java +++ b/src/main/java/page/clab/api/domain/workExperience/api/WorkExperienceController.java @@ -21,8 +21,8 @@ import page.clab.api.domain.workExperience.dto.request.WorkExperienceRequestDto; import page.clab.api.domain.workExperience.dto.request.WorkExperienceUpdateRequestDto; import page.clab.api.domain.workExperience.dto.response.WorkExperienceResponseDto; -import page.clab.api.global.common.dto.PagedResponseDto; import page.clab.api.global.common.dto.ApiResponse; +import page.clab.api.global.common.dto.PagedResponseDto; import page.clab.api.global.exception.PermissionDeniedException; @RestController @@ -94,4 +94,16 @@ public ApiResponse deleteWorkExperience( return ApiResponse.success(id); } + @GetMapping("/deleted") + @Operation(summary = "[S] 삭제된 경력사항 조회하기", description = "ROLE_SUPER 이상의 권한이 필요함") + @Secured({"ROLE_SUPER"}) + public ApiResponse> getDeletedWorkExperiences( + @RequestParam(name = "page", defaultValue = "0") int page, + @RequestParam(name = "size", defaultValue = "20") int size + ) { + Pageable pageable = PageRequest.of(page, size); + PagedResponseDto workExperiences = workExperienceService.getDeletedWorkExperiences(pageable); + return ApiResponse.success(workExperiences); + } + } diff --git a/src/main/java/page/clab/api/domain/workExperience/application/WorkExperienceService.java b/src/main/java/page/clab/api/domain/workExperience/application/WorkExperienceService.java index 2987f68c6..c013af943 100644 --- a/src/main/java/page/clab/api/domain/workExperience/application/WorkExperienceService.java +++ b/src/main/java/page/clab/api/domain/workExperience/application/WorkExperienceService.java @@ -49,6 +49,12 @@ public PagedResponseDto getWorkExperiencesByCondition return new PagedResponseDto<>(workExperiences.map(WorkExperienceResponseDto::toDto)); } + @Transactional(readOnly = true) + public PagedResponseDto getDeletedWorkExperiences(Pageable pageable) { + Page workExperiences = workExperienceRepository.findAllByIsDeletedTrue(pageable); + return new PagedResponseDto<>(workExperiences.map(WorkExperienceResponseDto::toDto)); + } + @Transactional public Long updateWorkExperience(Long workExperienceId, WorkExperienceUpdateRequestDto requestDto) throws PermissionDeniedException { Member currentMember = memberService.getCurrentMember(); diff --git a/src/main/java/page/clab/api/domain/workExperience/dao/WorkExperienceRepository.java b/src/main/java/page/clab/api/domain/workExperience/dao/WorkExperienceRepository.java index 9e50b19e3..4bd2da232 100644 --- a/src/main/java/page/clab/api/domain/workExperience/dao/WorkExperienceRepository.java +++ b/src/main/java/page/clab/api/domain/workExperience/dao/WorkExperienceRepository.java @@ -3,6 +3,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; import page.clab.api.domain.member.domain.Member; import page.clab.api.domain.workExperience.domain.WorkExperience; @@ -12,4 +13,7 @@ public interface WorkExperienceRepository extends JpaRepository findAllByMemberOrderByStartDateDesc(Member member, Pageable pageable); + @Query(value = "SELECT w.* FROM work_experience w WHERE w.is_deleted = true", nativeQuery = true) + Page findAllByIsDeletedTrue(Pageable pageable); + } diff --git a/src/main/java/page/clab/api/domain/workExperience/domain/WorkExperience.java b/src/main/java/page/clab/api/domain/workExperience/domain/WorkExperience.java index 2b5b403cd..b74f05ecd 100644 --- a/src/main/java/page/clab/api/domain/workExperience/domain/WorkExperience.java +++ b/src/main/java/page/clab/api/domain/workExperience/domain/WorkExperience.java @@ -13,6 +13,8 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.SQLRestriction; import page.clab.api.domain.member.domain.Member; import page.clab.api.domain.workExperience.dto.request.WorkExperienceUpdateRequestDto; import page.clab.api.global.common.domain.BaseEntity; @@ -27,6 +29,8 @@ @Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor(access = AccessLevel.PRIVATE) +@SQLDelete(sql = "UPDATE work_experience SET is_deleted = true WHERE id = ?") +@SQLRestriction("is_deleted = false") public class WorkExperience extends BaseEntity { @Id diff --git a/src/main/java/page/clab/api/global/auth/filter/IpAuthenticationFilter.java b/src/main/java/page/clab/api/global/auth/filter/IpAuthenticationFilter.java index 9692fa72c..e33516376 100644 --- a/src/main/java/page/clab/api/global/auth/filter/IpAuthenticationFilter.java +++ b/src/main/java/page/clab/api/global/auth/filter/IpAuthenticationFilter.java @@ -46,7 +46,7 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha } IPResponse ipResponse = storeIpInformation(clientIpAddress, httpRequest); if (isNonPermittedCountry(ipResponse)) { - log.warn("Access from non-permitted country: {}", ipResponse.getCountryCode()); + log.warn("[{}:{}] Access from non-permitted country", clientIpAddress, ipResponse.getCountryName()); return; } } catch (RateLimitedException e) { diff --git a/src/main/java/page/clab/api/global/common/domain/BaseEntity.java b/src/main/java/page/clab/api/global/common/domain/BaseEntity.java index 46d52154e..819fbf3b3 100644 --- a/src/main/java/page/clab/api/global/common/domain/BaseEntity.java +++ b/src/main/java/page/clab/api/global/common/domain/BaseEntity.java @@ -22,4 +22,7 @@ public class BaseEntity { @LastModifiedDate private LocalDateTime updatedAt; + @Column(name = "is_deleted") + protected Boolean isDeleted = Boolean.FALSE; + } \ No newline at end of file diff --git a/src/main/java/page/clab/api/global/common/slack/application/SlackService.java b/src/main/java/page/clab/api/global/common/slack/application/SlackService.java index a80b04e05..f1380fd56 100644 --- a/src/main/java/page/clab/api/global/common/slack/application/SlackService.java +++ b/src/main/java/page/clab/api/global/common/slack/application/SlackService.java @@ -3,6 +3,8 @@ import com.slack.api.Slack; import com.slack.api.webhook.Payload; import com.slack.api.webhook.WebhookResponse; +import io.ipinfo.api.model.IPResponse; +import io.ipinfo.spring.strategies.attribute.AttributeStrategy; import jakarta.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; @@ -30,10 +32,14 @@ public class SlackService { private final Slack slack = Slack.getInstance(); + private final String webhookUrl; - public SlackService(@Value("${slack.webhook.url}") String webhookUrl) { + private final AttributeStrategy attributeStrategy; + + public SlackService(@Value("${slack.webhook.url}") String webhookUrl, AttributeStrategy attributeStrategy) { this.webhookUrl = webhookUrl; + this.attributeStrategy = attributeStrategy; } public CompletableFuture sendServerErrorNotification(HttpServletRequest request, Exception e) { @@ -55,9 +61,11 @@ public CompletableFuture sendSecurityAlertNotification(HttpServletReque String requestUrl = request.getRequestURI(); Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); String username = (authentication == null || authentication.getName() == null) ? "anonymous" : authentication.getName(); + IPResponse ipResponse = attributeStrategy.getAttribute(request); + String location = ipResponse == null ? "Unknown" : ipResponse.getCountryName() + ", " + ipResponse.getCity(); - String message = String.format(":red_circle: *%s [%s] - %s*\n>*User*: %s\n>*Endpoint*: %s\n>*Details*: `%s`\n>```%s```", - alertType.getTitle(), clientIpAddress, serverTime, username, requestUrl, alertType.getDefaultMessage(), additionalMessage); + String message = String.format(":red_circle: *%s [%s] - %s*\n>*User*: %s\n>*Location*: %s\n>*Endpoint*: %s\n>*Details*: `%s`\n>```%s```", + alertType.getTitle(), clientIpAddress, serverTime, username, location, requestUrl, alertType.getDefaultMessage(), additionalMessage); return sendSlackMessage(message); } diff --git a/src/main/java/page/clab/api/global/handler/GlobalExceptionHandler.java b/src/main/java/page/clab/api/global/handler/GlobalExceptionHandler.java index 4ff6408ec..05df9d8bc 100644 --- a/src/main/java/page/clab/api/global/handler/GlobalExceptionHandler.java +++ b/src/main/java/page/clab/api/global/handler/GlobalExceptionHandler.java @@ -49,8 +49,8 @@ import page.clab.api.global.auth.exception.TokenNotFoundException; import page.clab.api.global.auth.exception.TokenValidateException; import page.clab.api.global.auth.exception.UnAuthorizeException; -import page.clab.api.global.common.dto.ErrorResponse; import page.clab.api.global.common.dto.ApiResponse; +import page.clab.api.global.common.dto.ErrorResponse; import page.clab.api.global.common.file.exception.AssignmentFileUploadFailException; import page.clab.api.global.common.file.exception.CloudStorageNotEnoughException; import page.clab.api.global.common.file.exception.FileUploadFailException; diff --git a/src/main/resources/continent.json b/src/main/resources/continent.json new file mode 100644 index 000000000..516fde24c --- /dev/null +++ b/src/main/resources/continent.json @@ -0,0 +1,252 @@ +{ + "BD": {"code": "AS", "name": "Asia"}, + "BE": {"code": "EU", "name": "Europe"}, + "BF": {"code": "AF", "name": "Africa"}, + "BG": {"code": "EU", "name": "Europe"}, + "BA": {"code": "EU", "name": "Europe"}, + "BB": {"code": "NA", "name": "North America"}, + "WF": {"code": "OC", "name": "Oceania"}, + "BL": {"code": "NA", "name": "North America"}, + "BM": {"code": "NA", "name": "North America"}, + "BN": {"code": "AS", "name": "Asia"}, + "BO": {"code": "SA", "name": "South America"}, + "BH": {"code": "AS", "name": "Asia"}, + "BI": {"code": "AF", "name": "Africa"}, + "BJ": {"code": "AF", "name": "Africa"}, + "BT": {"code": "AS", "name": "Asia"}, + "JM": {"code": "NA", "name": "North America"}, + "BV": {"code": "AN", "name": "Antarctica"}, + "BW": {"code": "AF", "name": "Africa"}, + "WS": {"code": "OC", "name": "Oceania"}, + "BQ": {"code": "NA", "name": "North America"}, + "BR": {"code": "SA", "name": "South America"}, + "BS": {"code": "NA", "name": "North America"}, + "JE": {"code": "EU", "name": "Europe"}, + "BY": {"code": "EU", "name": "Europe"}, + "BZ": {"code": "NA", "name": "North America"}, + "RU": {"code": "EU", "name": "Europe"}, + "RW": {"code": "AF", "name": "Africa"}, + "RS": {"code": "EU", "name": "Europe"}, + "TL": {"code": "OC", "name": "Oceania"}, + "RE": {"code": "AF", "name": "Africa"}, + "TM": {"code": "AS", "name": "Asia"}, + "TJ": {"code": "AS", "name": "Asia"}, + "RO": {"code": "EU", "name": "Europe"}, + "TK": {"code": "OC", "name": "Oceania"}, + "GW": {"code": "AF", "name": "Africa"}, + "GU": {"code": "OC", "name": "Oceania"}, + "GT": {"code": "NA", "name": "North America"}, + "GS": {"code": "AN", "name": "Antarctica"}, + "GR": {"code": "EU", "name": "Europe"}, + "GQ": {"code": "AF", "name": "Africa"}, + "GP": {"code": "NA", "name": "North America"}, + "JP": {"code": "AS", "name": "Asia"}, + "GY": {"code": "SA", "name": "South America"}, + "GG": {"code": "EU", "name": "Europe"}, + "GF": {"code": "SA", "name": "South America"}, + "GE": {"code": "AS", "name": "Asia"}, + "GD": {"code": "NA", "name": "North America"}, + "GB": {"code": "EU", "name": "Europe"}, + "GA": {"code": "AF", "name": "Africa"}, + "SV": {"code": "NA", "name": "North America"}, + "GN": {"code": "AF", "name": "Africa"}, + "GM": {"code": "AF", "name": "Africa"}, + "GL": {"code": "NA", "name": "North America"}, + "GI": {"code": "EU", "name": "Europe"}, + "GH": {"code": "AF", "name": "Africa"}, + "OM": {"code": "AS", "name": "Asia"}, + "TN": {"code": "AF", "name": "Africa"}, + "JO": {"code": "AS", "name": "Asia"}, + "HR": {"code": "EU", "name": "Europe"}, + "HT": {"code": "NA", "name": "North America"}, + "HU": {"code": "EU", "name": "Europe"}, + "HK": {"code": "AS", "name": "Asia"}, + "HN": {"code": "NA", "name": "North America"}, + "HM": {"code": "AN", "name": "Antarctica"}, + "VE": {"code": "SA", "name": "South America"}, + "PR": {"code": "NA", "name": "North America"}, + "PS": {"code": "AS", "name": "Asia"}, + "PW": {"code": "OC", "name": "Oceania"}, + "PT": {"code": "EU", "name": "Europe"}, + "SJ": {"code": "EU", "name": "Europe"}, + "PY": {"code": "SA", "name": "South America"}, + "IQ": {"code": "AS", "name": "Asia"}, + "PA": {"code": "NA", "name": "North America"}, + "PF": {"code": "OC", "name": "Oceania"}, + "PG": {"code": "OC", "name": "Oceania"}, + "PE": {"code": "SA", "name": "South America"}, + "PK": {"code": "AS", "name": "Asia"}, + "PH": {"code": "AS", "name": "Asia"}, + "PN": {"code": "OC", "name": "Oceania"}, + "PL": {"code": "EU", "name": "Europe"}, + "PM": {"code": "NA", "name": "North America"}, + "ZM": {"code": "AF", "name": "Africa"}, + "EH": {"code": "AF", "name": "Africa"}, + "EE": {"code": "EU", "name": "Europe"}, + "EG": {"code": "AF", "name": "Africa"}, + "ZA": {"code": "AF", "name": "Africa"}, + "EC": {"code": "SA", "name": "South America"}, + "IT": {"code": "EU", "name": "Europe"}, + "VN": {"code": "AS", "name": "Asia"}, + "SB": {"code": "OC", "name": "Oceania"}, + "ET": {"code": "AF", "name": "Africa"}, + "SO": {"code": "AF", "name": "Africa"}, + "ZW": {"code": "AF", "name": "Africa"}, + "SA": {"code": "AS", "name": "Asia"}, + "ES": {"code": "EU", "name": "Europe"}, + "ER": {"code": "AF", "name": "Africa"}, + "ME": {"code": "EU", "name": "Europe"}, + "MD": {"code": "EU", "name": "Europe"}, + "MG": {"code": "AF", "name": "Africa"}, + "MF": {"code": "NA", "name": "North America"}, + "MA": {"code": "AF", "name": "Africa"}, + "MC": {"code": "EU", "name": "Europe"}, + "UZ": {"code": "AS", "name": "Asia"}, + "MM": {"code": "AS", "name": "Asia"}, + "ML": {"code": "AF", "name": "Africa"}, + "MO": {"code": "AS", "name": "Asia"}, + "MN": {"code": "AS", "name": "Asia"}, + "MH": {"code": "OC", "name": "Oceania"}, + "MK": {"code": "EU", "name": "Europe"}, + "MU": {"code": "AF", "name": "Africa"}, + "MT": {"code": "EU", "name": "Europe"}, + "MW": {"code": "AF", "name": "Africa"}, + "MV": {"code": "AS", "name": "Asia"}, + "MQ": {"code": "NA", "name": "North America"}, + "MP": {"code": "OC", "name": "Oceania"}, + "MS": {"code": "NA", "name": "North America"}, + "MR": {"code": "AF", "name": "Africa"}, + "IM": {"code": "EU", "name": "Europe"}, + "UG": {"code": "AF", "name": "Africa"}, + "TZ": {"code": "AF", "name": "Africa"}, + "MY": {"code": "AS", "name": "Asia"}, + "MX": {"code": "NA", "name": "North America"}, + "IL": {"code": "AS", "name": "Asia"}, + "FR": {"code": "EU", "name": "Europe"}, + "IO": {"code": "AS", "name": "Asia"}, + "SH": {"code": "AF", "name": "Africa"}, + "FI": {"code": "EU", "name": "Europe"}, + "FJ": {"code": "OC", "name": "Oceania"}, + "FK": {"code": "SA", "name": "South America"}, + "FM": {"code": "OC", "name": "Oceania"}, + "FO": {"code": "EU", "name": "Europe"}, + "NI": {"code": "NA", "name": "North America"}, + "NL": {"code": "EU", "name": "Europe"}, + "NO": {"code": "EU", "name": "Europe"}, + "NA": {"code": "AF", "name": "Africa"}, + "VU": {"code": "OC", "name": "Oceania"}, + "NC": {"code": "OC", "name": "Oceania"}, + "NE": {"code": "AF", "name": "Africa"}, + "NF": {"code": "OC", "name": "Oceania"}, + "NG": {"code": "AF", "name": "Africa"}, + "NZ": {"code": "OC", "name": "Oceania"}, + "NP": {"code": "AS", "name": "Asia"}, + "NR": {"code": "OC", "name": "Oceania"}, + "NU": {"code": "OC", "name": "Oceania"}, + "CK": {"code": "OC", "name": "Oceania"}, + "XK": {"code": "EU", "name": "Europe"}, + "CI": {"code": "AF", "name": "Africa"}, + "CH": {"code": "EU", "name": "Europe"}, + "CO": {"code": "SA", "name": "South America"}, + "CN": {"code": "AS", "name": "Asia"}, + "CM": {"code": "AF", "name": "Africa"}, + "CL": {"code": "SA", "name": "South America"}, + "CC": {"code": "AS", "name": "Asia"}, + "CA": {"code": "NA", "name": "North America"}, + "CG": {"code": "AF", "name": "Africa"}, + "CF": {"code": "AF", "name": "Africa"}, + "CD": {"code": "AF", "name": "Africa"}, + "CZ": {"code": "EU", "name": "Europe"}, + "CY": {"code": "EU", "name": "Europe"}, + "CX": {"code": "AS", "name": "Asia"}, + "CR": {"code": "NA", "name": "North America"}, + "CW": {"code": "NA", "name": "North America"}, + "CV": {"code": "AF", "name": "Africa"}, + "CU": {"code": "NA", "name": "North America"}, + "SZ": {"code": "AF", "name": "Africa"}, + "SY": {"code": "AS", "name": "Asia"}, + "SX": {"code": "NA", "name": "North America"}, + "KG": {"code": "AS", "name": "Asia"}, + "KE": {"code": "AF", "name": "Africa"}, + "SS": {"code": "AF", "name": "Africa"}, + "SR": {"code": "SA", "name": "South America"}, + "KI": {"code": "OC", "name": "Oceania"}, + "KH": {"code": "AS", "name": "Asia"}, + "KN": {"code": "NA", "name": "North America"}, + "KM": {"code": "AF", "name": "Africa"}, + "ST": {"code": "AF", "name": "Africa"}, + "SK": {"code": "EU", "name": "Europe"}, + "KR": {"code": "AS", "name": "Asia"}, + "SI": {"code": "EU", "name": "Europe"}, + "KP": {"code": "AS", "name": "Asia"}, + "KW": {"code": "AS", "name": "Asia"}, + "SN": {"code": "AF", "name": "Africa"}, + "SM": {"code": "EU", "name": "Europe"}, + "SL": {"code": "AF", "name": "Africa"}, + "SC": {"code": "AF", "name": "Africa"}, + "KZ": {"code": "AS", "name": "Asia"}, + "KY": {"code": "NA", "name": "North America"}, + "SG": {"code": "AS", "name": "Asia"}, + "SE": {"code": "EU", "name": "Europe"}, + "SD": {"code": "AF", "name": "Africa"}, + "DO": {"code": "NA", "name": "North America"}, + "DM": {"code": "NA", "name": "North America"}, + "DJ": {"code": "AF", "name": "Africa"}, + "DK": {"code": "EU", "name": "Europe"}, + "VG": {"code": "NA", "name": "North America"}, + "DE": {"code": "EU", "name": "Europe"}, + "YE": {"code": "AS", "name": "Asia"}, + "DZ": {"code": "AF", "name": "Africa"}, + "US": {"code": "NA", "name": "North America"}, + "UY": {"code": "SA", "name": "South America"}, + "YT": {"code": "AF", "name": "Africa"}, + "UM": {"code": "OC", "name": "Oceania"}, + "LB": {"code": "AS", "name": "Asia"}, + "LC": {"code": "NA", "name": "North America"}, + "LA": {"code": "AS", "name": "Asia"}, + "TV": {"code": "OC", "name": "Oceania"}, + "TW": {"code": "AS", "name": "Asia"}, + "TT": {"code": "NA", "name": "North America"}, + "TR": {"code": "AS", "name": "Asia"}, + "LK": {"code": "AS", "name": "Asia"}, + "LI": {"code": "EU", "name": "Europe"}, + "LV": {"code": "EU", "name": "Europe"}, + "TO": {"code": "OC", "name": "Oceania"}, + "LT": {"code": "EU", "name": "Europe"}, + "LU": {"code": "EU", "name": "Europe"}, + "LR": {"code": "AF", "name": "Africa"}, + "LS": {"code": "AF", "name": "Africa"}, + "TH": {"code": "AS", "name": "Asia"}, + "TF": {"code": "AN", "name": "Antarctica"}, + "TG": {"code": "AF", "name": "Africa"}, + "TD": {"code": "AF", "name": "Africa"}, + "TC": {"code": "NA", "name": "North America"}, + "LY": {"code": "AF", "name": "Africa"}, + "VA": {"code": "EU", "name": "Europe"}, + "VC": {"code": "NA", "name": "North America"}, + "AE": {"code": "AS", "name": "Asia"}, + "AD": {"code": "EU", "name": "Europe"}, + "AG": {"code": "NA", "name": "North America"}, + "AF": {"code": "AS", "name": "Asia"}, + "AI": {"code": "NA", "name": "North America"}, + "VI": {"code": "NA", "name": "North America"}, + "IS": {"code": "EU", "name": "Europe"}, + "IR": {"code": "AS", "name": "Asia"}, + "AM": {"code": "AS", "name": "Asia"}, + "AL": {"code": "EU", "name": "Europe"}, + "AO": {"code": "AF", "name": "Africa"}, + "AQ": {"code": "AN", "name": "Antarctica"}, + "AS": {"code": "OC", "name": "Oceania"}, + "AR": {"code": "SA", "name": "South America"}, + "AU": {"code": "OC", "name": "Oceania"}, + "AT": {"code": "EU", "name": "Europe"}, + "AW": {"code": "NA", "name": "North America"}, + "IN": {"code": "AS", "name": "Asia"}, + "AX": {"code": "EU", "name": "Europe"}, + "AZ": {"code": "AS", "name": "Asia"}, + "IE": {"code": "EU", "name": "Europe"}, + "ID": {"code": "AS", "name": "Asia"}, + "UA": {"code": "EU", "name": "Europe"}, + "QA": {"code": "AS", "name": "Asia"}, + "MZ": {"code": "AF", "name": "Africa"} +} diff --git a/src/main/resources/currency.json b/src/main/resources/currency.json new file mode 100644 index 000000000..2aea66d06 --- /dev/null +++ b/src/main/resources/currency.json @@ -0,0 +1,246 @@ +{ + "BD": {"code": "BDT", "symbol": "৳"}, + "BE": {"code": "EUR", "symbol": "€"}, + "BF": {"code": "XOF", "symbol": "CFA"}, + "BG": {"code": "BGN", "symbol": "лв"}, + "BA": {"code": "BAM", "symbol": "KM"}, + "BB": {"code": "BBD", "symbol": "$"}, + "WF": {"code": "XPF", "symbol": "₣"}, + "BL": {"code": "EUR", "symbol": "€"}, + "BM": {"code": "BMD", "symbol": "$"}, + "BN": {"code": "BND", "symbol": "$"}, + "BO": {"code": "BOB", "symbol": "$b"}, + "BH": {"code": "BHD", "symbol": ".د.ب"}, + "BI": {"code": "BIF", "symbol": "FBu"}, + "BJ": {"code": "XOF", "symbol": "CFA"}, + "BT": {"code": "BTN", "symbol": "Nu."}, + "JM": {"code": "JMD", "symbol": "J$"}, + "BV": {"code": "NOK", "symbol": "kr"}, + "BW": {"code": "BWP", "symbol": "P"}, + "WS": {"code": "WST", "symbol": "WS$"}, + "BQ": {"code": "USD", "symbol": "$"}, + "BR": {"code": "BRL", "symbol": "R$"}, + "BS": {"code": "BSD", "symbol": "$"}, + "JE": {"code": "GBP", "symbol": "£"}, + "BY": {"code": "BYN", "symbol": "Br"}, + "BZ": {"code": "BZD", "symbol": "BZ$"}, + "RU": {"code": "RUB", "symbol": "₽"}, + "RW": {"code": "RWF", "symbol": "FRw"}, + "RS": {"code": "RSD", "symbol": "Дин."}, + "TL": {"code": "USD", "symbol": "$"}, + "RE": {"code": "EUR", "symbol": "€"}, + "TM": {"code": "TMT", "symbol": "T"}, + "TJ": {"code": "TJS", "symbol": "SM"}, + "RO": {"code": "RON", "symbol": "lei"}, + "TK": {"code": "NZD", "symbol": "$"}, + "GW": {"code": "XOF", "symbol": "CFA"}, + "GU": {"code": "USD", "symbol": "$"}, + "GT": {"code": "GTQ", "symbol": "Q"}, + "GS": {"code": "GBP", "symbol": "£"}, + "GR": {"code": "EUR", "symbol": "€"}, + "GQ": {"code": "XAF", "symbol": "FCFA"}, + "GP": {"code": "EUR", "symbol": "€"}, + "JP": {"code": "JPY", "symbol": "¥"}, + "GY": {"code": "GYD", "symbol": "$"}, + "GG": {"code": "GBP", "symbol": "£"}, + "GF": {"code": "EUR", "symbol": "€"}, + "GE": {"code": "GEL", "symbol": "ლ"}, + "GD": {"code": "XCD", "symbol": "$"}, + "GB": {"code": "GBP", "symbol": "£"}, + "GA": {"code": "XAF", "symbol": "FCFA"}, + "SV": {"code": "USD", "symbol": "$"}, + "GN": {"code": "GNF", "symbol": "FG"}, + "GM": {"code": "GMD", "symbol": "D"}, + "GL": {"code": "DKK", "symbol": "kr"}, + "GI": {"code": "GIP", "symbol": "£"}, + "GH": {"code": "GHS", "symbol": "GH₵"}, + "OM": {"code": "OMR", "symbol": "﷼"}, + "TN": {"code": "TND", "symbol": "د.ت"}, + "JO": {"code": "JOD", "symbol": "JD"}, + "HR": {"code": "HRK", "symbol": "kn"}, + "HT": {"code": "HTG", "symbol": "G"}, + "HU": {"code": "HUF", "symbol": "Ft"}, + "HK": {"code": "HKD", "symbol": "$"}, + "HN": {"code": "HNL", "symbol": "L"}, + "HM": {"code": "AUD", "symbol": "$"}, + "VE": {"code": "VES", "symbol": "Bs"}, + "PR": {"code": "USD", "symbol": "$"}, + "PS": {"code": "ILS", "symbol": "₪"}, + "PW": {"code": "USD", "symbol": "$"}, + "PT": {"code": "EUR", "symbol": "€"}, + "SJ": {"code": "NOK", "symbol": "kr"}, + "PY": {"code": "PYG", "symbol": "₲"}, + "IQ": {"code": "IQD", "symbol": "ع.د"}, + "PA": {"code": "PAB", "symbol": "B/."}, + "PF": {"code": "XPF", "symbol": "₣"}, + "PG": {"code": "PGK", "symbol": "K"}, + "PE": {"code": "PEN", "symbol": "S/"}, + "PK": {"code": "PKR", "symbol": "₨"}, + "PH": {"code": "PHP", "symbol": "₱"}, + "PN": {"code": "NZD", "symbol": "$"}, + "PL": {"code": "PLN", "symbol": "zł"}, + "PM": {"code": "EUR", "symbol": "€"}, + "ZM": {"code": "ZMW", "symbol": "ZK"}, + "EH": {"code": "MAD", "symbol": "MAD"}, + "EE": {"code": "EUR", "symbol": "€"}, + "EG": {"code": "EGP", "symbol": "£"}, + "ZA": {"code": "ZAR", "symbol": "R"}, + "EC": {"code": "USD", "symbol": "$"}, + "IT": {"code": "EUR", "symbol": "€"}, + "VN": {"code": "VND", "symbol": "₫"}, + "SB": {"code": "SBD", "symbol": "$"}, + "ET": {"code": "ETB", "symbol": "Br"}, + "SO": {"code": "SOS", "symbol": "S"}, + "ZW": {"code": "ZWL", "symbol": "Z$"}, + "SA": {"code": "SAR", "symbol": "﷼"}, + "ES": {"code": "EUR", "symbol": "€"}, + "ER": {"code": "ERN", "symbol": "Nfk"}, + "ME": {"code": "EUR", "symbol": "€"}, + "MD": {"code": "MDL", "symbol": "lei"}, + "MG": {"code": "MGA", "symbol": "Ar"}, + "MF": {"code": "EUR", "symbol": "€"}, + "MA": {"code": "MAD", "symbol": "MAD"}, + "MC": {"code": "EUR", "symbol": "€"}, + "UZ": {"code": "UZS", "symbol": "лв"}, + "MM": {"code": "MMK", "symbol": "K"}, + "ML": {"code": "XOF", "symbol": "CFA"}, + "MO": {"code": "MOP", "symbol": "MOP$"}, + "MN": {"code": "MNT", "symbol": "₮"}, + "MH": {"code": "USD", "symbol": "$"}, + "MK": {"code": "MKD", "symbol": "ден"}, + "MU": {"code": "MUR", "symbol": "₨"}, + "MT": {"code": "EUR", "symbol": "€"}, + "MW": {"code": "MWK", "symbol": "MK"}, + "MQ": {"code": "EUR", "symbol": "€"}, + "MP": {"code": "USD", "symbol": "$"}, + "MS": {"code": "XCD", "symbol": "$"}, + "MR": {"code": "MRO", "symbol": "UM"}, + "IM": {"code": "GBP", "symbol": "£"}, + "UG": {"code": "UGX", "symbol": "USh"}, + "TZ": {"code": "TZS", "symbol": "TSh"}, + "MY": {"code": "MYR", "symbol": "RM"}, + "MX": {"code": "MXN", "symbol": "$"}, + "IL": {"code": "ILS", "symbol": "₪"}, + "FR": {"code": "EUR", "symbol": "€"}, + "IO": {"code": "USD", "symbol": "$"}, + "SH": {"code": "SHP", "symbol": "£"}, + "FI": {"code": "EUR", "symbol": "€"}, + "FJ": {"code": "FJD", "symbol": "$"}, + "FK": {"code": "FKP", "symbol": "£"}, + "FM": {"code": "USD", "symbol": "$"}, + "FO": {"code": "DKK", "symbol": "kr"}, + "NI": {"code": "NIO", "symbol": "C$"}, + "NL": {"code": "EUR", "symbol": "€"}, + "NO": {"code": "NOK", "symbol": "kr"}, + "NA": {"code": "NAD", "symbol": "N$"}, + "VU": {"code": "VUV", "symbol": "VT"}, + "NC": {"code": "XPF", "symbol": "₣"}, + "NE": {"code": "XOF", "symbol": "CFA"}, + "NF": {"code": "AUD", "symbol": "$"}, + "NG": {"code": "NGN", "symbol": "₦"}, + "NZ": {"code": "NZD", "symbol": "$"}, + "NP": {"code": "NPR", "symbol": "₨"}, + "NR": {"code": "AUD", "symbol": "$"}, + "NU": {"code": "NZD", "symbol": "$"}, + "CK": {"code": "NZD", "symbol": "$"}, + "XK": {"code": "EUR", "symbol": "€"}, + "CI": {"code": "XOF", "symbol": "CFA"}, + "CH": {"code": "CHF", "symbol": "CHF"}, + "CO": {"code": "COP", "symbol": "$"}, + "CN": {"code": "CNY", "symbol": "¥"}, + "CM": {"code": "XAF", "symbol": "FCFA"}, + "CL": {"code": "CLP", "symbol": "$"}, + "CC": {"code": "AUD", "symbol": "$"}, + "CA": {"code": "CAD", "symbol": "$"}, + "CG": {"code": "XAF", "symbol": "FCFA"}, + "CF": {"code": "XAF", "symbol": "FCFA"}, + "CD": {"code": "CDF", "symbol": "FC"}, + "CZ": {"code": "CZK", "symbol": "Kč"}, + "CY": {"code": "EUR", "symbol": "€"}, + "CX": {"code": "AUD", "symbol": "$"}, + "CR": {"code": "CRC", "symbol": "₡"}, + "CW": {"code": "ANG", "symbol": "ƒ"}, + "CV": {"code": "CVE", "symbol": "$"}, + "CU": {"code": "CUP", "symbol": "₱"}, + "SZ": {"code": "SZL", "symbol": "L"}, + "SY": {"code": "SYP", "symbol": "£"}, + "SX": {"code": "ANG", "symbol": "ƒ"}, + "KG": {"code": "KGS", "symbol": "лв"}, + "KE": {"code": "KES", "symbol": "KSh"}, + "SS": {"code": "SSP", "symbol": "£"}, + "SR": {"code": "SRD", "symbol": "$"}, + "KI": {"code": "AUD", "symbol": "$"}, + "KH": {"code": "KHR", "symbol": "៛"}, + "KN": {"code": "XCD", "symbol": "$"}, + "KM": {"code": "KMF", "symbol": "CF"}, + "ST": {"code": "STD", "symbol": "Db"}, + "SK": {"code": "EUR", "symbol": "€"}, + "KR": {"code": "KRW", "symbol": "₩"}, + "SI": {"code": "EUR", "symbol": "€"}, + "KP": {"code": "KPW", "symbol": "₩"}, + "KW": {"code": "KWD", "symbol": "KD"}, + "SN": {"code": "XOF", "symbol": "CFA"}, + "SM": {"code": "EUR", "symbol": "€"}, + "SL": {"code": "SLL", "symbol": "Le"}, + "SC": {"code": "SCR", "symbol": "₨"}, + "KZ": {"code": "KZT", "symbol": "₸"}, + "KY": {"code": "KYD", "symbol": "$"}, + "SG": {"code": "SGD", "symbol": "$"}, + "SE": {"code": "SEK", "symbol": "kr"}, + "SD": {"code": "SDG", "symbol": "ج.س."}, + "DO": {"code": "DOP", "symbol": "RD$"}, + "DM": {"code": "XCD", "symbol": "$"}, + "DJ": {"code": "DJF", "symbol": "Fdj"}, + "DK": {"code": "DKK", "symbol": "kr"}, + "VG": {"code": "USD", "symbol": "$"}, + "DE": {"code": "EUR", "symbol": "€"}, + "YE": {"code": "YER", "symbol": "﷼"}, + "DZ": {"code": "DZD", "symbol": "دج"}, + "US": {"code": "USD", "symbol": "$"}, + "UY": {"code": "UYU", "symbol": "$U"}, + "YT": {"code": "EUR", "symbol": "€"}, + "UM": {"code": "USD", "symbol": "$"}, + "LB": {"code": "LBP", "symbol": "£"}, + "LC": {"code": "XCD", "symbol": "$"}, + "LA": {"code": "LAK", "symbol": "₭"}, + "TV": {"code": "AUD", "symbol": "$"}, + "TW": {"code": "TWD", "symbol": "NT$"}, + "TT": {"code": "TTD", "symbol": "TT$"}, + "TR": {"code": "TRY", "symbol": "₺"}, + "LK": {"code": "LKR", "symbol": "₨"}, + "LI": {"code": "CHF", "symbol": "CHF"}, + "LV": {"code": "EUR", "symbol": "€"}, + "TO": {"code": "TOP", "symbol": "T$"}, + "LT": {"code": "EUR", "symbol": "€"}, + "LU": {"code": "EUR", "symbol": "€"}, + "LR": {"code": "LRD", "symbol": "$"}, + "LS": {"code": "LSL", "symbol": "M"}, + "TH": {"code": "THB", "symbol": "฿"}, + "TF": {"code": "EUR", "symbol": "€"}, + "TG": {"code": "XOF", "symbol": "CFA"}, + "TD": {"code": "XAF", "symbol": "FCFA"}, + "TC": {"code": "USD", "symbol": "$"}, + "LY": {"code": "LYD", "symbol": "LD"}, + "VA": {"code": "EUR", "symbol": "€"}, + "VC": {"code": "XCD", "symbol": "$"}, + "VI": {"code": "USD", "symbol": "$"}, + "IS": {"code": "ISK", "symbol": "kr"}, + "IR": {"code": "IRR", "symbol": "﷼"}, + "AM": {"code": "AMD", "symbol": "֏"}, + "AL": {"code": "ALL", "symbol": "L"}, + "AO": {"code": "AOA", "symbol": "Kz"}, + "AQ": {"code": "USD", "symbol": "$"}, + "AS": {"code": "USD", "symbol": "$"}, + "AR": {"code": "ARS", "symbol": "$"}, + "AU": {"code": "AUD", "symbol": "$"}, + "AT": {"code": "EUR", "symbol": "€"}, + "AW": {"code": "AWG", "symbol": "ƒ"}, + "IN": {"code": "INR", "symbol": "₹"}, + "AX": {"code": "EUR", "symbol": "€"}, + "AZ": {"code": "AZN", "symbol": "₼"}, + "IE": {"code": "EUR", "symbol": "€"}, + "ID": {"code": "IDR", "symbol": "Rp"}, + "UA": {"code": "UAH", "symbol": "₴"}, + "QA": {"code": "QAR", "symbol": "﷼"}, + "MZ": {"code": "MZN", "symbol": "MT"} +} diff --git a/src/main/resources/en_US.json b/src/main/resources/en_US.json new file mode 100644 index 000000000..db5315c48 --- /dev/null +++ b/src/main/resources/en_US.json @@ -0,0 +1,252 @@ +{ + "BD": "Bangladesh", + "BE": "Belgium", + "BF": "Burkina Faso", + "BG": "Bulgaria", + "BA": "Bosnia and Herzegovina", + "BB": "Barbados", + "WF": "Wallis and Futuna", + "BL": "Saint Barthelemy", + "BM": "Bermuda", + "BN": "Brunei", + "BO": "Bolivia", + "BH": "Bahrain", + "BI": "Burundi", + "BJ": "Benin", + "BT": "Bhutan", + "JM": "Jamaica", + "BV": "Bouvet Island", + "BW": "Botswana", + "WS": "Samoa", + "BQ": "Bonaire, Saint Eustatius and Saba", + "BR": "Brazil", + "BS": "Bahamas", + "JE": "Jersey", + "BY": "Belarus", + "BZ": "Belize", + "RU": "Russia", + "RW": "Rwanda", + "RS": "Serbia", + "TL": "East Timor", + "RE": "Reunion", + "TM": "Turkmenistan", + "TJ": "Tajikistan", + "RO": "Romania", + "TK": "Tokelau", + "GW": "Guinea-Bissau", + "GU": "Guam", + "GT": "Guatemala", + "GS": "South Georgia and the South Sandwich Islands", + "GR": "Greece", + "GQ": "Equatorial Guinea", + "GP": "Guadeloupe", + "JP": "Japan", + "GY": "Guyana", + "GG": "Guernsey", + "GF": "French Guiana", + "GE": "Georgia", + "GD": "Grenada", + "GB": "United Kingdom", + "GA": "Gabon", + "SV": "El Salvador", + "GN": "Guinea", + "GM": "Gambia", + "GL": "Greenland", + "GI": "Gibraltar", + "GH": "Ghana", + "OM": "Oman", + "TN": "Tunisia", + "JO": "Jordan", + "HR": "Croatia", + "HT": "Haiti", + "HU": "Hungary", + "HK": "Hong Kong", + "HN": "Honduras", + "HM": "Heard Island and McDonald Islands", + "VE": "Venezuela", + "PR": "Puerto Rico", + "PS": "Palestinian Territory", + "PW": "Palau", + "PT": "Portugal", + "SJ": "Svalbard and Jan Mayen", + "PY": "Paraguay", + "IQ": "Iraq", + "PA": "Panama", + "PF": "French Polynesia", + "PG": "Papua New Guinea", + "PE": "Peru", + "PK": "Pakistan", + "PH": "Philippines", + "PN": "Pitcairn", + "PL": "Poland", + "PM": "Saint Pierre and Miquelon", + "ZM": "Zambia", + "EH": "Western Sahara", + "EE": "Estonia", + "EG": "Egypt", + "ZA": "South Africa", + "EC": "Ecuador", + "IT": "Italy", + "VN": "Vietnam", + "SB": "Solomon Islands", + "ET": "Ethiopia", + "SO": "Somalia", + "ZW": "Zimbabwe", + "SA": "Saudi Arabia", + "ES": "Spain", + "ER": "Eritrea", + "ME": "Montenegro", + "MD": "Moldova", + "MG": "Madagascar", + "MF": "Saint Martin", + "MA": "Morocco", + "MC": "Monaco", + "UZ": "Uzbekistan", + "MM": "Myanmar", + "ML": "Mali", + "MO": "Macao", + "MN": "Mongolia", + "MH": "Marshall Islands", + "MK": "Macedonia", + "MU": "Mauritius", + "MT": "Malta", + "MW": "Malawi", + "MV": "Maldives", + "MQ": "Martinique", + "MP": "Northern Mariana Islands", + "MS": "Montserrat", + "MR": "Mauritania", + "IM": "Isle of Man", + "UG": "Uganda", + "TZ": "Tanzania", + "MY": "Malaysia", + "MX": "Mexico", + "IL": "Israel", + "FR": "France", + "IO": "British Indian Ocean Territory", + "SH": "Saint Helena", + "FI": "Finland", + "FJ": "Fiji", + "FK": "Falkland Islands", + "FM": "Micronesia", + "FO": "Faroe Islands", + "NI": "Nicaragua", + "NL": "Netherlands", + "NO": "Norway", + "NA": "Namibia", + "VU": "Vanuatu", + "NC": "New Caledonia", + "NE": "Niger", + "NF": "Norfolk Island", + "NG": "Nigeria", + "NZ": "New Zealand", + "NP": "Nepal", + "NR": "Nauru", + "NU": "Niue", + "CK": "Cook Islands", + "XK": "Kosovo", + "CI": "Ivory Coast", + "CH": "Switzerland", + "CO": "Colombia", + "CN": "China", + "CM": "Cameroon", + "CL": "Chile", + "CC": "Cocos Islands", + "CA": "Canada", + "CG": "Republic of the Congo", + "CF": "Central African Republic", + "CD": "Democratic Republic of the Congo", + "CZ": "Czech Republic", + "CY": "Cyprus", + "CX": "Christmas Island", + "CR": "Costa Rica", + "CW": "Curacao", + "CV": "Cape Verde", + "CU": "Cuba", + "SZ": "Swaziland", + "SY": "Syria", + "SX": "Sint Maarten", + "KG": "Kyrgyzstan", + "KE": "Kenya", + "SS": "South Sudan", + "SR": "Suriname", + "KI": "Kiribati", + "KH": "Cambodia", + "KN": "Saint Kitts and Nevis", + "KM": "Comoros", + "ST": "Sao Tome and Principe", + "SK": "Slovakia", + "KR": "South Korea", + "SI": "Slovenia", + "KP": "North Korea", + "KW": "Kuwait", + "SN": "Senegal", + "SM": "San Marino", + "SL": "Sierra Leone", + "SC": "Seychelles", + "KZ": "Kazakhstan", + "KY": "Cayman Islands", + "SG": "Singapore", + "SE": "Sweden", + "SD": "Sudan", + "DO": "Dominican Republic", + "DM": "Dominica", + "DJ": "Djibouti", + "DK": "Denmark", + "VG": "British Virgin Islands", + "DE": "Germany", + "YE": "Yemen", + "DZ": "Algeria", + "US": "United States", + "UY": "Uruguay", + "YT": "Mayotte", + "UM": "United States Minor Outlying Islands", + "LB": "Lebanon", + "LC": "Saint Lucia", + "LA": "Laos", + "TV": "Tuvalu", + "TW": "Taiwan", + "TT": "Trinidad and Tobago", + "TR": "Turkey", + "LK": "Sri Lanka", + "LI": "Liechtenstein", + "LV": "Latvia", + "TO": "Tonga", + "LT": "Lithuania", + "LU": "Luxembourg", + "LR": "Liberia", + "LS": "Lesotho", + "TH": "Thailand", + "TF": "French Southern Territories", + "TG": "Togo", + "TD": "Chad", + "TC": "Turks and Caicos Islands", + "LY": "Libya", + "VA": "Vatican", + "VC": "Saint Vincent and the Grenadines", + "AE": "United Arab Emirates", + "AD": "Andorra", + "AG": "Antigua and Barbuda", + "AF": "Afghanistan", + "AI": "Anguilla", + "VI": "U.S. Virgin Islands", + "IS": "Iceland", + "IR": "Iran", + "AM": "Armenia", + "AL": "Albania", + "AO": "Angola", + "AQ": "Antarctica", + "AS": "American Samoa", + "AR": "Argentina", + "AU": "Australia", + "AT": "Austria", + "AW": "Aruba", + "IN": "India", + "AX": "Aland Islands", + "AZ": "Azerbaijan", + "IE": "Ireland", + "ID": "Indonesia", + "UA": "Ukraine", + "QA": "Qatar", + "MZ": "Mozambique" +} diff --git a/src/main/resources/eu.json b/src/main/resources/eu.json new file mode 100644 index 000000000..1a2543025 --- /dev/null +++ b/src/main/resources/eu.json @@ -0,0 +1,29 @@ +[ + "IE", + "AT", + "LT", + "LU", + "LV", + "DE", + "DK", + "SE", + "SI", + "SK", + "CZ", + "CY", + "NL", + "FI", + "FR", + "MT", + "ES", + "IT", + "EE", + "PL", + "PT", + "HU", + "HR", + "GR", + "RO", + "BG", + "BE" +] diff --git a/src/main/resources/flags.json b/src/main/resources/flags.json new file mode 100644 index 000000000..4c7719918 --- /dev/null +++ b/src/main/resources/flags.json @@ -0,0 +1,252 @@ +{ + "BD": {"emoji": "🇧🇩", "unicode": "U+1F1E7 U+1F1E9"}, + "BE": {"emoji": "🇧🇪", "unicode": "U+1F1E7 U+1F1EA"}, + "BF": {"emoji": "🇧🇫", "unicode": "U+1F1E7 U+1F1EB"}, + "BG": {"emoji": "🇧🇬", "unicode": "U+1F1E7 U+1F1EC"}, + "BA": {"emoji": "🇧🇦", "unicode": "U+1F1E7 U+1F1E6"}, + "BB": {"emoji": "🇧🇧", "unicode": "U+1F1E7 U+1F1E7"}, + "WF": {"emoji": "🇼🇫", "unicode": "U+1F1FC U+1F1EB"}, + "BL": {"emoji": "🇧🇱", "unicode": "U+1F1E7 U+1F1F1"}, + "BM": {"emoji": "🇧🇲", "unicode": "U+1F1E7 U+1F1F2"}, + "BN": {"emoji": "🇧🇳", "unicode": "U+1F1E7 U+1F1F3"}, + "BO": {"emoji": "🇧🇴", "unicode": "U+1F1E7 U+1F1F4"}, + "BH": {"emoji": "🇧🇭", "unicode": "U+1F1E7 U+1F1ED"}, + "BI": {"emoji": "🇧🇮", "unicode": "U+1F1E7 U+1F1EE"}, + "BJ": {"emoji": "🇧🇯", "unicode": "U+1F1E7 U+1F1EF"}, + "BT": {"emoji": "🇧🇹", "unicode": "U+1F1E7 U+1F1F9"}, + "JM": {"emoji": "🇯🇲", "unicode": "U+1F1EF U+1F1F2"}, + "BV": {"emoji": "🇧🇻", "unicode": "U+1F1E7 U+1F1FB"}, + "BW": {"emoji": "🇧🇼", "unicode": "U+1F1E7 U+1F1FC"}, + "WS": {"emoji": "🇼🇸", "unicode": "U+1F1FC U+1F1F8"}, + "BQ": {"emoji": "🇧🇶", "unicode": "U+1F1E7 U+1F1F6"}, + "BR": {"emoji": "🇧🇷", "unicode": "U+1F1E7 U+1F1F7"}, + "BS": {"emoji": "🇧🇸", "unicode": "U+1F1E7 U+1F1F8"}, + "JE": {"emoji": "🇯🇪", "unicode": "U+1F1EF U+1F1EA"}, + "BY": {"emoji": "🇧🇾", "unicode": "U+1F1E7 U+1F1FE"}, + "BZ": {"emoji": "🇧🇿", "unicode": "U+1F1E7 U+1F1FF"}, + "RU": {"emoji": "🇷🇺", "unicode": "U+1F1F7 U+1F1FA"}, + "RW": {"emoji": "🇷🇼", "unicode": "U+1F1F7 U+1F1FC"}, + "RS": {"emoji": "🇷🇸", "unicode": "U+1F1F7 U+1F1F8"}, + "TL": {"emoji": "🇹🇱", "unicode": "U+1F1F9 U+1F1F1"}, + "RE": {"emoji": "🇷🇪", "unicode": "U+1F1F7 U+1F1EA"}, + "TM": {"emoji": "🇹🇲", "unicode": "U+1F1F9 U+1F1F2"}, + "TJ": {"emoji": "🇹🇯", "unicode": "U+1F1F9 U+1F1EF"}, + "RO": {"emoji": "🇷🇴", "unicode": "U+1F1F7 U+1F1F4"}, + "TK": {"emoji": "🇹🇰", "unicode": "U+1F1F9 U+1F1F0"}, + "GW": {"emoji": "🇬🇼", "unicode": "U+1F1EC U+1F1FC"}, + "GU": {"emoji": "🇬🇺", "unicode": "U+1F1EC U+1F1FA"}, + "GT": {"emoji": "🇬🇹", "unicode": "U+1F1EC U+1F1F9"}, + "GS": {"emoji": "🇬🇸", "unicode": "U+1F1EC U+1F1F8"}, + "GR": {"emoji": "🇬🇷", "unicode": "U+1F1EC U+1F1F7"}, + "GQ": {"emoji": "🇬🇶", "unicode": "U+1F1EC U+1F1F6"}, + "GP": {"emoji": "🇬🇵", "unicode": "U+1F1EC U+1F1F5"}, + "JP": {"emoji": "🇯🇵", "unicode": "U+1F1EF U+1F1F5"}, + "GY": {"emoji": "🇬🇾", "unicode": "U+1F1EC U+1F1FE"}, + "GG": {"emoji": "🇬🇬", "unicode": "U+1F1EC U+1F1EC"}, + "GF": {"emoji": "🇬🇫", "unicode": "U+1F1EC U+1F1EB"}, + "GE": {"emoji": "🇬🇪", "unicode": "U+1F1EC U+1F1EA"}, + "GD": {"emoji": "🇬🇩", "unicode": "U+1F1EC U+1F1E9"}, + "GB": {"emoji": "🇬🇧", "unicode": "U+1F1EC U+1F1E7"}, + "GA": {"emoji": "🇬🇦", "unicode": "U+1F1EC U+1F1E6"}, + "SV": {"emoji": "🇸🇻", "unicode": "U+1F1F8 U+1F1FB"}, + "GN": {"emoji": "🇬🇳", "unicode": "U+1F1EC U+1F1F3"}, + "GM": {"emoji": "🇬🇲", "unicode": "U+1F1EC U+1F1F2"}, + "GL": {"emoji": "🇬🇱", "unicode": "U+1F1EC U+1F1F1"}, + "GI": {"emoji": "🇬🇮", "unicode": "U+1F1EC U+1F1EE"}, + "GH": {"emoji": "🇬🇭", "unicode": "U+1F1EC U+1F1ED"}, + "OM": {"emoji": "🇴🇲", "unicode": "U+1F1F4 U+1F1F2"}, + "TN": {"emoji": "🇹🇳", "unicode": "U+1F1F9 U+1F1F3"}, + "JO": {"emoji": "🇯🇴", "unicode": "U+1F1EF U+1F1F4"}, + "HR": {"emoji": "🇭🇷", "unicode": "U+1F1ED U+1F1F7"}, + "HT": {"emoji": "🇭🇹", "unicode": "U+1F1ED U+1F1F9"}, + "HU": {"emoji": "🇭🇺", "unicode": "U+1F1ED U+1F1FA"}, + "HK": {"emoji": "🇭🇰", "unicode": "U+1F1ED U+1F1F0"}, + "HN": {"emoji": "🇭🇳", "unicode": "U+1F1ED U+1F1F3"}, + "HM": {"emoji": "🇭🇲", "unicode": "U+1F1ED U+1F1F2"}, + "VE": {"emoji": "🇻🇪", "unicode": "U+1F1FB U+1F1EA"}, + "PR": {"emoji": "🇵🇷", "unicode": "U+1F1F5 U+1F1F7"}, + "PS": {"emoji": "🇵🇸", "unicode": "U+1F1F5 U+1F1F8"}, + "PW": {"emoji": "🇵🇼", "unicode": "U+1F1F5 U+1F1FC"}, + "PT": {"emoji": "🇵🇹", "unicode": "U+1F1F5 U+1F1F9"}, + "SJ": {"emoji": "🇸🇯", "unicode": "U+1F1F8 U+1F1EF"}, + "PY": {"emoji": "🇵🇾", "unicode": "U+1F1F5 U+1F1FE"}, + "IQ": {"emoji": "🇮🇶", "unicode": "U+1F1EE U+1F1F6"}, + "PA": {"emoji": "🇵🇦", "unicode": "U+1F1F5 U+1F1E6"}, + "PF": {"emoji": "🇵🇫", "unicode": "U+1F1F5 U+1F1EB"}, + "PG": {"emoji": "🇵🇬", "unicode": "U+1F1F5 U+1F1EC"}, + "PE": {"emoji": "🇵🇪", "unicode": "U+1F1F5 U+1F1EA"}, + "PK": {"emoji": "🇵🇰", "unicode": "U+1F1F5 U+1F1F0"}, + "PH": {"emoji": "🇵🇭", "unicode": "U+1F1F5 U+1F1ED"}, + "PN": {"emoji": "🇵🇳", "unicode": "U+1F1F5 U+1F1F3"}, + "PL": {"emoji": "🇵🇱", "unicode": "U+1F1F5 U+1F1F1"}, + "PM": {"emoji": "🇵🇲", "unicode": "U+1F1F5 U+1F1F2"}, + "ZM": {"emoji": "🇿🇲", "unicode": "U+1F1FF U+1F1F2"}, + "EH": {"emoji": "🇪🇭", "unicode": "U+1F1EA U+1F1ED"}, + "EE": {"emoji": "🇪🇪", "unicode": "U+1F1EA U+1F1EA"}, + "EG": {"emoji": "🇪🇬", "unicode": "U+1F1EA U+1F1EC"}, + "ZA": {"emoji": "🇿🇦", "unicode": "U+1F1FF U+1F1E6"}, + "EC": {"emoji": "🇪🇨", "unicode": "U+1F1EA U+1F1E8"}, + "IT": {"emoji": "🇮🇹", "unicode": "U+1F1EE U+1F1F9"}, + "VN": {"emoji": "🇻🇳", "unicode": "U+1F1FB U+1F1F3"}, + "SB": {"emoji": "🇸🇧", "unicode": "U+1F1F8 U+1F1E7"}, + "ET": {"emoji": "🇪🇹", "unicode": "U+1F1EA U+1F1F9"}, + "SO": {"emoji": "🇸🇴", "unicode": "U+1F1F8 U+1F1F4"}, + "ZW": {"emoji": "🇿🇼", "unicode": "U+1F1FF U+1F1FC"}, + "SA": {"emoji": "🇸🇦", "unicode": "U+1F1F8 U+1F1E6"}, + "ES": {"emoji": "🇪🇸", "unicode": "U+1F1EA U+1F1F8"}, + "ER": {"emoji": "🇪🇷", "unicode": "U+1F1EA U+1F1F7"}, + "ME": {"emoji": "🇲🇪", "unicode": "U+1F1F2 U+1F1EA"}, + "MD": {"emoji": "🇲🇩", "unicode": "U+1F1F2 U+1F1E9"}, + "MG": {"emoji": "🇲🇬", "unicode": "U+1F1F2 U+1F1EC"}, + "MF": {"emoji": "🇲🇫", "unicode": "U+1F1F2 U+1F1EB"}, + "MA": {"emoji": "🇲🇦", "unicode": "U+1F1F2 U+1F1E6"}, + "MC": {"emoji": "🇲🇨", "unicode": "U+1F1F2 U+1F1E8"}, + "UZ": {"emoji": "🇺🇿", "unicode": "U+1F1FA U+1F1FF"}, + "MM": {"emoji": "🇲🇲", "unicode": "U+1F1F2 U+1F1F2"}, + "ML": {"emoji": "🇲🇱", "unicode": "U+1F1F2 U+1F1F1"}, + "MO": {"emoji": "🇲🇴", "unicode": "U+1F1F2 U+1F1F4"}, + "MN": {"emoji": "🇲🇳", "unicode": "U+1F1F2 U+1F1F3"}, + "MH": {"emoji": "🇲🇭", "unicode": "U+1F1F2 U+1F1ED"}, + "MK": {"emoji": "🇲🇰", "unicode": "U+1F1F2 U+1F1F0"}, + "MU": {"emoji": "🇲🇺", "unicode": "U+1F1F2 U+1F1FA"}, + "MT": {"emoji": "🇲🇹", "unicode": "U+1F1F2 U+1F1F9"}, + "MW": {"emoji": "🇲🇼", "unicode": "U+1F1F2 U+1F1FC"}, + "MV": {"emoji": "🇲🇻", "unicode": "U+1F1F2 U+1F1FB"}, + "MQ": {"emoji": "🇲🇶", "unicode": "U+1F1F2 U+1F1F6"}, + "MP": {"emoji": "🇲🇵", "unicode": "U+1F1F2 U+1F1F5"}, + "MS": {"emoji": "🇲🇸", "unicode": "U+1F1F2 U+1F1F8"}, + "MR": {"emoji": "🇲🇷", "unicode": "U+1F1F2 U+1F1F7"}, + "IM": {"emoji": "🇮🇲", "unicode": "U+1F1EE U+1F1F2"}, + "UG": {"emoji": "🇺🇬", "unicode": "U+1F1FA U+1F1EC"}, + "TZ": {"emoji": "🇹🇿", "unicode": "U+1F1F9 U+1F1FF"}, + "MY": {"emoji": "🇲🇾", "unicode": "U+1F1F2 U+1F1FE"}, + "MX": {"emoji": "🇲🇽", "unicode": "U+1F1F2 U+1F1FD"}, + "IL": {"emoji": "🇮🇱", "unicode": "U+1F1EE U+1F1F1"}, + "FR": {"emoji": "🇫🇷", "unicode": "U+1F1EB U+1F1F7"}, + "IO": {"emoji": "🇮🇴", "unicode": "U+1F1EE U+1F1F4"}, + "SH": {"emoji": "🇸🇭", "unicode": "U+1F1F8 U+1F1ED"}, + "FI": {"emoji": "🇫🇮", "unicode": "U+1F1EB U+1F1EE"}, + "FJ": {"emoji": "🇫🇯", "unicode": "U+1F1EB U+1F1EF"}, + "FK": {"emoji": "🇫🇰", "unicode": "U+1F1EB U+1F1F0"}, + "FM": {"emoji": "🇫🇲", "unicode": "U+1F1EB U+1F1F2"}, + "FO": {"emoji": "🇫🇴", "unicode": "U+1F1EB U+1F1F4"}, + "NI": {"emoji": "🇳🇮", "unicode": "U+1F1F3 U+1F1EE"}, + "NL": {"emoji": "🇳🇱", "unicode": "U+1F1F3 U+1F1F1"}, + "NO": {"emoji": "🇳🇴", "unicode": "U+1F1F3 U+1F1F4"}, + "NA": {"emoji": "🇳🇦", "unicode": "U+1F1F3 U+1F1E6"}, + "VU": {"emoji": "🇻🇺", "unicode": "U+1F1FB U+1F1FA"}, + "NC": {"emoji": "🇳🇨", "unicode": "U+1F1F3 U+1F1E8"}, + "NE": {"emoji": "🇳🇪", "unicode": "U+1F1F3 U+1F1EA"}, + "NF": {"emoji": "🇳🇫", "unicode": "U+1F1F3 U+1F1EB"}, + "NG": {"emoji": "🇳🇬", "unicode": "U+1F1F3 U+1F1EC"}, + "NZ": {"emoji": "🇳🇿", "unicode": "U+1F1F3 U+1F1FF"}, + "NP": {"emoji": "🇳🇵", "unicode": "U+1F1F3 U+1F1F5"}, + "NR": {"emoji": "🇳🇷", "unicode": "U+1F1F3 U+1F1F7"}, + "NU": {"emoji": "🇳🇺", "unicode": "U+1F1F3 U+1F1FA"}, + "CK": {"emoji": "🇨🇰", "unicode": "U+1F1E8 U+1F1F0"}, + "XK": {"emoji": "🇽🇰", "unicode": "U+1F1FD U+1F1F0"}, + "CI": {"emoji": "🇨🇮", "unicode": "U+1F1E8 U+1F1EE"}, + "CH": {"emoji": "🇨🇭", "unicode": "U+1F1E8 U+1F1ED"}, + "CO": {"emoji": "🇨🇴", "unicode": "U+1F1E8 U+1F1F4"}, + "CN": {"emoji": "🇨🇳", "unicode": "U+1F1E8 U+1F1F3"}, + "CM": {"emoji": "🇨🇲", "unicode": "U+1F1E8 U+1F1F2"}, + "CL": {"emoji": "🇨🇱", "unicode": "U+1F1E8 U+1F1F1"}, + "CC": {"emoji": "🇨🇨", "unicode": "U+1F1E8 U+1F1E8"}, + "CA": {"emoji": "🇨🇦", "unicode": "U+1F1E8 U+1F1E6"}, + "CG": {"emoji": "🇨🇬", "unicode": "U+1F1E8 U+1F1EC"}, + "CF": {"emoji": "🇨🇫", "unicode": "U+1F1E8 U+1F1EB"}, + "CD": {"emoji": "🇨🇩", "unicode": "U+1F1E8 U+1F1E9"}, + "CZ": {"emoji": "🇨🇿", "unicode": "U+1F1E8 U+1F1FF"}, + "CY": {"emoji": "🇨🇾", "unicode": "U+1F1E8 U+1F1FE"}, + "CX": {"emoji": "🇨🇽", "unicode": "U+1F1E8 U+1F1FD"}, + "CR": {"emoji": "🇨🇷", "unicode": "U+1F1E8 U+1F1F7"}, + "CW": {"emoji": "🇨🇼", "unicode": "U+1F1E8 U+1F1FC"}, + "CV": {"emoji": "🇨🇻", "unicode": "U+1F1E8 U+1F1FB"}, + "CU": {"emoji": "🇨🇺", "unicode": "U+1F1E8 U+1F1FA"}, + "SZ": {"emoji": "🇸🇿", "unicode": "U+1F1F8 U+1F1FF"}, + "SY": {"emoji": "🇸🇾", "unicode": "U+1F1F8 U+1F1FE"}, + "SX": {"emoji": "🇸🇽", "unicode": "U+1F1F8 U+1F1FD"}, + "KG": {"emoji": "🇰🇬", "unicode": "U+1F1F0 U+1F1EC"}, + "KE": {"emoji": "🇰🇪", "unicode": "U+1F1F0 U+1F1EA"}, + "SS": {"emoji": "🇸🇸", "unicode": "U+1F1F8 U+1F1F8"}, + "SR": {"emoji": "🇸🇷", "unicode": "U+1F1F8 U+1F1F7"}, + "KI": {"emoji": "🇰🇮", "unicode": "U+1F1F0 U+1F1EE"}, + "KH": {"emoji": "🇰🇭", "unicode": "U+1F1F0 U+1F1ED"}, + "KN": {"emoji": "🇰🇳", "unicode": "U+1F1F0 U+1F1F3"}, + "KM": {"emoji": "🇰🇲", "unicode": "U+1F1F0 U+1F1F2"}, + "ST": {"emoji": "🇸🇹", "unicode": "U+1F1F8 U+1F1F9"}, + "SK": {"emoji": "🇸🇰", "unicode": "U+1F1F8 U+1F1F0"}, + "KR": {"emoji": "🇰🇷", "unicode": "U+1F1F0 U+1F1F7"}, + "SI": {"emoji": "🇸🇮", "unicode": "U+1F1F8 U+1F1EE"}, + "KP": {"emoji": "🇰🇵", "unicode": "U+1F1F0 U+1F1F5"}, + "KW": {"emoji": "🇰🇼", "unicode": "U+1F1F0 U+1F1FC"}, + "SN": {"emoji": "🇸🇳", "unicode": "U+1F1F8 U+1F1F3"}, + "SM": {"emoji": "🇸🇲", "unicode": "U+1F1F8 U+1F1F2"}, + "SL": {"emoji": "🇸🇱", "unicode": "U+1F1F8 U+1F1F1"}, + "SC": {"emoji": "🇸🇨", "unicode": "U+1F1F8 U+1F1E8"}, + "KZ": {"emoji": "🇰🇿", "unicode": "U+1F1F0 U+1F1FF"}, + "KY": {"emoji": "🇰🇾", "unicode": "U+1F1F0 U+1F1FE"}, + "SG": {"emoji": "🇸🇬", "unicode": "U+1F1F8 U+1F1EC"}, + "SE": {"emoji": "🇸🇪", "unicode": "U+1F1F8 U+1F1EA"}, + "SD": {"emoji": "🇸🇩", "unicode": "U+1F1F8 U+1F1E9"}, + "DO": {"emoji": "🇩🇴", "unicode": "U+1F1E9 U+1F1F4"}, + "DM": {"emoji": "🇩🇲", "unicode": "U+1F1E9 U+1F1F2"}, + "DJ": {"emoji": "🇩🇯", "unicode": "U+1F1E9 U+1F1EF"}, + "DK": {"emoji": "🇩🇰", "unicode": "U+1F1E9 U+1F1F0"}, + "VG": {"emoji": "🇻🇬", "unicode": "U+1F1FB U+1F1EC"}, + "DE": {"emoji": "🇩🇪", "unicode": "U+1F1E9 U+1F1EA"}, + "YE": {"emoji": "🇾🇪", "unicode": "U+1F1FE U+1F1EA"}, + "DZ": {"emoji": "🇩🇿", "unicode": "U+1F1E9 U+1F1FF"}, + "US": {"emoji": "🇺🇸", "unicode": "U+1F1FA U+1F1F8"}, + "UY": {"emoji": "🇺🇾", "unicode": "U+1F1FA U+1F1FE"}, + "YT": {"emoji": "🇾🇹", "unicode": "U+1F1FE U+1F1F9"}, + "UM": {"emoji": "🇺🇲", "unicode": "U+1F1FA U+1F1F2"}, + "LB": {"emoji": "🇱🇧", "unicode": "U+1F1F1 U+1F1E7"}, + "LC": {"emoji": "🇱🇨", "unicode": "U+1F1F1 U+1F1E8"}, + "LA": {"emoji": "🇱🇦", "unicode": "U+1F1F1 U+1F1E6"}, + "TV": {"emoji": "🇹🇻", "unicode": "U+1F1F9 U+1F1FB"}, + "TW": {"emoji": "🇹🇼", "unicode": "U+1F1F9 U+1F1FC"}, + "TT": {"emoji": "🇹🇹", "unicode": "U+1F1F9 U+1F1F9"}, + "TR": {"emoji": "🇹🇷", "unicode": "U+1F1F9 U+1F1F7"}, + "LK": {"emoji": "🇱🇰", "unicode": "U+1F1F1 U+1F1F0"}, + "LI": {"emoji": "🇱🇮", "unicode": "U+1F1F1 U+1F1EE"}, + "LV": {"emoji": "🇱🇻", "unicode": "U+1F1F1 U+1F1FB"}, + "TO": {"emoji": "🇹🇴", "unicode": "U+1F1F9 U+1F1F4"}, + "LT": {"emoji": "🇱🇹", "unicode": "U+1F1F1 U+1F1F9"}, + "LU": {"emoji": "🇱🇺", "unicode": "U+1F1F1 U+1F1FA"}, + "LR": {"emoji": "🇱🇷", "unicode": "U+1F1F1 U+1F1F7"}, + "LS": {"emoji": "🇱🇸", "unicode": "U+1F1F1 U+1F1F8"}, + "TH": {"emoji": "🇹🇭", "unicode": "U+1F1F9 U+1F1ED"}, + "TF": {"emoji": "🇹🇫", "unicode": "U+1F1F9 U+1F1EB"}, + "TG": {"emoji": "🇹🇬", "unicode": "U+1F1F9 U+1F1EC"}, + "TD": {"emoji": "🇹🇩", "unicode": "U+1F1F9 U+1F1E9"}, + "TC": {"emoji": "🇹🇨", "unicode": "U+1F1F9 U+1F1E8"}, + "LY": {"emoji": "🇱🇾", "unicode": "U+1F1F1 U+1F1FE"}, + "VA": {"emoji": "🇻🇦", "unicode": "U+1F1FB U+1F1E6"}, + "VC": {"emoji": "🇻🇨", "unicode": "U+1F1FB U+1F1E8"}, + "AE": {"emoji": "🇦🇪", "unicode": "U+1F1E6 U+1F1EA"}, + "AD": {"emoji": "🇦🇩", "unicode": "U+1F1E6 U+1F1E9"}, + "AG": {"emoji": "🇦🇬", "unicode": "U+1F1E6 U+1F1EC"}, + "AF": {"emoji": "🇦🇫", "unicode": "U+1F1E6 U+1F1EB"}, + "AI": {"emoji": "🇦🇮", "unicode": "U+1F1E6 U+1F1EE"}, + "VI": {"emoji": "🇻🇮", "unicode": "U+1F1FB U+1F1EE"}, + "IS": {"emoji": "🇮🇸", "unicode": "U+1F1EE U+1F1F8"}, + "IR": {"emoji": "🇮🇷", "unicode": "U+1F1EE U+1F1F7"}, + "AM": {"emoji": "🇦🇲", "unicode": "U+1F1E6 U+1F1F2"}, + "AL": {"emoji": "🇦🇱", "unicode": "U+1F1E6 U+1F1F1"}, + "AO": {"emoji": "🇦🇴", "unicode": "U+1F1E6 U+1F1F4"}, + "AQ": {"emoji": "🇦🇶", "unicode": "U+1F1E6 U+1F1F6"}, + "AS": {"emoji": "🇦🇸", "unicode": "U+1F1E6 U+1F1F8"}, + "AR": {"emoji": "🇦🇷", "unicode": "U+1F1E6 U+1F1F7"}, + "AU": {"emoji": "🇦🇺", "unicode": "U+1F1E6 U+1F1FA"}, + "AT": {"emoji": "🇦🇹", "unicode": "U+1F1E6 U+1F1F9"}, + "AW": {"emoji": "🇦🇼", "unicode": "U+1F1E6 U+1F1FC"}, + "IN": {"emoji": "🇮🇳", "unicode": "U+1F1EE U+1F1F3"}, + "AX": {"emoji": "🇦🇽", "unicode": "U+1F1E6 U+1F1FD"}, + "AZ": {"emoji": "🇦🇿", "unicode": "U+1F1E6 U+1F1FF"}, + "IE": {"emoji": "🇮🇪", "unicode": "U+1F1EE U+1F1EA"}, + "ID": {"emoji": "🇮🇩", "unicode": "U+1F1EE U+1F1E9"}, + "UA": {"emoji": "🇺🇦", "unicode": "U+1F1FA U+1F1E6"}, + "QA": {"emoji": "🇶🇦", "unicode": "U+1F1F6 U+1F1E6"}, + "MZ": {"emoji": "🇲🇿", "unicode": "U+1F1F2 U+1F1FF"} +}