diff --git a/.github/workflows/aws-cicd-dev.yml b/.github/workflows/aws-cicd-dev.yml index 37627514..af5968da 100644 --- a/.github/workflows/aws-cicd-dev.yml +++ b/.github/workflows/aws-cicd-dev.yml @@ -4,7 +4,7 @@ on: push: branches: - develop - - feat/LA-20_3 + - feat/LA-27-2 env: REGISTRY: "docker.io" diff --git a/.github/workflows/aws-cicd-prod.yml b/.github/workflows/aws-cicd-prod.yml index 33aa5669..110d2a80 100644 --- a/.github/workflows/aws-cicd-prod.yml +++ b/.github/workflows/aws-cicd-prod.yml @@ -4,6 +4,7 @@ on: push: branches: - main +# - feat/LA-27-2 env: REGISTRY: "docker.io" diff --git a/.gitignore b/.gitignore index 4e76a4c9..0fddef61 100644 --- a/.gitignore +++ b/.gitignore @@ -58,4 +58,8 @@ credentials.json layer-api/src/main/resources/tokens/StoredCredential layer-batch/src/main/resources/application-secret.properties -layer-admin/src/main/resources/application-secret.properties \ No newline at end of file +layer-admin/src/main/resources/application-secret.properties +layer-admin/src/main/resources/application.yml +layer-admin/src/main/resources/data.sql + + diff --git a/build.gradle b/build.gradle index ac8dbd9e..f43b9470 100644 --- a/build.gradle +++ b/build.gradle @@ -68,6 +68,7 @@ project(":layer-api") { implementation 'org.springframework.boot:spring-boot-starter-actuator' implementation 'org.springframework.boot:spring-boot-starter-validation' + implementation 'org.springframework.boot:spring-boot-starter-web' //== jwt ==// implementation 'io.jsonwebtoken:jjwt-api:0.12.5' @@ -201,6 +202,7 @@ project(":layer-admin") { dependencies { implementation project(path: ':layer-domain') + implementation project(path: ':layer-common') implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' @@ -211,6 +213,9 @@ project(":layer-admin") { // Security implementation 'org.springframework.boot:spring-boot-starter-security' + + // redis + implementation 'org.springframework.boot:spring-boot-starter-data-redis' } } \ No newline at end of file diff --git a/layer-admin/src/main/java/org/layer/common/exception/AdminException.java b/layer-admin/src/main/java/org/layer/common/exception/AdminException.java new file mode 100644 index 00000000..4fbb87fa --- /dev/null +++ b/layer-admin/src/main/java/org/layer/common/exception/AdminException.java @@ -0,0 +1,7 @@ +package org.layer.common.exception; + +public class AdminException extends BaseCustomException { + public AdminException(ExceptionType exceptionType) { + super(exceptionType); + } +} diff --git a/layer-admin/src/main/java/org/layer/config/RedisConfig.java b/layer-admin/src/main/java/org/layer/config/RedisConfig.java new file mode 100644 index 00000000..508ff4d7 --- /dev/null +++ b/layer-admin/src/main/java/org/layer/config/RedisConfig.java @@ -0,0 +1,68 @@ +package org.layer.config; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator; +import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.RedisStandaloneConfiguration; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.repository.configuration.EnableRedisRepositories; +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; + + +@Configuration +@EnableRedisRepositories +public class RedisConfig { + @Value("${spring.data.redis.host}") + private String host; + + @Value("${spring.data.redis.port}") + private int port; + + @Value("${spring.data.redis.password}") + private String password; + + // prod에서 최근 서비스 이용 시점 기록 - 1번 데이터베이스 + @Bean + public RedisConnectionFactory redisConnectionFactory() { + RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(); + redisStandaloneConfiguration.setHostName(host); + redisStandaloneConfiguration.setPort(port); + redisStandaloneConfiguration.setDatabase(1); // 1번 데이터베이스 + + return new LettuceConnectionFactory(redisStandaloneConfiguration); + + } + + @Bean + public RedisTemplate redisTemplate() { + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(redisConnectionFactory()); + + PolymorphicTypeValidator typeValidator = BasicPolymorphicTypeValidator + .builder() + .allowIfSubType(Object.class) + .build(); + + ObjectMapper objectMapper = new ObjectMapper() + .findAndRegisterModules() + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false) + .activateDefaultTyping(typeValidator, ObjectMapper.DefaultTyping.NON_FINAL) + .registerModule(new JavaTimeModule()); + + template.setKeySerializer(new StringRedisSerializer()); + template.setValueSerializer(new GenericJackson2JsonRedisSerializer(objectMapper)); + + return template; + } + +} \ No newline at end of file diff --git a/layer-admin/src/main/java/org/layer/member/controller/dto/GetMemberActivityResponse.java b/layer-admin/src/main/java/org/layer/member/controller/dto/GetMemberActivityResponse.java index 3a957e66..be97eb31 100644 --- a/layer-admin/src/main/java/org/layer/member/controller/dto/GetMemberActivityResponse.java +++ b/layer-admin/src/main/java/org/layer/member/controller/dto/GetMemberActivityResponse.java @@ -1,17 +1,17 @@ package org.layer.member.controller.dto; -import java.time.LocalDateTime; - import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; +import java.time.LocalDateTime; + @Schema(name = "GetMemberActivityResponse", description = "회원 활동 Dto") public record GetMemberActivityResponse( @NotNull @Schema(description = "회원 이름", example = "홍길동") String name, @NotNull - @Schema(description = "최근 활동 날짜", example = "2024-11-30T16:21:47.031Z") + @Schema(description = "최근 활동 날짜, 최근 6개월 동안 접속 없을 시 null", example = "2024-11-30T16:21:47.031Z") LocalDateTime recentActivityDate, @NotNull @Schema(description = "소속된 스페이스 수", example = "7") diff --git a/layer-admin/src/main/java/org/layer/member/service/AdminMemberService.java b/layer-admin/src/main/java/org/layer/member/service/AdminMemberService.java index d761a368..cce764d6 100644 --- a/layer-admin/src/main/java/org/layer/member/service/AdminMemberService.java +++ b/layer-admin/src/main/java/org/layer/member/service/AdminMemberService.java @@ -1,7 +1,8 @@ package org.layer.member.service; -import java.util.List; +import lombok.RequiredArgsConstructor; +import org.layer.common.dto.RecentActivityDto; import org.layer.domain.answer.repository.AdminAnswerRepository; import org.layer.domain.member.entity.Member; import org.layer.domain.member.repository.AdminMemberRepository; @@ -11,10 +12,11 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; +import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import lombok.RequiredArgsConstructor; +import java.util.List; @Service @RequiredArgsConstructor @@ -23,6 +25,7 @@ public class AdminMemberService { private final AdminMemberRepository adminMemberRepository; private final AdminMemberSpaceRelationRepository adminMemberSpaceRelationRepository; private final AdminAnswerRepository adminAnswerRepository; + private final RedisTemplate redisTemplate; @Value("${admin.password}") private String password; @@ -43,8 +46,12 @@ public GetMembersActivitiesResponse getMemberActivities(String password, int pag Long spaceCount = adminMemberSpaceRelationRepository.countAllByMemberId(member.getId()); Long retrospectAnswerCount = adminAnswerRepository.countAllByMemberId(member.getId()); - return new GetMemberActivityResponse(member.getName(), null, spaceCount, retrospectAnswerCount, - member.getCreatedAt(), member.getSocialType().name()); + RecentActivityDto recentActivityDto = (RecentActivityDto)redisTemplate.opsForValue() + .get(Long.toString(member.getId())); + + return new GetMemberActivityResponse(member.getName(), + recentActivityDto == null ? null : recentActivityDto.getRecentActivityDate(), + spaceCount, retrospectAnswerCount, member.getCreatedAt(), member.getSocialType().name()); }).toList(); return new GetMembersActivitiesResponse(responses); diff --git a/layer-admin/src/main/resources/application-dev.yml b/layer-admin/src/main/resources/application-dev.yml index b68b603a..e99506f2 100644 --- a/layer-admin/src/main/resources/application-dev.yml +++ b/layer-admin/src/main/resources/application-dev.yml @@ -21,6 +21,11 @@ spring: show_sql: true open-in-view: false database: mysql + data: + redis: + host: ${DEV_REDIS_HOST} + port: ${DEV_REDIS_PORT} + password: ${DEV_REDIS_PASSWORD} admin: password: ${ADMIN_PASSWORD} \ No newline at end of file diff --git a/layer-admin/src/main/resources/application-local.yml b/layer-admin/src/main/resources/application-local.yml new file mode 100644 index 00000000..11f26409 --- /dev/null +++ b/layer-admin/src/main/resources/application-local.yml @@ -0,0 +1,34 @@ +server: + port: 3000 + +spring: + config: + import: application-secret.properties + datasource: + driver-class-name: org.h2.Driver + url: jdbc:h2:mem:layer-local-db;DATABASE_TO_UPPER=FALSE;mode=mysql # H2 접속 정보 (전부 소문자로 지정) + username: sa + password: + h2: + console: + enabled: true + path: /h2-console + jpa: + database-platform: org.hibernate.dialect.H2Dialect + hibernate: + ddl-auto: create + properties: + hibernate: + format_sql: true + show_sql: true + open-in-view: false + defer-datasource-initialization: true + + data: + redis: + host: localhost + port: 6379 + password: + +admin: + password: ${ADMIN_PASSWORD} \ No newline at end of file diff --git a/layer-admin/src/main/resources/application-prod.yml b/layer-admin/src/main/resources/application-prod.yml index 54120ad1..3c0a4f07 100644 --- a/layer-admin/src/main/resources/application-prod.yml +++ b/layer-admin/src/main/resources/application-prod.yml @@ -22,5 +22,11 @@ spring: open-in-view: false database: mysql + data: + redis: + host: ${DEV_REDIS_HOST} + port: ${DEV_REDIS_PORT} + password: ${DEV_REDIS_PASSWORD} + admin: password: ${ADMIN_PASSWORD} \ No newline at end of file diff --git a/layer-api/infra/development/Dockerfile-redis b/layer-api/infra/development/Dockerfile-redis new file mode 100644 index 00000000..f1ba86b8 --- /dev/null +++ b/layer-api/infra/development/Dockerfile-redis @@ -0,0 +1,8 @@ +# Base image +FROM redis:latest + +# Copy custom Redis configuration +COPY redis.conf /usr/local/etc/redis/redis.conf + +# Command to run Redis with the custom configuration +CMD ["redis-server", "/usr/local/etc/redis/redis.conf"] \ No newline at end of file diff --git a/layer-api/infra/development/docker-compose.yaml b/layer-api/infra/development/docker-compose.yaml index b0ea5bbe..918fc5e4 100644 --- a/layer-api/infra/development/docker-compose.yaml +++ b/layer-api/infra/development/docker-compose.yaml @@ -1,4 +1,16 @@ services: + redis: + build: + context: . + dockerfile: Dockerfile-redis + ports: + - "6379:6379" + volumes: + - redis-data:/data # Persistent data storage + restart: always + networks: + - app-network + java-app: image: docker.io/clean01/layer-server_layer-api:latest container_name: layer-api @@ -60,4 +72,7 @@ services: - app-network networks: - app-network: \ No newline at end of file + app-network: + +volumes: + redis-data: \ No newline at end of file diff --git a/layer-api/infra/development/nginx.conf b/layer-api/infra/development/nginx.conf index fabe06a6..530c5113 100644 --- a/layer-api/infra/development/nginx.conf +++ b/layer-api/infra/development/nginx.conf @@ -12,7 +12,7 @@ http { # api.layerapp.io에 대한 서버 블록 server { listen 80; - server_name api.layerapp.io; + server_name stgapi.layerapp.io; location / { proxy_pass http://layer-api; diff --git a/layer-api/infra/development/redis.conf b/layer-api/infra/development/redis.conf new file mode 100644 index 00000000..21c696c5 --- /dev/null +++ b/layer-api/infra/development/redis.conf @@ -0,0 +1,20 @@ +# Save the DB snapshot every 180 seconds if at least 1 key changes +save 180 1 + +# Specify the filename for the RDB file +dbfilename dump.rdb + +# Directory where the RDB snapshot will be saved +dir /data + +# Enable RDB snapshot logging (optional for debugging) +loglevel notice + +# Disable AOF (if you want only RDB persistence) +appendonly no + +# Compression for RDB files (enabled by default) +rdbcompression yes + +# Checksum verification for RDB files (enabled by default) +rdbchecksum yes diff --git a/layer-api/infra/production/Dockerfile-redis b/layer-api/infra/production/Dockerfile-redis new file mode 100644 index 00000000..f1ba86b8 --- /dev/null +++ b/layer-api/infra/production/Dockerfile-redis @@ -0,0 +1,8 @@ +# Base image +FROM redis:latest + +# Copy custom Redis configuration +COPY redis.conf /usr/local/etc/redis/redis.conf + +# Command to run Redis with the custom configuration +CMD ["redis-server", "/usr/local/etc/redis/redis.conf"] \ No newline at end of file diff --git a/layer-api/infra/production/docker-compose-blue.yaml b/layer-api/infra/production/docker-compose-blue.yaml index 1f7311d0..3f061985 100644 --- a/layer-api/infra/production/docker-compose-blue.yaml +++ b/layer-api/infra/production/docker-compose-blue.yaml @@ -1,4 +1,16 @@ services: + redis: + build: + context: . + dockerfile: Dockerfile-redis + ports: + - "6379:6379" + volumes: + - redis-data:/data # Persistent data storage + restart: always + networks: + - app-network + layer-api-blue: image: docker.io/clean01/layer-server_layer-api:latest container_name: layer-api-blue @@ -48,4 +60,7 @@ services: restart: always networks: - app-network: \ No newline at end of file + app-network: + +volumes: + redis-data: \ No newline at end of file diff --git a/layer-api/infra/production/docker-compose-green.yaml b/layer-api/infra/production/docker-compose-green.yaml index 9df97019..21e50a1c 100644 --- a/layer-api/infra/production/docker-compose-green.yaml +++ b/layer-api/infra/production/docker-compose-green.yaml @@ -1,4 +1,16 @@ services: + redis: + build: + context: . + dockerfile: Dockerfile-redis + ports: + - "6379:6379" + volumes: + - redis-data:/data # Persistent data storage + restart: always + networks: + - app-network + layer-api-green: image: docker.io/clean01/layer-server_layer-api:latest container_name: layer-api-green @@ -48,4 +60,7 @@ services: restart: always networks: - app-network: \ No newline at end of file + app-network: + +volumes: + redis-data: \ No newline at end of file diff --git a/layer-api/infra/production/redis.conf b/layer-api/infra/production/redis.conf new file mode 100644 index 00000000..21c696c5 --- /dev/null +++ b/layer-api/infra/production/redis.conf @@ -0,0 +1,20 @@ +# Save the DB snapshot every 180 seconds if at least 1 key changes +save 180 1 + +# Specify the filename for the RDB file +dbfilename dump.rdb + +# Directory where the RDB snapshot will be saved +dir /data + +# Enable RDB snapshot logging (optional for debugging) +loglevel notice + +# Disable AOF (if you want only RDB persistence) +appendonly no + +# Compression for RDB files (enabled by default) +rdbcompression yes + +# Checksum verification for RDB files (enabled by default) +rdbchecksum yes diff --git a/layer-api/src/main/java/org/layer/aop/RecentAccessHistoryAop.java b/layer-api/src/main/java/org/layer/aop/RecentAccessHistoryAop.java new file mode 100644 index 00000000..7ebe1a0b --- /dev/null +++ b/layer-api/src/main/java/org/layer/aop/RecentAccessHistoryAop.java @@ -0,0 +1,54 @@ +package org.layer.aop; + + +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.layer.common.dto.RecentActivityDto; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; + +import java.time.Duration; +import java.time.LocalDateTime; + +@RequiredArgsConstructor +@Aspect +@Component +@Log4j2 +public class RecentAccessHistoryAop { + @Qualifier("recentActivityDate") + private final RedisTemplate redisTemplate; + + // 모든 layer-api 모듈 내의 controller package에 존재하는 클래스 + @Around("execution(* org.layer.domain..controller..*(..))") + public Object recordRecentAccessHistory(ProceedingJoinPoint pjp) throws Throwable { + Long memberId = getCurrentMemberId(); + + if(memberId != null) { // 멤버 아이디가 있다면 현재 시간을 저장. + setRecentTime(Long.toString(memberId), LocalDateTime.now()); + } + Object result = pjp.proceed(); + return result; + } + + private Long getCurrentMemberId() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + + try { + return Long.parseLong(authentication.getName()); + } catch(Exception e) { + return null; + } + } + + + private void setRecentTime(String memberId, LocalDateTime recentTime) { + Duration ttl = Duration.ofDays(30 * 6); // 6개월 + redisTemplate.opsForValue().set(memberId, new RecentActivityDto(recentTime), ttl); + } +} \ No newline at end of file diff --git a/layer-api/src/main/java/org/layer/config/RedisConfig.java b/layer-api/src/main/java/org/layer/config/RedisConfig.java index e3372349..619baf92 100644 --- a/layer-api/src/main/java/org/layer/config/RedisConfig.java +++ b/layer-api/src/main/java/org/layer/config/RedisConfig.java @@ -1,6 +1,12 @@ package org.layer.config; -import org.layer.domain.member.entity.Member; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator; +import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -9,7 +15,7 @@ import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.repository.configuration.EnableRedisRepositories; -import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; @Configuration @@ -34,8 +40,8 @@ public RedisConnectionFactory redisConnectionFactory() { } @Bean - RedisTemplate redisTemplate() { - RedisTemplate redisTemplate = new RedisTemplate<>(); + RedisTemplate redisTemplate() { + RedisTemplate redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(redisConnectionFactory()); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(new StringRedisSerializer()); @@ -43,4 +49,41 @@ RedisTemplate redisTemplate() { return redisTemplate; } -} + + // 최근 서비스 이용 시점 기록 - 1번 데이터베이스 + @Bean + @Qualifier("recentActivityDateConnectionFactory") + public RedisConnectionFactory recentActivityDateRedisConnectionFactory() { + RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(); + redisStandaloneConfiguration.setHostName(host); + redisStandaloneConfiguration.setPort(port); + redisStandaloneConfiguration.setDatabase(1); // 1번 데이터베이스 + + return new LettuceConnectionFactory(redisStandaloneConfiguration); + } + + @Bean + @Qualifier("recentActivityDate") + public RedisTemplate recentActivityDateRedisTemplate(@Qualifier("recentActivityDateConnectionFactory") RedisConnectionFactory redisConnectionFactory) { + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(redisConnectionFactory); + + PolymorphicTypeValidator typeValidator = BasicPolymorphicTypeValidator + .builder() + .allowIfSubType(Object.class) + .build(); + + ObjectMapper objectMapper = new ObjectMapper() + .findAndRegisterModules() + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false) + .activateDefaultTyping(typeValidator, ObjectMapper.DefaultTyping.NON_FINAL) + .registerModule(new JavaTimeModule()); + + template.setKeySerializer(new StringRedisSerializer()); + template.setValueSerializer(new GenericJackson2JsonRedisSerializer(objectMapper)); + + return template; + } + +} \ No newline at end of file diff --git a/layer-api/src/main/java/org/layer/domain/jwt/service/JwtService.java b/layer-api/src/main/java/org/layer/domain/jwt/service/JwtService.java index 66a5cca0..ff6d07be 100644 --- a/layer-api/src/main/java/org/layer/domain/jwt/service/JwtService.java +++ b/layer-api/src/main/java/org/layer/domain/jwt/service/JwtService.java @@ -27,7 +27,7 @@ public class JwtService { private final JwtProvider jwtProvider; private final JwtValidator jwtValidator; - private final RedisTemplate redisTemplate; + private final RedisTemplate redisTemplate; public JwtToken issueToken(Long memberId, MemberRole memberRole) { String accessToken = jwtProvider.createToken(MemberAuthentication.create(memberId, memberRole), ACCESS_TOKEN_EXPIRATION_TIME); diff --git a/layer-common/src/main/java/org/layer/common/dto/RecentActivityDto.java b/layer-common/src/main/java/org/layer/common/dto/RecentActivityDto.java new file mode 100644 index 00000000..9491e4ae --- /dev/null +++ b/layer-common/src/main/java/org/layer/common/dto/RecentActivityDto.java @@ -0,0 +1,19 @@ +package org.layer.common.dto; + +import java.time.LocalDateTime; + +import com.fasterxml.jackson.annotation.JsonCreator; + +import lombok.EqualsAndHashCode; +import lombok.Getter; + +@EqualsAndHashCode +@Getter +public class RecentActivityDto { + private final LocalDateTime recentActivityDate; + + @JsonCreator + public RecentActivityDto(LocalDateTime recentActivityDate) { + this.recentActivityDate = recentActivityDate; + } +} diff --git a/layer-common/src/main/java/org/layer/common/exception/AdminExceptionType.java b/layer-common/src/main/java/org/layer/common/exception/AdminExceptionType.java new file mode 100644 index 00000000..2a04b377 --- /dev/null +++ b/layer-common/src/main/java/org/layer/common/exception/AdminExceptionType.java @@ -0,0 +1,23 @@ +package org.layer.common.exception; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@RequiredArgsConstructor +public enum AdminExceptionType implements ExceptionType{ + IllegalDateTime(HttpStatus.INTERNAL_SERVER_ERROR, "유저의 최근 접속 시간이 올바르지 않습니다."); + + + private final HttpStatus status; + private final String message; + + @Override + public HttpStatus httpStatus() { + return status; + } + + @Override + public String message() { + return message; + } +} \ No newline at end of file