diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/.DS_Store differ diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..2eea525 --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +.env \ No newline at end of file diff --git a/.gitignore b/.gitignore index e8baff1..9836877 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,16 @@ out/ hs_err_pid44980.jfr hs_err_pid44980.log + +build/ + +docker-compose.yaml + +#GCP, FIREBASE KEY +/src/main/resources/gcp +/src/main/resources/firebase + +data/ +gradle/ + +buildDockerWindow.sh diff --git a/Dockerfile b/Dockerfile index cc15b00..bf87811 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,12 @@ FROM openjdk:17 ARG JAR_FILE=build/libs/*.jar -COPY ${JAR_FILE} test-app.jar +COPY ${JAR_FILE} prod.jar + +COPY src/main/resources/gcp/ancient-pipe-447417-i4-755ce59fbf03.json /app/ancient-pipe-447417-i4-755ce59fbf03.json +ENV GOOGLE_APPLICATION_CREDENTIALS="app/ancient-pipe-447417-i4-755ce59fbf03.json" + +COPY src/main/resources/firebase/comncheck-firebase-adminsdk-fbsvc-1255c6f2ed.json /app/comncheck-firebase-adminsdk-fbsvc-1255c6f2ed.json + EXPOSE 8080 -CMD ["java", "-Dtest.customName=${CUSTOM_NAME}", "-jar", "test-app.jar"] \ No newline at end of file +CMD ["java", "-Dtest.customName=${CUSTOM_NAME}", "-jar", "prod.jar"] + diff --git a/README.md b/README.md index a494f0a..69aec04 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,63 @@ -# backend +

Image -## branch 특징 -### main : 배포 -### develop : 개발 + + +# 1. OverView(프로젝트 개요) +- 프로젝트 이름: ComNCheck +- 프로젝트 설명: 한국외국어대학교 컴퓨터공학부 알리미 +- 프로젝트 시작 계기 : 학창시절 과회장을 하면서 학부 행사 같은 경우 카톡방에 쌓이고, 학교 공지는 홈페이지에 수시로 들어가야 하는 불편함을 하나의 서비스로 해결하기 위해 +- 프로젝트 사이트 : https://www.comncheck.com +- 프로젝트에서 하고 싶었던 부분 : Spring, FastAPI 백엔드 개발, 쿠버네티스 기반 서버 구축 및 운영 +# 2. Team Members (팀원 및 팀 소개) +| 조성민 | 노성원 | 이예림 | +|:------------------------------------------------------------------------------------------------:|:------------------------------------------------------------------------------------------------:|:------------------------------------------------------------------------------------------------:| +| Lead, BE, Server | FE, UI/UX Designer | FE, UI/UX Designer | +|

|

|

| +| [@sungmin306](https://github.com/sungmin306) | [@sungwonnoh](https://github.com/sungwonnoh) | [@YerimLee](https://github.com/yerimi00) | +# 3. Project Preview +

+ +- 약 134명의 사용자가 현재 접속하여 사용중(2025.03.24 기준) + +# 4. Architecture +

+ +1. 단일 노드에서 `K3s` 기반으로 구축했다.(기존 GCP에서 미니PC 설치 후 서버 이동) +2. 클라이언트(Client)는 `Nginx` 서버로 접속한다. → 80 또는 **`443`** + - Nginx 내부적으로 Certbot을 이용하여 SSL 인증을 진행 +3. `Nginx`는 “/” 경로로 온 요청은 Next.js 컨테이너로 요청을 보낸다. +4. `Nginx`는 “/api” 경로로 온 요청은 `Spring` 컨테이너로 요청을 보낸다. + - 직접적으로 외부 통신 하지 않는 파드 같은 경우 Cluster IP로 설정하여 외부 서버에서 접근하는것을 막았다. + - `FastAPI` 및 `Next.js` 같은 경우 Replicaset 기반으로 5개의 파드가 동작해 비스의 확장성과 안정성이 보장한다. +5. 모든 요청과 응답은 `Nginx`를 통해 전달한다. +6. DB 서버는 백업기능을 위해 서버 로컬에서 관리한다. + +# 5. Service Detail +| 일정 관리 | 알람 기능 | +|:---------------------------------------------------------------------------------------------------------------------:|:---------------------------------------------------------------------------------------------------------------------:| +| Image | Image | +| **익명 질문** | **추가 기능** | +| Image | Image | + +# 4. Tech stack + +### Backend + + + + +# 5. 앞으로의 목표 + +백엔드 + +- [ ] 테스트코드 작성 +- [ ] 레디스 이용 → 성능 최적화 +- [ ] 코드 리펙토링(클린코드 만들기) +- [ ] 로직개선 + +클라우드 + +- [ ] control-plane, worker 노드 분리(고가용성) +- [ ] HPA 설정 +- [ ] 모니터링 툴 세팅 +- [ ] CI/CD 환경구성 diff --git a/build.gradle b/build.gradle index 11d7364..f32fbcb 100644 --- a/build.gradle +++ b/build.gradle @@ -23,21 +23,27 @@ repositories { mavenCentral() } + dependencies { implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'io.jsonwebtoken:jjwt-api:0.12.3' implementation 'io.jsonwebtoken:jjwt-impl:0.12.3' implementation 'io.jsonwebtoken:jjwt-jackson:0.12.3' - implementation 'io.github.cdimascio:java-dotenv:5.2.2' // .env 파일 + implementation 'org.springframework.boot:spring-boot-starter-data-redis' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' - runtimeOnly 'com.h2database:h2' + // runtimeOnly 'mysql:mysql-connector-java:8.0.33' + runtimeOnly 'com.mysql:mysql-connector-j' implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'com.google.firebase:firebase-admin:8.1.0' compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0' + implementation group: 'com.google.cloud', name: 'spring-cloud-gcp-starter', version: '6.0.0' + implementation group: 'com.google.cloud', name: 'spring-cloud-gcp-storage', version: '6.0.0' + implementation group: 'com.google.protobuf', name: 'protobuf-java', version: '4.29.3' } tasks.named('test') { diff --git a/docker-compose.yaml b/docker-compose.yaml deleted file mode 100644 index 1cc991a..0000000 --- a/docker-compose.yaml +++ /dev/null @@ -1,11 +0,0 @@ -version: '3.8' - -services: - app: - image: comncheck/backend-spring-test:1.0.1 - build: . - ports: - - "8080:8080" - env_file: - - .env - diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index a4b76b9..0000000 Binary files a/gradle/wrapper/gradle-wrapper.jar and /dev/null differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index e2847c8..0000000 --- a/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,7 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip -networkTimeout=10000 -validateDistributionUrl=true -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/hs_err_pid44980.jfr b/hs_err_pid44980.jfr deleted file mode 100644 index 87dc5cf..0000000 Binary files a/hs_err_pid44980.jfr and /dev/null differ diff --git a/hs_err_pid44980.log b/hs_err_pid44980.log deleted file mode 100644 index 5d2a9cb..0000000 --- a/hs_err_pid44980.log +++ /dev/null @@ -1,1100 +0,0 @@ -# -# A fatal error has been detected by the Java Runtime Environment: -# -# SIGBUS (0xa) at pc=0x00000001248014e4, pid=44980, tid=8707 -# -# JRE version: OpenJDK Runtime Environment Corretto-17.0.8.8.1 (17.0.8.1+8) (build 17.0.8.1+8-LTS) -# Java VM: OpenJDK 64-Bit Server VM Corretto-17.0.8.8.1 (17.0.8.1+8-LTS, mixed mode, emulated-client, sharing, tiered, compressed oops, compressed class ptrs, g1 gc, bsd-aarch64) -# Problematic frame: -# v ~StubRoutines::SafeFetch32 -# -# No core dump will be written. Core dumps have been disabled. To enable core dumping, try "ulimit -c unlimited" before starting Java again -# -# JFR recording file will be written. Location: /Users/sungmin/Desktop/ComNCheck/Spring/ComNCheck-backend/hs_err_pid44980.jfr -# -# If you would like to submit a bug report, please visit: -# https://github.com/corretto/corretto-17/issues/ -# The crash happened outside the Java Virtual Machine in native code. -# See problematic frame for where to report the bug. -# - ---------------- S U M M A R Y ------------ - -Command Line: -XX:TieredStopAtLevel=1 -Dspring.output.ansi.enabled=always -Dcom.sun.management.jmxremote -Dspring.jmx.enabled=true -Dspring.liveBeansView.mbeanDomain -Dspring.application.admin.enabled=true -Dmanagement.endpoints.jmx.exposure.include=* -javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=57802:/Applications/IntelliJ IDEA.app/Contents/bin -agentpath:/private/var/folders/c3/rqkh7s3n2c1gdz85gpftmpjr0000gn/T/libasyncProfiler.so=start,jfr,event=cpu,interval=10ms,jfrsync=profile,cstack=no,file=/Users/sungmin/IdeaSnapshots/ComNCheckApplication_2025_01_26_233554.jfr,log=/private/var/folders/c3/rqkh7s3n2c1gdz85gpftmpjr0000gn/T/ComNCheckApplication_2025_01_26_233554.jfr.log.txt,logLevel=DEBUG -Dfile.encoding=UTF-8 com.ComNCheck.ComNCheck.ComNCheckApplication - -Host: "MacBookAir10,1" arm64, 8 cores, 8G, Darwin 23.4.0, macOS 14.4.1 (23E224) -Time: Sun Jan 26 23:35:58 2025 KST elapsed time: 4.219515 seconds (0d 0h 0m 4s) - ---------------- T H R E A D --------------- - -Current thread (0x000000010e042e00): JavaThread "main" [_thread_in_native, id=8707, stack(0x000000016d98c000,0x000000016db8f000)] - -Stack: [0x000000016d98c000,0x000000016db8f000], sp=0x000000016db8c3d0, free space=2048k -Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code) -v ~StubRoutines::SafeFetch32 -V [libjvm.dylib+0x7c8890] os::is_readable_range(void const*, void const*)+0x2c -V [libjvm.dylib+0x77531c] Method::is_valid_method(Method const*)+0x38 -V [libjvm.dylib+0x35fbb8] forte_fill_call_trace_given_top(JavaThread*, ASGCT_CallTrace*, int, frame)+0x1e0 -V [libjvm.dylib+0x35f9bc] AsyncGetCallTrace+0x1d4 -C [libasyncProfiler.so+0x3ac54] Profiler::getJavaTraceAsync(void*, ASGCT_CallFrame*, int, StackContext*)+0x15c -C [libasyncProfiler.so+0x3bd04] Profiler::recordSampleForThread(int, void*, unsigned long long, int, Event*, int*)+0x1c4 -C [libasyncProfiler.so+0x4a03c] WallClock::signalHandler(int, __siginfo*, void*)+0xa8 -C [libsystem_platform.dylib+0x4584] _sigtramp+0x38 -C 0x0e41800000000069 -C [libzip.dylib+0x4844] Java_java_util_zip_Inflater_inflateBytesBytes+0xa8 -j java.util.zip.Inflater.inflateBytesBytes(J[BII[BII)J+0 java.base@17.0.8.1 -j java.util.zip.Inflater.inflate([BII)I+77 java.base@17.0.8.1 -j java.util.zip.InflaterInputStream.read([BII)I+53 java.base@17.0.8.1 -j jdk.internal.loader.Resource.getBytes()[B+117 java.base@17.0.8.1 -j jdk.internal.loader.URLClassPath$JarLoader$2.getBytes()[B+1 java.base@17.0.8.1 -j jdk.internal.loader.BuiltinClassLoader.defineClass(Ljava/lang/String;Ljdk/internal/loader/Resource;)Ljava/lang/Class;+84 java.base@17.0.8.1 -j jdk.internal.loader.BuiltinClassLoader.findClassOnClassPathOrNull(Ljava/lang/String;)Ljava/lang/Class;+37 java.base@17.0.8.1 -j jdk.internal.loader.BuiltinClassLoader.loadClassOrNull(Ljava/lang/String;Z)Ljava/lang/Class;+111 java.base@17.0.8.1 -j jdk.internal.loader.BuiltinClassLoader.loadClass(Ljava/lang/String;Z)Ljava/lang/Class;+3 java.base@17.0.8.1 -j jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(Ljava/lang/String;Z)Ljava/lang/Class;+36 java.base@17.0.8.1 -j java.lang.ClassLoader.loadClass(Ljava/lang/String;)Ljava/lang/Class;+3 java.base@17.0.8.1 -v ~StubRoutines::call_stub -V [libjvm.dylib+0x473abc] JavaCalls::call_helper(JavaValue*, methodHandle const&, JavaCallArguments*, JavaThread*)+0x390 -V [libjvm.dylib+0x472b28] JavaCalls::call_virtual(JavaValue*, Klass*, Symbol*, Symbol*, JavaCallArguments*, JavaThread*)+0xf8 -V [libjvm.dylib+0x472c80] JavaCalls::call_virtual(JavaValue*, Handle, Klass*, Symbol*, Symbol*, Handle, JavaThread*)+0x68 -V [libjvm.dylib+0x97f234] SystemDictionary::load_instance_class_impl(Symbol*, Handle, JavaThread*)+0xfc -V [libjvm.dylib+0x97dcc8] SystemDictionary::load_instance_class(unsigned int, Symbol*, Handle, JavaThread*)+0x30 -V [libjvm.dylib+0x97d3cc] SystemDictionary::resolve_instance_class_or_null(Symbol*, Handle, Handle, JavaThread*)+0x4f0 -V [libjvm.dylib+0x97c9b4] SystemDictionary::resolve_or_fail(Symbol*, Handle, Handle, bool, JavaThread*)+0x80 -V [libjvm.dylib+0x2b9574] ConstantPool::klass_at_impl(constantPoolHandle const&, int, JavaThread*)+0x1e0 -V [libjvm.dylib+0x2ba2e8] ConstantPool::klass_ref_at(int, JavaThread*)+0x6c -V [libjvm.dylib+0x68df08] LinkInfo::LinkInfo(constantPoolHandle const&, int, methodHandle const&, JavaThread*)+0x48 -V [libjvm.dylib+0x68ff70] LinkResolver::resolve_field_access(fieldDescriptor&, constantPoolHandle const&, int, methodHandle const&, Bytecodes::Code, JavaThread*)+0x30 -V [libjvm.dylib+0x46c140] InterpreterRuntime::resolve_get_put(JavaThread*, Bytecodes::Code)+0x138 -V [libjvm.dylib+0x46d4c8] InterpreterRuntime::resolve_from_cache(JavaThread*, Bytecodes::Code)+0x78 -j io.github.cdimascio.dotenv.internal.DotenvParser.(Lio/github/cdimascio/dotenv/internal/DotenvReader;ZZ)V+26 -j io.github.cdimascio.dotenv.DotenvBuilder.load()Lio/github/cdimascio/dotenv/Dotenv;+27 -j io.github.cdimascio.dotenv.Dotenv$Instance.load()Lio/github/cdimascio/dotenv/Dotenv;+7 -j io.github.cdimascio.dotenv.Dotenv.load()Lio/github/cdimascio/dotenv/Dotenv;+3 -j com.ComNCheck.ComNCheck.ComNCheckApplication.main([Ljava/lang/String;)V+0 -v ~StubRoutines::call_stub -V [libjvm.dylib+0x473abc] JavaCalls::call_helper(JavaValue*, methodHandle const&, JavaCallArguments*, JavaThread*)+0x390 -V [libjvm.dylib+0x4d8ab0] jni_invoke_static(JNIEnv_*, JavaValue*, _jobject*, JNICallType, _jmethodID*, JNI_ArgumentPusher*, JavaThread*)+0x110 -V [libjvm.dylib+0x4dc0cc] jni_CallStaticVoidMethod+0x130 -C [libjli.dylib+0x77e8] JavaMain+0xa80 -C [libjli.dylib+0x9ab8] ThreadJavaMain+0xc -C [libsystem_pthread.dylib+0x6f94] _pthread_start+0x88 - -Java frames: (J=compiled Java code, j=interpreted, Vv=VM code) -j java.util.zip.Inflater.inflateBytesBytes(J[BII[BII)J+0 java.base@17.0.8.1 -j java.util.zip.Inflater.inflate([BII)I+77 java.base@17.0.8.1 -j java.util.zip.InflaterInputStream.read([BII)I+53 java.base@17.0.8.1 -j jdk.internal.loader.Resource.getBytes()[B+117 java.base@17.0.8.1 -j jdk.internal.loader.URLClassPath$JarLoader$2.getBytes()[B+1 java.base@17.0.8.1 -j jdk.internal.loader.BuiltinClassLoader.defineClass(Ljava/lang/String;Ljdk/internal/loader/Resource;)Ljava/lang/Class;+84 java.base@17.0.8.1 -j jdk.internal.loader.BuiltinClassLoader.findClassOnClassPathOrNull(Ljava/lang/String;)Ljava/lang/Class;+37 java.base@17.0.8.1 -j jdk.internal.loader.BuiltinClassLoader.loadClassOrNull(Ljava/lang/String;Z)Ljava/lang/Class;+111 java.base@17.0.8.1 -j jdk.internal.loader.BuiltinClassLoader.loadClass(Ljava/lang/String;Z)Ljava/lang/Class;+3 java.base@17.0.8.1 -j jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(Ljava/lang/String;Z)Ljava/lang/Class;+36 java.base@17.0.8.1 -j java.lang.ClassLoader.loadClass(Ljava/lang/String;)Ljava/lang/Class;+3 java.base@17.0.8.1 -v ~StubRoutines::call_stub -j io.github.cdimascio.dotenv.internal.DotenvParser.(Lio/github/cdimascio/dotenv/internal/DotenvReader;ZZ)V+26 -j io.github.cdimascio.dotenv.DotenvBuilder.load()Lio/github/cdimascio/dotenv/Dotenv;+27 -j io.github.cdimascio.dotenv.Dotenv$Instance.load()Lio/github/cdimascio/dotenv/Dotenv;+7 -j io.github.cdimascio.dotenv.Dotenv.load()Lio/github/cdimascio/dotenv/Dotenv;+3 -j com.ComNCheck.ComNCheck.ComNCheckApplication.main([Ljava/lang/String;)V+0 -v ~StubRoutines::call_stub - -siginfo: si_signo: 10 (SIGBUS), si_code: 1 (BUS_ADRALN), si_addr: 0x00000001248014e4 - -Register to memory mapping: - - x0=0x0000007000259000 is pointing into metadata - x1=0x00000000cafebabe is an unknown value - x2=0x0 is NULL - x3=0x0 is NULL - x4=0x000000016db8d230 is pointing into the stack for thread: 0x000000010e042e00 - x5=0x000000016db8d230 is pointing into the stack for thread: 0x000000010e042e00 - x6=0xffffffffffffff97 is an unknown value - x7=0x000000000000006e is an unknown value - x8=0x00000001248014e0 is at begin+0 in a stub -StubRoutines::SafeFetch32 [0x00000001248014e0, 0x00000001248014ec] (12 bytes) - x9=0x0000000000000450 is an unknown value -x10=0x0000000124805600 is pointing into interpreter code (not bytecode specific) -x11=0x00000000000000ab is an unknown value -x12=0x00000000000000ab is an unknown value -x13=0x0 is NULL -x14=0x0 is NULL -x15=0x0000000787a315c8 is pointing into object: [B -{0x0000000787a31548} - klass: {type array byte} - - length: 1024 -x16=0x000000019d7a9da4: pthread_getspecific+0 in /usr/lib/system/libsystem_pthread.dylib at 0x000000019d7a8000 -x17=0x0000600004ac7870 points into unknown readable memory: 0xfffffffffffffffe | fe ff ff ff ff ff ff ff -x18=0x0 is NULL -x19=0x0000007000259000 is pointing into metadata -x20=0x000000010e042e00 is a thread -x21=0x0000000000000001 is an unknown value -x22={method} {0x0000007000259e10} 'inflateBytesBytes' '(J[BII[BII)J' in 'java/util/zip/Inflater' -x23=0x00000001046a3a40: _ZN12StubRoutines18_safefetch32_entryE+0 in /Users/sungmin/Library/Java/JavaVirtualMachines/corretto-17.0.8.1-1/Contents/Home/lib/server/libjvm.dylib at 0x0000000103a34000 -x24=0x00000000cafebabe is an unknown value -x25=0x000000010e0430b0 points into unknown readable memory: 0x00000001046669f8 | f8 69 66 04 01 00 00 00 -x26=0x000000016db8cfb0 is pointing into the stack for thread: 0x000000010e042e00 -x27=0x0000000110014390 points into unknown readable memory: 0x000000000000014d | 4d 01 00 00 00 00 00 00 -x28=0x00000001026b0890: inflate_fast+0x33c in /Users/sungmin/Library/Java/JavaVirtualMachines/corretto-17.0.8.1-1/Contents/Home/lib/libzip.dylib at 0x00000001026a4000 - - -Registers: - x0=0x0000007000259000 x1=0x00000000cafebabe x2=0x0000000000000000 x3=0x0000000000000000 - x4=0x000000016db8d230 x5=0x000000016db8d230 x6=0xffffffffffffff97 x7=0x000000000000006e - x8=0x00000001248014e0 x9=0x0000000000000450 x10=0x0000000124805600 x11=0x00000000000000ab -x12=0x00000000000000ab x13=0x0000000000000000 x14=0x0000000000000000 x15=0x0000000787a315c8 -x16=0x000000019d7a9da4 x17=0x0000600004ac7870 x18=0x0000000000000000 x19=0x0000007000259000 -x20=0x000000010e042e00 x21=0x0000000000000001 x22=0x0000007000259e10 x23=0x00000001046a3a40 -x24=0x00000000cafebabe x25=0x000000010e0430b0 x26=0x000000016db8cfb0 x27=0x0000000110014390 -x28=0x00000001026b0890 fp=0x000000016db8c400 lr=0x00000001041fc290 sp=0x000000016db8c3d0 -pc=0x00000001248014e4 cpsr=0x0000000060001000 -Top of Stack: (sp=0x000000016db8c3d0) -0x000000016db8c3d0: 000000010e042e00 00000000000007ff -0x000000016db8c3e0: 0000007000259e10 000000010e042e00 -0x000000016db8c3f0: 0000007000259000 0000007000259e68 -0x000000016db8c400: 000000016db8c420 00000001041fc890 -0x000000016db8c410: 0000000000000800 0000007000259e10 -0x000000016db8c420: 000000016db8c440 00000001041a931c -0x000000016db8c430: 0000000000000800 000000016db8d190 -0x000000016db8c440: 000000016db8d0b0 0000000103d93bb8 -0x000000016db8c450: 0000000000000000 0000000000000000 -0x000000016db8c460: 0000000000000000 0000000000000000 -0x000000016db8c470: 0000000000000000 0000000000000000 -0x000000016db8c480: 0000000000000000 0000000000000000 -0x000000016db8c490: 0000000000000000 0000000000000000 -0x000000016db8c4a0: 0000000000000000 0000000000000000 -0x000000016db8c4b0: 0000000000000000 0000000000000000 -0x000000016db8c4c0: 0000000000000000 0000000000000000 -0x000000016db8c4d0: 0000000000000000 0000000000000000 -0x000000016db8c4e0: 0000000000000000 0000000000000000 -0x000000016db8c4f0: 0000000000000000 0000000000000000 -0x000000016db8c500: 0000000000000000 0000000000000000 -0x000000016db8c510: 0000000000000000 0000000000000000 -0x000000016db8c520: 0000000000000000 0000000000000000 -0x000000016db8c530: 0000000000000000 0000000000000000 -0x000000016db8c540: 0000000000000000 0000000000000000 -0x000000016db8c550: 0000000000000000 0000000000000000 -0x000000016db8c560: 0000000000000000 0000000000000000 -0x000000016db8c570: 0000000000000000 0000000000000000 -0x000000016db8c580: 0000000000000000 0000000000000000 -0x000000016db8c590: 0000000000000000 0000000000000000 -0x000000016db8c5a0: 000000010361d7a0 0000000000000000 -0x000000016db8c5b0: 0000000122a47ab0 0000000000000000 -0x000000016db8c5c0: 0000000000000000 0000000000000000 - -Instructions: (pc=0x00000001248014e4) -0x00000001248013e4: 1e614000 1400003a d2923809 f2a08ce9 -0x00000001248013f4: f2c00029 6d425935 1e640886 0c402d31 -0x0000000124801404: 1e6408c7 1f5654d0 1e6c1017 1f5050d0 -0x0000000124801414: 1f504cd0 1f5048d0 1e650af6 1f50d8f6 -0x0000000124801424: 1f5694d6 1f5158f6 1e762880 36000501 -0x0000000124801434: 1e614000 14000026 2e251ca5 d2924009 -0x0000000124801444: f2a08ce9 f2c00029 6d425d36 1e600806 -0x0000000124801454: 0c402d32 1f5758c7 1e6c101a 1f4754c7 -0x0000000124801464: 1e650805 1f4750c7 d2866668 f2a7fa68 -0x0000000124801474: 1f474cc7 1e6608c0 1f4748c7 1e6e1019 -0x0000000124801484: eb08007f 540000ac 1f479400 1f460340 -0x0000000124801494: 1e603b20 1400000e d2a7fd29 eb09007f -0x00000001248014a4: 540000ac 51480069 d3607d29 9e670121 -0x00000001248014b4: 14000002 1e6a5001 1f679417 1f668742 -0x00000001248014c4: 1e613b23 1e773857 1e773860 a8c17ff3 -0x00000001248014d4: 910003bf a8c17bfd d65f03c0 b9400001 -0x00000001248014e4: aa0103e0 d65f03c0 f9400001 aa0103e0 -0x00000001248014f4: d65f03c0 00000000 00000000 00000000 -0x0000000124801504: 00000000 00000000 00000000 00000000 -0x0000000124801514: 00000000 00000000 00000000 00000000 -0x0000000124801524: 00000000 00000000 00000000 00000000 -0x0000000124801534: 00000000 00000000 00000000 00000000 -0x0000000124801544: 00000000 00000000 00000000 00000000 -0x0000000124801554: 00000000 00000000 00000000 00000000 -0x0000000124801564: 00000000 00000000 00000000 00000000 -0x0000000124801574: 00000000 00000000 00000000 00000000 -0x0000000124801584: 00000000 00000000 00000000 00000000 -0x0000000124801594: 00000000 00000000 00000000 00000000 -0x00000001248015a4: 00000000 00000000 00000000 00000000 -0x00000001248015b4: 00000000 00000000 00000000 00000000 -0x00000001248015c4: 00000000 00000000 00000000 00000000 -0x00000001248015d4: 00000000 00000000 00000000 00000000 - - -Stack slot to memory mapping: -stack at sp + 0 slots: 0x000000010e042e00 is a thread -stack at sp + 1 slots: 0x00000000000007ff is an unknown value -stack at sp + 2 slots: {method} {0x0000007000259e10} 'inflateBytesBytes' '(J[BII[BII)J' in 'java/util/zip/Inflater' -stack at sp + 3 slots: 0x000000010e042e00 is a thread -stack at sp + 4 slots: 0x0000007000259000 is pointing into metadata -stack at sp + 5 slots: 0x0000007000259e68 is pointing into metadata -stack at sp + 6 slots: 0x000000016db8c420 is pointing into the stack for thread: 0x000000010e042e00 -stack at sp + 7 slots: 0x00000001041fc890: _ZN2os17is_readable_rangeEPKvS1_+0x2c in /Users/sungmin/Library/Java/JavaVirtualMachines/corretto-17.0.8.1-1/Contents/Home/lib/server/libjvm.dylib at 0x0000000103a34000 - -StubRoutines::SafeFetch32 [0x00000001248014e0, 0x00000001248014ec] (12 bytes) -[MachCode] - 0x00000001248014e0: 0100 40b9 | e003 01aa | c003 5fd6 -[/MachCode] - ---------------- P R O C E S S --------------- - -Threads class SMR info: -_java_thread_list=0x000060000299b7a0, length=17, elements={ -0x000000010e042e00, 0x000000010e055600, 0x000000010e057e00, 0x000000010e058400, -0x00000001228fb800, 0x0000000123908600, 0x0000000123908c00, 0x000000010c860e00, -0x000000010e098a00, 0x000000010e099000, 0x0000000122936a00, 0x000000010c848c00, -0x0000000123909800, 0x000000010d097e00, 0x000000010d0ac800, 0x000000012219c400, -0x000000010da13800 -} - -Java Threads: ( => current thread ) -=>0x000000010e042e00 JavaThread "main" [_thread_in_native, id=8707, stack(0x000000016d98c000,0x000000016db8f000)] - 0x000000010e055600 JavaThread "Reference Handler" daemon [_thread_blocked, id=18947, stack(0x000000016e7e0000,0x000000016e9e3000)] - 0x000000010e057e00 JavaThread "Finalizer" daemon [_thread_blocked, id=19715, stack(0x000000016e9ec000,0x000000016ebef000)] - 0x000000010e058400 JavaThread "Signal Dispatcher" daemon [_thread_blocked, id=31235, stack(0x000000016ed10000,0x000000016ef13000)] - 0x00000001228fb800 JavaThread "Service Thread" daemon [_thread_blocked, id=23299, stack(0x000000016ef1c000,0x000000016f11f000)] - 0x0000000123908600 JavaThread "Monitor Deflation Thread" daemon [_thread_blocked, id=30723, stack(0x000000016f128000,0x000000016f32b000)] - 0x0000000123908c00 JavaThread "C1 CompilerThread0" daemon [_thread_in_native, id=30467, stack(0x000000016f334000,0x000000016f537000)] - 0x000000010c860e00 JavaThread "Sweeper thread" daemon [_thread_blocked, id=29955, stack(0x000000016f540000,0x000000016f743000)] - 0x000000010e098a00 JavaThread "Common-Cleaner" daemon [_thread_blocked, id=24323, stack(0x000000016f74c000,0x000000016f94f000)] - 0x000000010e099000 JavaThread "Attach Listener" daemon [_thread_blocked, id=29443, stack(0x000000016f958000,0x000000016fb5b000)] - 0x0000000122936a00 JavaThread "Monitor Ctrl-Break" daemon [_thread_in_native, id=24835, stack(0x000000016fb64000,0x000000016fd67000)] - 0x000000010c848c00 JavaThread "JFR Recorder Thread" daemon [_thread_blocked, id=28931, stack(0x000000016fd70000,0x000000016ff73000)] - 0x0000000123909800 JavaThread "JFR Periodic Tasks" daemon [_thread_blocked, id=28675, stack(0x000000016ff7c000,0x000000017017f000)] - 0x000000010d097e00 JavaThread "RMI TCP Accept-0" daemon [_thread_in_native, id=27907, stack(0x00000001707ac000,0x00000001709af000)] - 0x000000010d0ac800 JavaThread "Async-profiler Timer" daemon [_thread_in_native, id=27139, stack(0x0000000170a44000,0x0000000170c47000)] - 0x000000012219c400 JavaThread "Notification Thread" daemon [_thread_blocked, id=43267, stack(0x0000000170c50000,0x0000000170e53000)] - 0x000000010da13800 JavaThread "RMI TCP Connection(1)-127.0.0.1" daemon [_thread_in_vm, id=33027, stack(0x0000000171068000,0x000000017126b000)] - -Other Threads: - 0x0000000121f6b3b0 VMThread "VM Thread" [stack: 0x000000016e5d4000,0x000000016e7d7000] [id=20227] - 0x0000000102837da0 WatcherThread [stack: 0x0000000170e5c000,0x000000017105f000] [id=42755] - 0x000000010282ba30 GCTaskThread "GC Thread#0" [stack: 0x000000016db98000,0x000000016dd9b000] [id=14339] - 0x0000000103735f40 GCTaskThread "GC Thread#1" [stack: 0x0000000170188000,0x000000017038b000] [id=25603] - 0x00000001037367b0 GCTaskThread "GC Thread#2" [stack: 0x0000000170394000,0x0000000170597000] [id=28163] - 0x0000000121f73800 GCTaskThread "GC Thread#3" [stack: 0x00000001705a0000,0x00000001707a3000] [id=26371] - 0x000000010282c0e0 ConcurrentGCThread "G1 Main Marker" [stack: 0x000000016dda4000,0x000000016dfa7000] [id=13059] - 0x000000010361dfb0 ConcurrentGCThread "G1 Conc#0" [stack: 0x000000016dfb0000,0x000000016e1b3000] [id=13827] - 0x000000010361e820 ConcurrentGCThread "G1 Refine#0" [stack: 0x000000016e1bc000,0x000000016e3bf000] [id=16643] - 0x0000000121e7c260 ConcurrentGCThread "G1 Service" [stack: 0x000000016e3c8000,0x000000016e5cb000] [id=21251] - -Threads with active compile tasks: -C1 CompilerThread0 4268 1269 1 java.lang.reflect.AccessibleObject::checkCanSetAccessible (368 bytes) - -VM state: not at safepoint (normal execution) - -VM Mutex/Monitor currently owned by a thread: None - -Heap address: 0x0000000780000000, size: 2048 MB, Compressed Oops mode: Zero based, Oop shift amount: 3 - -CDS archive(s) mapped at: [0x0000007000000000-0x0000007000be0000-0x0000007000be0000), size 12451840, SharedBaseAddress: 0x0000007000000000, ArchiveRelocationMode: 1. -Compressed class space mapped at: 0x0000007001000000-0x0000007041000000, reserved size: 1073741824 -Narrow klass base: 0x0000007000000000, Narrow klass shift: 0, Narrow klass range: 0x100000000 - -GC Precious Log: - CPUs: 8 total, 8 available - Memory: 8192M - Large Page Support: Disabled - NUMA Support: Disabled - Compressed Oops: Enabled (Zero based) - Heap Region Size: 1M - Heap Min Capacity: 8M - Heap Initial Capacity: 128M - Heap Max Capacity: 2G - Pre-touch: Disabled - Parallel Workers: 8 - Concurrent Workers: 2 - Concurrent Refinement Workers: 8 - Periodic GC: Disabled - -Heap: - garbage-first heap total 133120K, used 14944K [0x0000000780000000, 0x0000000800000000) - region size 1024K, 9 young (9216K), 3 survivors (3072K) - Metaspace used 6500K, committed 6720K, reserved 1114112K - class space used 807K, committed 896K, reserved 1048576K - -Heap Regions: E=young(eden), S=young(survivor), O=old, HS=humongous(starts), HC=humongous(continues), CS=collection set, F=free, OA=open archive, CA=closed archive, TAMS=top-at-mark-start (previous, next) -| 0|0x0000000780000000, 0x0000000780100000, 0x0000000780100000|100%|HS| |TAMS 0x0000000780000000, 0x0000000780000000| Complete -| 1|0x0000000780100000, 0x0000000780200000, 0x0000000780200000|100%|HS| |TAMS 0x0000000780100000, 0x0000000780100000| Complete -| 2|0x0000000780200000, 0x0000000780300000, 0x0000000780300000|100%| O| |TAMS 0x0000000780200000, 0x0000000780200000| Untracked -| 3|0x0000000780300000, 0x0000000780400000, 0x0000000780400000|100%| O| |TAMS 0x0000000780300000, 0x0000000780300000| Untracked -| 4|0x0000000780400000, 0x0000000780500000, 0x0000000780500000|100%| O| |TAMS 0x0000000780400000, 0x0000000780400000| Untracked -| 5|0x0000000780500000, 0x00000007805a0000, 0x0000000780600000| 62%| O| |TAMS 0x0000000780500000, 0x0000000780500000| Untracked -| 6|0x0000000780600000, 0x0000000780600000, 0x0000000780700000| 0%| F| |TAMS 0x0000000780600000, 0x0000000780600000| Untracked -| 7|0x0000000780700000, 0x0000000780700000, 0x0000000780800000| 0%| F| |TAMS 0x0000000780700000, 0x0000000780700000| Untracked -| 8|0x0000000780800000, 0x0000000780800000, 0x0000000780900000| 0%| F| |TAMS 0x0000000780800000, 0x0000000780800000| Untracked -| 9|0x0000000780900000, 0x0000000780900000, 0x0000000780a00000| 0%| F| |TAMS 0x0000000780900000, 0x0000000780900000| Untracked -| 10|0x0000000780a00000, 0x0000000780a00000, 0x0000000780b00000| 0%| F| |TAMS 0x0000000780a00000, 0x0000000780a00000| Untracked -| 11|0x0000000780b00000, 0x0000000780b00000, 0x0000000780c00000| 0%| F| |TAMS 0x0000000780b00000, 0x0000000780b00000| Untracked -| 12|0x0000000780c00000, 0x0000000780c00000, 0x0000000780d00000| 0%| F| |TAMS 0x0000000780c00000, 0x0000000780c00000| Untracked -| 13|0x0000000780d00000, 0x0000000780d00000, 0x0000000780e00000| 0%| F| |TAMS 0x0000000780d00000, 0x0000000780d00000| Untracked -| 14|0x0000000780e00000, 0x0000000780e00000, 0x0000000780f00000| 0%| F| |TAMS 0x0000000780e00000, 0x0000000780e00000| Untracked -| 15|0x0000000780f00000, 0x0000000780f00000, 0x0000000781000000| 0%| F| |TAMS 0x0000000780f00000, 0x0000000780f00000| Untracked -| 16|0x0000000781000000, 0x0000000781000000, 0x0000000781100000| 0%| F| |TAMS 0x0000000781000000, 0x0000000781000000| Untracked -| 17|0x0000000781100000, 0x0000000781100000, 0x0000000781200000| 0%| F| |TAMS 0x0000000781100000, 0x0000000781100000| Untracked -| 18|0x0000000781200000, 0x0000000781200000, 0x0000000781300000| 0%| F| |TAMS 0x0000000781200000, 0x0000000781200000| Untracked -| 19|0x0000000781300000, 0x0000000781300000, 0x0000000781400000| 0%| F| |TAMS 0x0000000781300000, 0x0000000781300000| Untracked -| 20|0x0000000781400000, 0x0000000781400000, 0x0000000781500000| 0%| F| |TAMS 0x0000000781400000, 0x0000000781400000| Untracked -| 21|0x0000000781500000, 0x0000000781500000, 0x0000000781600000| 0%| F| |TAMS 0x0000000781500000, 0x0000000781500000| Untracked -| 22|0x0000000781600000, 0x0000000781600000, 0x0000000781700000| 0%| F| |TAMS 0x0000000781600000, 0x0000000781600000| Untracked -| 23|0x0000000781700000, 0x0000000781700000, 0x0000000781800000| 0%| F| |TAMS 0x0000000781700000, 0x0000000781700000| Untracked -| 24|0x0000000781800000, 0x0000000781800000, 0x0000000781900000| 0%| F| |TAMS 0x0000000781800000, 0x0000000781800000| Untracked -| 25|0x0000000781900000, 0x0000000781900000, 0x0000000781a00000| 0%| F| |TAMS 0x0000000781900000, 0x0000000781900000| Untracked -| 26|0x0000000781a00000, 0x0000000781a00000, 0x0000000781b00000| 0%| F| |TAMS 0x0000000781a00000, 0x0000000781a00000| Untracked -| 27|0x0000000781b00000, 0x0000000781b00000, 0x0000000781c00000| 0%| F| |TAMS 0x0000000781b00000, 0x0000000781b00000| Untracked -| 28|0x0000000781c00000, 0x0000000781c00000, 0x0000000781d00000| 0%| F| |TAMS 0x0000000781c00000, 0x0000000781c00000| Untracked -| 29|0x0000000781d00000, 0x0000000781d00000, 0x0000000781e00000| 0%| F| |TAMS 0x0000000781d00000, 0x0000000781d00000| Untracked -| 30|0x0000000781e00000, 0x0000000781e00000, 0x0000000781f00000| 0%| F| |TAMS 0x0000000781e00000, 0x0000000781e00000| Untracked -| 31|0x0000000781f00000, 0x0000000781f00000, 0x0000000782000000| 0%| F| |TAMS 0x0000000781f00000, 0x0000000781f00000| Untracked -| 32|0x0000000782000000, 0x0000000782000000, 0x0000000782100000| 0%| F| |TAMS 0x0000000782000000, 0x0000000782000000| Untracked -| 33|0x0000000782100000, 0x0000000782100000, 0x0000000782200000| 0%| F| |TAMS 0x0000000782100000, 0x0000000782100000| Untracked -| 34|0x0000000782200000, 0x0000000782200000, 0x0000000782300000| 0%| F| |TAMS 0x0000000782200000, 0x0000000782200000| Untracked -| 35|0x0000000782300000, 0x0000000782300000, 0x0000000782400000| 0%| F| |TAMS 0x0000000782300000, 0x0000000782300000| Untracked -| 36|0x0000000782400000, 0x0000000782400000, 0x0000000782500000| 0%| F| |TAMS 0x0000000782400000, 0x0000000782400000| Untracked -| 37|0x0000000782500000, 0x0000000782500000, 0x0000000782600000| 0%| F| |TAMS 0x0000000782500000, 0x0000000782500000| Untracked -| 38|0x0000000782600000, 0x0000000782600000, 0x0000000782700000| 0%| F| |TAMS 0x0000000782600000, 0x0000000782600000| Untracked -| 39|0x0000000782700000, 0x0000000782700000, 0x0000000782800000| 0%| F| |TAMS 0x0000000782700000, 0x0000000782700000| Untracked -| 40|0x0000000782800000, 0x0000000782800000, 0x0000000782900000| 0%| F| |TAMS 0x0000000782800000, 0x0000000782800000| Untracked -| 41|0x0000000782900000, 0x0000000782900000, 0x0000000782a00000| 0%| F| |TAMS 0x0000000782900000, 0x0000000782900000| Untracked -| 42|0x0000000782a00000, 0x0000000782a00000, 0x0000000782b00000| 0%| F| |TAMS 0x0000000782a00000, 0x0000000782a00000| Untracked -| 43|0x0000000782b00000, 0x0000000782b00000, 0x0000000782c00000| 0%| F| |TAMS 0x0000000782b00000, 0x0000000782b00000| Untracked -| 44|0x0000000782c00000, 0x0000000782c00000, 0x0000000782d00000| 0%| F| |TAMS 0x0000000782c00000, 0x0000000782c00000| Untracked -| 45|0x0000000782d00000, 0x0000000782d00000, 0x0000000782e00000| 0%| F| |TAMS 0x0000000782d00000, 0x0000000782d00000| Untracked -| 46|0x0000000782e00000, 0x0000000782e00000, 0x0000000782f00000| 0%| F| |TAMS 0x0000000782e00000, 0x0000000782e00000| Untracked -| 47|0x0000000782f00000, 0x0000000782f00000, 0x0000000783000000| 0%| F| |TAMS 0x0000000782f00000, 0x0000000782f00000| Untracked -| 48|0x0000000783000000, 0x0000000783000000, 0x0000000783100000| 0%| F| |TAMS 0x0000000783000000, 0x0000000783000000| Untracked -| 49|0x0000000783100000, 0x0000000783100000, 0x0000000783200000| 0%| F| |TAMS 0x0000000783100000, 0x0000000783100000| Untracked -| 50|0x0000000783200000, 0x0000000783200000, 0x0000000783300000| 0%| F| |TAMS 0x0000000783200000, 0x0000000783200000| Untracked -| 51|0x0000000783300000, 0x0000000783300000, 0x0000000783400000| 0%| F| |TAMS 0x0000000783300000, 0x0000000783300000| Untracked -| 52|0x0000000783400000, 0x0000000783400000, 0x0000000783500000| 0%| F| |TAMS 0x0000000783400000, 0x0000000783400000| Untracked -| 53|0x0000000783500000, 0x0000000783500000, 0x0000000783600000| 0%| F| |TAMS 0x0000000783500000, 0x0000000783500000| Untracked -| 54|0x0000000783600000, 0x0000000783600000, 0x0000000783700000| 0%| F| |TAMS 0x0000000783600000, 0x0000000783600000| Untracked -| 55|0x0000000783700000, 0x0000000783700000, 0x0000000783800000| 0%| F| |TAMS 0x0000000783700000, 0x0000000783700000| Untracked -| 56|0x0000000783800000, 0x0000000783800000, 0x0000000783900000| 0%| F| |TAMS 0x0000000783800000, 0x0000000783800000| Untracked -| 57|0x0000000783900000, 0x0000000783900000, 0x0000000783a00000| 0%| F| |TAMS 0x0000000783900000, 0x0000000783900000| Untracked -| 58|0x0000000783a00000, 0x0000000783a00000, 0x0000000783b00000| 0%| F| |TAMS 0x0000000783a00000, 0x0000000783a00000| Untracked -| 59|0x0000000783b00000, 0x0000000783b00000, 0x0000000783c00000| 0%| F| |TAMS 0x0000000783b00000, 0x0000000783b00000| Untracked -| 60|0x0000000783c00000, 0x0000000783c00000, 0x0000000783d00000| 0%| F| |TAMS 0x0000000783c00000, 0x0000000783c00000| Untracked -| 61|0x0000000783d00000, 0x0000000783d00000, 0x0000000783e00000| 0%| F| |TAMS 0x0000000783d00000, 0x0000000783d00000| Untracked -| 62|0x0000000783e00000, 0x0000000783e00000, 0x0000000783f00000| 0%| F| |TAMS 0x0000000783e00000, 0x0000000783e00000| Untracked -| 63|0x0000000783f00000, 0x0000000783f00000, 0x0000000784000000| 0%| F| |TAMS 0x0000000783f00000, 0x0000000783f00000| Untracked -| 64|0x0000000784000000, 0x0000000784000000, 0x0000000784100000| 0%| F| |TAMS 0x0000000784000000, 0x0000000784000000| Untracked -| 65|0x0000000784100000, 0x0000000784100000, 0x0000000784200000| 0%| F| |TAMS 0x0000000784100000, 0x0000000784100000| Untracked -| 66|0x0000000784200000, 0x0000000784200000, 0x0000000784300000| 0%| F| |TAMS 0x0000000784200000, 0x0000000784200000| Untracked -| 67|0x0000000784300000, 0x0000000784300000, 0x0000000784400000| 0%| F| |TAMS 0x0000000784300000, 0x0000000784300000| Untracked -| 68|0x0000000784400000, 0x0000000784400000, 0x0000000784500000| 0%| F| |TAMS 0x0000000784400000, 0x0000000784400000| Untracked -| 69|0x0000000784500000, 0x0000000784500000, 0x0000000784600000| 0%| F| |TAMS 0x0000000784500000, 0x0000000784500000| Untracked -| 70|0x0000000784600000, 0x0000000784600000, 0x0000000784700000| 0%| F| |TAMS 0x0000000784600000, 0x0000000784600000| Untracked -| 71|0x0000000784700000, 0x0000000784700000, 0x0000000784800000| 0%| F| |TAMS 0x0000000784700000, 0x0000000784700000| Untracked -| 72|0x0000000784800000, 0x0000000784800000, 0x0000000784900000| 0%| F| |TAMS 0x0000000784800000, 0x0000000784800000| Untracked -| 73|0x0000000784900000, 0x0000000784900000, 0x0000000784a00000| 0%| F| |TAMS 0x0000000784900000, 0x0000000784900000| Untracked -| 74|0x0000000784a00000, 0x0000000784a00000, 0x0000000784b00000| 0%| F| |TAMS 0x0000000784a00000, 0x0000000784a00000| Untracked -| 75|0x0000000784b00000, 0x0000000784b00000, 0x0000000784c00000| 0%| F| |TAMS 0x0000000784b00000, 0x0000000784b00000| Untracked -| 76|0x0000000784c00000, 0x0000000784c00000, 0x0000000784d00000| 0%| F| |TAMS 0x0000000784c00000, 0x0000000784c00000| Untracked -| 77|0x0000000784d00000, 0x0000000784d00000, 0x0000000784e00000| 0%| F| |TAMS 0x0000000784d00000, 0x0000000784d00000| Untracked -| 78|0x0000000784e00000, 0x0000000784e00000, 0x0000000784f00000| 0%| F| |TAMS 0x0000000784e00000, 0x0000000784e00000| Untracked -| 79|0x0000000784f00000, 0x0000000784f00000, 0x0000000785000000| 0%| F| |TAMS 0x0000000784f00000, 0x0000000784f00000| Untracked -| 80|0x0000000785000000, 0x0000000785000000, 0x0000000785100000| 0%| F| |TAMS 0x0000000785000000, 0x0000000785000000| Untracked -| 81|0x0000000785100000, 0x0000000785100000, 0x0000000785200000| 0%| F| |TAMS 0x0000000785100000, 0x0000000785100000| Untracked -| 82|0x0000000785200000, 0x0000000785200000, 0x0000000785300000| 0%| F| |TAMS 0x0000000785200000, 0x0000000785200000| Untracked -| 83|0x0000000785300000, 0x0000000785300000, 0x0000000785400000| 0%| F| |TAMS 0x0000000785300000, 0x0000000785300000| Untracked -| 84|0x0000000785400000, 0x0000000785400000, 0x0000000785500000| 0%| F| |TAMS 0x0000000785400000, 0x0000000785400000| Untracked -| 85|0x0000000785500000, 0x0000000785500000, 0x0000000785600000| 0%| F| |TAMS 0x0000000785500000, 0x0000000785500000| Untracked -| 86|0x0000000785600000, 0x0000000785600000, 0x0000000785700000| 0%| F| |TAMS 0x0000000785600000, 0x0000000785600000| Untracked -| 87|0x0000000785700000, 0x0000000785700000, 0x0000000785800000| 0%| F| |TAMS 0x0000000785700000, 0x0000000785700000| Untracked -| 88|0x0000000785800000, 0x0000000785800000, 0x0000000785900000| 0%| F| |TAMS 0x0000000785800000, 0x0000000785800000| Untracked -| 89|0x0000000785900000, 0x0000000785900000, 0x0000000785a00000| 0%| F| |TAMS 0x0000000785900000, 0x0000000785900000| Untracked -| 90|0x0000000785a00000, 0x0000000785a00000, 0x0000000785b00000| 0%| F| |TAMS 0x0000000785a00000, 0x0000000785a00000| Untracked -| 91|0x0000000785b00000, 0x0000000785b00000, 0x0000000785c00000| 0%| F| |TAMS 0x0000000785b00000, 0x0000000785b00000| Untracked -| 92|0x0000000785c00000, 0x0000000785c00000, 0x0000000785d00000| 0%| F| |TAMS 0x0000000785c00000, 0x0000000785c00000| Untracked -| 93|0x0000000785d00000, 0x0000000785d00000, 0x0000000785e00000| 0%| F| |TAMS 0x0000000785d00000, 0x0000000785d00000| Untracked -| 94|0x0000000785e00000, 0x0000000785e00000, 0x0000000785f00000| 0%| F| |TAMS 0x0000000785e00000, 0x0000000785e00000| Untracked -| 95|0x0000000785f00000, 0x0000000785f00000, 0x0000000786000000| 0%| F| |TAMS 0x0000000785f00000, 0x0000000785f00000| Untracked -| 96|0x0000000786000000, 0x0000000786000000, 0x0000000786100000| 0%| F| |TAMS 0x0000000786000000, 0x0000000786000000| Untracked -| 97|0x0000000786100000, 0x0000000786100000, 0x0000000786200000| 0%| F| |TAMS 0x0000000786100000, 0x0000000786100000| Untracked -| 98|0x0000000786200000, 0x0000000786200000, 0x0000000786300000| 0%| F| |TAMS 0x0000000786200000, 0x0000000786200000| Untracked -| 99|0x0000000786300000, 0x0000000786300000, 0x0000000786400000| 0%| F| |TAMS 0x0000000786300000, 0x0000000786300000| Untracked -| 100|0x0000000786400000, 0x0000000786400000, 0x0000000786500000| 0%| F| |TAMS 0x0000000786400000, 0x0000000786400000| Untracked -| 101|0x0000000786500000, 0x0000000786500000, 0x0000000786600000| 0%| F| |TAMS 0x0000000786500000, 0x0000000786500000| Untracked -| 102|0x0000000786600000, 0x0000000786700000, 0x0000000786700000|100%| S|CS|TAMS 0x0000000786600000, 0x0000000786600000| Complete -| 103|0x0000000786700000, 0x0000000786800000, 0x0000000786800000|100%| S|CS|TAMS 0x0000000786700000, 0x0000000786700000| Complete -| 104|0x0000000786800000, 0x0000000786900000, 0x0000000786900000|100%| S|CS|TAMS 0x0000000786800000, 0x0000000786800000| Complete -| 105|0x0000000786900000, 0x0000000786900000, 0x0000000786a00000| 0%| F| |TAMS 0x0000000786900000, 0x0000000786900000| Untracked -| 106|0x0000000786a00000, 0x0000000786a00000, 0x0000000786b00000| 0%| F| |TAMS 0x0000000786a00000, 0x0000000786a00000| Untracked -| 107|0x0000000786b00000, 0x0000000786b00000, 0x0000000786c00000| 0%| F| |TAMS 0x0000000786b00000, 0x0000000786b00000| Untracked -| 108|0x0000000786c00000, 0x0000000786c00000, 0x0000000786d00000| 0%| F| |TAMS 0x0000000786c00000, 0x0000000786c00000| Untracked -| 109|0x0000000786d00000, 0x0000000786d00000, 0x0000000786e00000| 0%| F| |TAMS 0x0000000786d00000, 0x0000000786d00000| Untracked -| 110|0x0000000786e00000, 0x0000000786e00000, 0x0000000786f00000| 0%| F| |TAMS 0x0000000786e00000, 0x0000000786e00000| Untracked -| 111|0x0000000786f00000, 0x0000000786f00000, 0x0000000787000000| 0%| F| |TAMS 0x0000000786f00000, 0x0000000786f00000| Untracked -| 112|0x0000000787000000, 0x0000000787000000, 0x0000000787100000| 0%| F| |TAMS 0x0000000787000000, 0x0000000787000000| Untracked -| 113|0x0000000787100000, 0x0000000787100000, 0x0000000787200000| 0%| F| |TAMS 0x0000000787100000, 0x0000000787100000| Untracked -| 114|0x0000000787200000, 0x0000000787200000, 0x0000000787300000| 0%| F| |TAMS 0x0000000787200000, 0x0000000787200000| Untracked -| 115|0x0000000787300000, 0x0000000787300000, 0x0000000787400000| 0%| F| |TAMS 0x0000000787300000, 0x0000000787300000| Untracked -| 116|0x0000000787400000, 0x0000000787400000, 0x0000000787500000| 0%| F| |TAMS 0x0000000787400000, 0x0000000787400000| Untracked -| 117|0x0000000787500000, 0x0000000787500000, 0x0000000787600000| 0%| F| |TAMS 0x0000000787500000, 0x0000000787500000| Untracked -| 118|0x0000000787600000, 0x0000000787600000, 0x0000000787700000| 0%| F| |TAMS 0x0000000787600000, 0x0000000787600000| Untracked -| 119|0x0000000787700000, 0x0000000787700000, 0x0000000787800000| 0%| F| |TAMS 0x0000000787700000, 0x0000000787700000| Untracked -| 120|0x0000000787800000, 0x0000000787800000, 0x0000000787900000| 0%| F| |TAMS 0x0000000787800000, 0x0000000787800000| Untracked -| 121|0x0000000787900000, 0x0000000787900000, 0x0000000787a00000| 0%| F| |TAMS 0x0000000787900000, 0x0000000787900000| Untracked -| 122|0x0000000787a00000, 0x0000000787ac8340, 0x0000000787b00000| 78%| E| |TAMS 0x0000000787a00000, 0x0000000787a00000| Complete -| 123|0x0000000787b00000, 0x0000000787c00000, 0x0000000787c00000|100%| E|CS|TAMS 0x0000000787b00000, 0x0000000787b00000| Complete -| 124|0x0000000787c00000, 0x0000000787d00000, 0x0000000787d00000|100%| E|CS|TAMS 0x0000000787c00000, 0x0000000787c00000| Complete -| 125|0x0000000787d00000, 0x0000000787e00000, 0x0000000787e00000|100%| E|CS|TAMS 0x0000000787d00000, 0x0000000787d00000| Complete -| 126|0x0000000787e00000, 0x0000000787f00000, 0x0000000787f00000|100%| E|CS|TAMS 0x0000000787e00000, 0x0000000787e00000| Complete -| 127|0x0000000787f00000, 0x0000000788000000, 0x0000000788000000|100%| E|CS|TAMS 0x0000000787f00000, 0x0000000787f00000| Complete -|2046|0x00000007ffe00000, 0x00000007ffe78000, 0x00000007fff00000| 46%|OA| |TAMS 0x00000007ffe00000, 0x00000007ffe00000| Untracked -|2047|0x00000007fff00000, 0x00000007fff80000, 0x0000000800000000| 50%|CA| |TAMS 0x00000007fff00000, 0x00000007fff00000| Untracked - -Card table byte_map: [0x000000010ec00000,0x000000010f000000] _byte_map_base: 0x000000010b000000 - -Marking Bits (Prev, Next): (CMBitMap*) 0x000000010d032210, (CMBitMap*) 0x000000010d032250 - Prev Bits: [0x0000000158000000, 0x000000015a000000) - Next Bits: [0x000000015a000000, 0x000000015c000000) - -Polling page: 0x0000000102514000 - -Metaspace: - -Usage: - Non-class: 5.57 MB used. - Class: 809.43 KB used. - Both: 6.36 MB used. - -Virtual space: - Non-class space: 64.00 MB reserved, 5.69 MB ( 9%) committed, 1 nodes. - Class space: 1.00 GB reserved, 896.00 KB ( <1%) committed, 1 nodes. - Both: 1.06 GB reserved, 6.56 MB ( <1%) committed. - -Chunk freelists: - Non-Class: 10.30 MB - Class: 15.10 MB - Both: 25.39 MB - -MaxMetaspaceSize: unlimited -CompressedClassSpaceSize: 1.00 GB -Initial GC threshold: 21.00 MB -Current GC threshold: 21.00 MB -CDS: on -MetaspaceReclaimPolicy: balanced - - commit_granule_bytes: 65536. - - commit_granule_words: 8192. - - virtual_space_node_default_size: 8388608. - - enlarge_chunks_in_place: 1. - - new_chunks_are_fully_committed: 0. - - uncommit_free_chunks: 1. - - use_allocation_guard: 0. - - handle_deallocations: 1. - - -Internal statistics: - -num_allocs_failed_limit: 0. -num_arena_births: 136. -num_arena_deaths: 0. -num_vsnodes_births: 2. -num_vsnodes_deaths: 0. -num_space_committed: 105. -num_space_uncommitted: 0. -num_chunks_returned_to_freelist: 0. -num_chunks_taken_from_freelist: 195. -num_chunk_merges: 0. -num_chunk_splits: 103. -num_chunks_enlarged: 26. -num_inconsistent_stats: 0. - -CodeCache: size=49152Kb used=3474Kb max_used=3474Kb free=45677Kb - bounds [0x0000000124800000, 0x0000000124b70000, 0x0000000127800000] - total_blobs=1771 nmethods=1313 adapters=389 - compilation: enabled - stopped_count=0, restarted_count=0 - full_count=0 - -Compilation events (20 events): -Event: 4.211 Thread 0x0000000123908c00 nmethod 1305 0x0000000124b58710 code [0x0000000124b58880, 0x0000000124b58918] -Event: 4.211 Thread 0x0000000123908c00 1272 1 java.lang.invoke.MemberName::isCallerSensitive (8 bytes) -Event: 4.211 Thread 0x0000000123908c00 nmethod 1272 0x0000000124b58a10 code [0x0000000124b58b80, 0x0000000124b58c58] -Event: 4.211 Thread 0x0000000123908c00 1291 1 java.lang.invoke.MethodType::basicType (8 bytes) -Event: 4.211 Thread 0x0000000123908c00 nmethod 1291 0x0000000124b58d10 code [0x0000000124b58e80, 0x0000000124b58f58] -Event: 4.211 Thread 0x0000000123908c00 1303 1 jdk.jfr.internal.PlatformEventType::isLargeSize (5 bytes) -Event: 4.212 Thread 0x0000000123908c00 nmethod 1303 0x0000000124b59090 code [0x0000000124b59200, 0x0000000124b59298] -Event: 4.212 Thread 0x0000000123908c00 1304 1 jdk.jfr.internal.PlatformEventType::getStackTraceEnabled (5 bytes) -Event: 4.212 Thread 0x0000000123908c00 nmethod 1304 0x0000000124b59390 code [0x0000000124b59500, 0x0000000124b59598] -Event: 4.212 Thread 0x0000000123908c00 1273 1 java.lang.reflect.Modifier::isAbstract (14 bytes) -Event: 4.212 Thread 0x0000000123908c00 nmethod 1273 0x0000000124b59690 code [0x0000000124b59800, 0x0000000124b59898] -Event: 4.212 Thread 0x0000000123908c00 1275 1 jdk.internal.org.objectweb.asm.ClassWriter::visitField (53 bytes) -Event: 4.212 Thread 0x0000000123908c00 nmethod 1275 0x0000000124b59990 code [0x0000000124b59b40, 0x0000000124b59df8] -Event: 4.212 Thread 0x0000000123908c00 1276 1 jdk.internal.org.objectweb.asm.FieldWriter:: (69 bytes) -Event: 4.213 Thread 0x0000000123908c00 nmethod 1276 0x0000000124b5a010 code [0x0000000124b5a1c0, 0x0000000124b5a438] -Event: 4.213 Thread 0x0000000123908c00 1313 1 java.util.WeakHashMap::matchesKey (33 bytes) -Event: 4.214 Thread 0x0000000123908c00 nmethod 1313 0x0000000124b5a710 code [0x0000000124b5a8c0, 0x0000000124b5aa98] -Event: 4.214 Thread 0x0000000123908c00 1314 ! 1 jdk.internal.loader.URLClassPath$JarLoader::ensureOpen (36 bytes) -Event: 4.216 Thread 0x0000000123908c00 nmethod 1314 0x0000000124b5ac90 code [0x0000000124b5ae80, 0x0000000124b5b378] -Event: 4.216 Thread 0x0000000123908c00 1315 s! 1 jdk.internal.loader.URLClassPath::getLoader (194 bytes) - -GC Heap History (2 events): -Event: 4.100 GC heap before -{Heap before GC invocations=0 (full 0): - garbage-first heap total 133120K, used 25568K [0x0000000780000000, 0x0000000800000000) - region size 1024K, 23 young (23552K), 0 survivors (0K) - Metaspace used 5388K, committed 5632K, reserved 1114112K - class space used 667K, committed 768K, reserved 1048576K -} -Event: 4.113 GC heap after -{Heap after GC invocations=1 (full 0): - garbage-first heap total 133120K, used 9824K [0x0000000780000000, 0x0000000800000000) - region size 1024K, 3 young (3072K), 3 survivors (3072K) - Metaspace used 5388K, committed 5632K, reserved 1114112K - class space used 667K, committed 768K, reserved 1048576K -} - -Dll operation events (10 events): -Event: 2.839 Loaded shared library /Users/sungmin/Library/Java/JavaVirtualMachines/corretto-17.0.8.1-1/Contents/Home/lib/libjava.dylib -Event: 3.356 Loaded shared library /Users/sungmin/Library/Java/JavaVirtualMachines/corretto-17.0.8.1-1/Contents/Home/lib/libzip.dylib -Event: 3.389 Loaded shared library /Users/sungmin/Library/Java/JavaVirtualMachines/corretto-17.0.8.1-1/Contents/Home/lib/libinstrument.dylib -Event: 3.412 Loaded shared library /Users/sungmin/Library/Java/JavaVirtualMachines/corretto-17.0.8.1-1/Contents/Home/lib/libnio.dylib -Event: 3.427 Loaded shared library /Users/sungmin/Library/Java/JavaVirtualMachines/corretto-17.0.8.1-1/Contents/Home/lib/libzip.dylib -Event: 3.543 Loaded shared library /Users/sungmin/Library/Java/JavaVirtualMachines/corretto-17.0.8.1-1/Contents/Home/lib/libjimage.dylib -Event: 3.609 Loaded shared library /Users/sungmin/Library/Java/JavaVirtualMachines/corretto-17.0.8.1-1/Contents/Home/lib/libnet.dylib -Event: 3.631 Loaded shared library /Users/sungmin/Library/Java/JavaVirtualMachines/corretto-17.0.8.1-1/Contents/Home/lib/libextnet.dylib -Event: 3.803 Loaded shared library /Users/sungmin/Library/Java/JavaVirtualMachines/corretto-17.0.8.1-1/Contents/Home/lib/libmanagement.dylib -Event: 3.837 Loaded shared library /Users/sungmin/Library/Java/JavaVirtualMachines/corretto-17.0.8.1-1/Contents/Home/lib/libmanagement_ext.dylib - -Deoptimization events (6 events): -Event: 3.883 Thread 0x000000010e042e00 DEOPT PACKING pc=0x0000000124a52598 sp=0x000000016db8c970 -Event: 3.883 Thread 0x000000010e042e00 DEOPT UNPACKING pc=0x000000012484777c sp=0x000000016db8c660 mode 3 -Event: 3.906 Thread 0x000000010e042e00 DEOPT PACKING pc=0x0000000124a0cf0c sp=0x000000016db8da10 -Event: 3.906 Thread 0x000000010e042e00 DEOPT UNPACKING pc=0x000000012484777c sp=0x000000016db8d780 mode 3 -Event: 4.036 Thread 0x000000010e042e00 DEOPT PACKING pc=0x0000000124ae9d74 sp=0x000000016db8c3b0 -Event: 4.036 Thread 0x000000010e042e00 DEOPT UNPACKING pc=0x000000012484777c sp=0x000000016db8c070 mode 3 - -Classes unloaded (0 events): -No events - -Classes redefined (20 events): -Event: 4.092 Thread 0x0000000121f6b3b0 redefined class name=sun.nio.ch.SocketChannelImpl, count=1 -Event: 4.092 Thread 0x0000000121f6b3b0 redefined class name=java.lang.Throwable, count=1 -Event: 4.092 Thread 0x0000000121f6b3b0 redefined class name=java.lang.Error, count=2 -Event: 4.182 Thread 0x0000000121f6b3b0 redefined class name=jdk.internal.event.ProcessStartEvent, count=1 -Event: 4.182 Thread 0x0000000121f6b3b0 redefined class name=jdk.jfr.events.ContainerIOUsageEvent, count=1 -Event: 4.182 Thread 0x0000000121f6b3b0 redefined class name=jdk.jfr.events.ContainerMemoryUsageEvent, count=1 -Event: 4.182 Thread 0x0000000121f6b3b0 redefined class name=jdk.jfr.events.ContainerCPUThrottlingEvent, count=1 -Event: 4.182 Thread 0x0000000121f6b3b0 redefined class name=jdk.jfr.events.ContainerCPUUsageEvent, count=1 -Event: 4.182 Thread 0x0000000121f6b3b0 redefined class name=jdk.jfr.events.ContainerConfigurationEvent, count=1 -Event: 4.182 Thread 0x0000000121f6b3b0 redefined class name=jdk.jfr.events.InitialSecurityPropertyEvent, count=1 -Event: 4.182 Thread 0x0000000121f6b3b0 redefined class name=jdk.jfr.events.DirectBufferStatisticsEvent, count=1 -Event: 4.182 Thread 0x0000000121f6b3b0 redefined class name=jdk.jfr.events.ActiveRecordingEvent, count=1 -Event: 4.182 Thread 0x0000000121f6b3b0 redefined class name=jdk.jfr.events.ActiveSettingEvent, count=1 -Event: 4.182 Thread 0x0000000121f6b3b0 redefined class name=jdk.jfr.events.ErrorThrownEvent, count=1 -Event: 4.182 Thread 0x0000000121f6b3b0 redefined class name=jdk.jfr.events.ExceptionStatisticsEvent, count=1 -Event: 4.182 Thread 0x0000000121f6b3b0 redefined class name=jdk.jfr.events.SocketWriteEvent, count=1 -Event: 4.182 Thread 0x0000000121f6b3b0 redefined class name=jdk.jfr.events.SocketReadEvent, count=1 -Event: 4.182 Thread 0x0000000121f6b3b0 redefined class name=jdk.jfr.events.FileWriteEvent, count=1 -Event: 4.182 Thread 0x0000000121f6b3b0 redefined class name=jdk.jfr.events.FileReadEvent, count=1 -Event: 4.182 Thread 0x0000000121f6b3b0 redefined class name=jdk.jfr.events.FileForceEvent, count=1 - -Internal exceptions (20 events): -Event: 3.902 Thread 0x000000010e042e00 Exception (0x0000000787181a78) -thrown [src/hotspot/share/classfile/systemDictionary.cpp, line 256] -Event: 3.905 Thread 0x000000010e042e00 Exception (0x0000000787193f50) -thrown [src/hotspot/share/classfile/systemDictionary.cpp, line 256] -Event: 3.908 Thread 0x000000010e042e00 Exception (0x00000007871a3f48) -thrown [src/hotspot/share/classfile/systemDictionary.cpp, line 256] -Event: 3.909 Thread 0x000000010e042e00 Exception (0x00000007871b0e68) -thrown [src/hotspot/share/classfile/systemDictionary.cpp, line 256] -Event: 3.912 Thread 0x000000010e042e00 Exception (0x00000007871be7a8) -thrown [src/hotspot/share/classfile/systemDictionary.cpp, line 256] -Event: 3.917 Thread 0x000000010e099000 Exception (0x00000007871431f0) -thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 759] -Event: 3.918 Thread 0x000000010e042e00 Exception (0x00000007871e63f8) -thrown [src/hotspot/share/classfile/systemDictionary.cpp, line 256] -Event: 3.921 Thread 0x000000010e042e00 Exception (0x0000000787001198) -thrown [src/hotspot/share/classfile/systemDictionary.cpp, line 256] -Event: 3.925 Thread 0x000000010e042e00 Exception (0x0000000787013318) -thrown [src/hotspot/share/classfile/systemDictionary.cpp, line 256] -Event: 3.930 Thread 0x000000010e042e00 Exception (0x0000000787023520) -thrown [src/hotspot/share/classfile/systemDictionary.cpp, line 256] -Event: 3.932 Thread 0x000000010e042e00 Exception (0x000000078702ee80) -thrown [src/hotspot/share/classfile/systemDictionary.cpp, line 256] -Event: 3.937 Thread 0x000000010e042e00 Exception (0x0000000787045768) -thrown [src/hotspot/share/classfile/systemDictionary.cpp, line 256] -Event: 3.940 Thread 0x000000010e042e00 Exception (0x0000000787055278) -thrown [src/hotspot/share/classfile/systemDictionary.cpp, line 256] -Event: 3.942 Thread 0x000000010e042e00 Exception (0x0000000787062be0) -thrown [src/hotspot/share/classfile/systemDictionary.cpp, line 256] -Event: 3.945 Thread 0x000000010e042e00 Exception (0x0000000787070c60) -thrown [src/hotspot/share/classfile/systemDictionary.cpp, line 256] -Event: 3.946 Thread 0x000000010e042e00 Exception (0x00000007870f37d8) -thrown [src/hotspot/share/classfile/systemDictionary.cpp, line 256] -Event: 4.097 Thread 0x000000010e042e00 Exception (0x00000007869dc758) -thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 759] -Event: 4.098 Thread 0x000000010e042e00 Exception (0x00000007869e26b0) -thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 759] -Event: 4.128 Thread 0x000000010e042e00 Exception (0x0000000787f6de50) -thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 759] -Event: 4.169 Thread 0x000000010e099000 Exception (0x0000000787da3bd8) -thrown [src/hotspot/share/classfile/systemDictionary.cpp, line 256] - -VM Operations (20 events): -Event: 3.856 Executing VM operation: ICBufferFull -Event: 3.856 Executing VM operation: ICBufferFull done -Event: 4.044 Executing VM operation: ICBufferFull -Event: 4.044 Executing VM operation: ICBufferFull done -Event: 4.090 Executing VM operation: RedefineClasses -Event: 4.096 Executing VM operation: RedefineClasses done -Event: 4.100 Executing VM operation: G1CollectForAllocation -Event: 4.114 Executing VM operation: G1CollectForAllocation done -Event: 4.125 Executing VM operation: HandshakeAllThreads -Event: 4.125 Executing VM operation: HandshakeAllThreads done -Event: 4.163 Executing VM operation: JFRCheckpoint -Event: 4.163 Executing VM operation: JFRCheckpoint done -Event: 4.167 Executing VM operation: JFROldObject -Event: 4.167 Executing VM operation: JFROldObject done -Event: 4.180 Executing VM operation: RedefineClasses -Event: 4.185 Executing VM operation: RedefineClasses done -Event: 4.196 Executing VM operation: ClassLoaderStatsOperation -Event: 4.197 Executing VM operation: ClassLoaderStatsOperation done -Event: 4.207 Executing VM operation: HandshakeAllThreads -Event: 4.208 Executing VM operation: HandshakeAllThreads done - -Events (20 events): -Event: 4.195 loading class sun/rmi/server/MarshalOutputStream done -Event: 4.195 loading class sun/rmi/transport/ConnectionOutputStream done -Event: 4.197 loading class jdk/internal/agent/ConnectorAddressLink -Event: 4.198 loading class jdk/internal/agent/ConnectorAddressLink done -Event: 4.198 loading class sun/management/counter/Units -Event: 4.198 loading class sun/management/counter/Units done -Event: 4.205 Thread 0x000000010d0ac800 Thread added: 0x000000010d0ac800 -Event: 4.205 Protecting memory [0x0000000170a44000,0x0000000170a50000] with protection modes 0 -Event: 4.205 Thread 0x000000012219c400 Thread added: 0x000000012219c400 -Event: 4.205 Protecting memory [0x0000000170c50000,0x0000000170c5c000] with protection modes 0 -Event: 4.205 loading class jdk/internal/vm/PostVMInitHook -Event: 4.205 loading class jdk/internal/vm/PostVMInitHook done -Event: 4.207 loading class jdk/internal/loader/URLClassPath$FileLoader$1 -Event: 4.207 loading class jdk/internal/loader/URLClassPath$FileLoader$1 done -Event: 4.209 loading class sun/security/util/ManifestEntryVerifier -Event: 4.209 loading class sun/security/util/ManifestEntryVerifier done -Event: 4.215 loading class java/lang/UnsupportedOperationException -Event: 4.215 loading class java/lang/UnsupportedOperationException done -Event: 4.216 loading class java/lang/AssertionError -Event: 4.217 loading class java/lang/AssertionError done - - -Dynamic libraries: -0x0000000102980000 /Users/sungmin/Library/Java/JavaVirtualMachines/corretto-17.0.8.1-1/Contents/Home/lib/libjli.dylib -0x00000001b7ac9000 /System/Library/Frameworks/Cocoa.framework/Versions/A/Cocoa -0x00000001a10aa000 /System/Library/Frameworks/AppKit.framework/Versions/C/AppKit -0x00000001a4118000 /System/Library/Frameworks/CoreData.framework/Versions/A/CoreData -0x000000019e964000 /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation -0x00000001aa41e000 /usr/lib/libSystem.B.dylib -0x00000001a23e7000 /System/Library/PrivateFrameworks/UIFoundation.framework/Versions/A/UIFoundation -0x0000000238046000 /System/Library/PrivateFrameworks/CollectionViewCore.framework/Versions/A/CollectionViewCore -0x00000001b1680000 /System/Library/PrivateFrameworks/RemoteViewServices.framework/Versions/A/RemoteViewServices -0x00000001a82d0000 /System/Library/PrivateFrameworks/XCTTargetBootstrap.framework/Versions/A/XCTTargetBootstrap -0x00000001aca58000 /System/Library/PrivateFrameworks/InternationalSupport.framework/Versions/A/InternationalSupport -0x00000001acaac000 /System/Library/PrivateFrameworks/UserActivity.framework/Versions/A/UserActivity -0x000000025b9a2000 /System/Library/PrivateFrameworks/WindowManagement.framework/Versions/A/WindowManagement -0x000000019e5e4000 /System/Library/Frameworks/SystemConfiguration.framework/Versions/A/SystemConfiguration -0x00000001abec7000 /usr/lib/libspindump.dylib -0x00000001a259c000 /System/Library/Frameworks/UniformTypeIdentifiers.framework/Versions/A/UniformTypeIdentifiers -0x00000001a614a000 /usr/lib/libapp_launch_measurement.dylib -0x00000001a5572000 /System/Library/PrivateFrameworks/CoreAnalytics.framework/Versions/A/CoreAnalytics -0x00000001a6151000 /System/Library/PrivateFrameworks/CoreAutoLayout.framework/Versions/A/CoreAutoLayout -0x00000001a7af1000 /System/Library/Frameworks/Metal.framework/Versions/A/Metal -0x00000001a8a77000 /usr/lib/liblangid.dylib -0x00000001a82d6000 /System/Library/PrivateFrameworks/CoreSVG.framework/Versions/A/CoreSVG -0x00000001a2f65000 /System/Library/PrivateFrameworks/SkyLight.framework/Versions/A/SkyLight -0x00000001a3405000 /System/Library/Frameworks/CoreGraphics.framework/Versions/A/CoreGraphics -0x00000001b1d53000 /System/Library/Frameworks/Accelerate.framework/Versions/A/Accelerate -0x00000001abd22000 /System/Library/PrivateFrameworks/IconServices.framework/Versions/A/IconServices -0x00000001a7acf000 /System/Library/Frameworks/IOSurface.framework/Versions/A/IOSurface -0x00000001a55a1000 /usr/lib/libDiagnosticMessagesClient.dylib -0x00000001aa361000 /usr/lib/libz.1.dylib -0x00000001b54ef000 /System/Library/Frameworks/ApplicationServices.framework/Versions/A/ApplicationServices -0x00000001a82b8000 /System/Library/PrivateFrameworks/DFRFoundation.framework/Versions/A/DFRFoundation -0x00000001a0904000 /usr/lib/libicucore.A.dylib -0x00000001adab5000 /System/Library/Frameworks/AudioToolbox.framework/Versions/A/AudioToolbox -0x00000001aca67000 /System/Library/PrivateFrameworks/DataDetectorsCore.framework/Versions/A/DataDetectorsCore -0x00000001c6442000 /System/Library/PrivateFrameworks/TextInput.framework/Versions/A/TextInput -0x00000001a2eb6000 /usr/lib/libMobileGestalt.dylib -0x00000001a7ff4000 /System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/Versions/A/HIToolbox -0x00000001a5a78000 /System/Library/Frameworks/QuartzCore.framework/Versions/A/QuartzCore -0x00000001a0531000 /System/Library/Frameworks/Security.framework/Versions/A/Security -0x00000001b16c0000 /System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/SpeechRecognition.framework/Versions/A/SpeechRecognition -0x00000001a5e78000 /System/Library/PrivateFrameworks/CoreUI.framework/Versions/A/CoreUI -0x000000019fe30000 /System/Library/Frameworks/CoreAudio.framework/Versions/A/CoreAudio -0x00000001a5685000 /System/Library/Frameworks/DiskArbitration.framework/Versions/A/DiskArbitration -0x00000001ac322000 /System/Library/PrivateFrameworks/MultitouchSupport.framework/Versions/A/MultitouchSupport -0x00000001a2eb4000 /usr/lib/libenergytrace.dylib -0x00000001bbbcb000 /System/Library/PrivateFrameworks/RenderBox.framework/Versions/A/RenderBox -0x00000001a0f5e000 /System/Library/Frameworks/IOKit.framework/Versions/A/IOKit -0x00000001b1a94000 /System/Library/Frameworks/CoreServices.framework/Versions/A/CoreServices -0x00000001a60d6000 /System/Library/PrivateFrameworks/PerformanceAnalysis.framework/Versions/A/PerformanceAnalysis -0x00000001f3dad000 /System/Library/Frameworks/OpenGL.framework/Versions/A/OpenGL -0x00000001a619b000 /usr/lib/libxml2.2.dylib -0x00000001a97a6000 /System/Library/PrivateFrameworks/MobileKeyBag.framework/Versions/A/MobileKeyBag -0x000000019d3d0000 /usr/lib/libobjc.A.dylib -0x000000019d6c4000 /usr/lib/libc++.1.dylib -0x00000001b1a11000 /System/Library/Frameworks/Accessibility.framework/Versions/A/Accessibility -0x00000001a3ae4000 /System/Library/Frameworks/ColorSync.framework/Versions/A/ColorSync -0x000000019d810000 /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation -0x00000001a8632000 /System/Library/Frameworks/CoreImage.framework/Versions/A/CoreImage -0x000000019fc3b000 /System/Library/Frameworks/CoreText.framework/Versions/A/CoreText -0x00000001f8607000 /System/Library/Frameworks/CoreTransferable.framework/Versions/A/CoreTransferable -0x00000001f8acc000 /System/Library/Frameworks/DeveloperToolsSupport.framework/Versions/A/DeveloperToolsSupport -0x00000001a8311000 /System/Library/Frameworks/ImageIO.framework/Versions/A/ImageIO -0x00000001fce33000 /System/Library/Frameworks/Symbols.framework/Versions/A/Symbols -0x00000001aa424000 /System/Library/PrivateFrameworks/SoftLinking.framework/Versions/A/SoftLinking -0x00000001ad467000 /usr/lib/swift/libswiftCore.dylib -0x00000001c30eb000 /usr/lib/swift/libswiftCoreFoundation.dylib -0x00000001c0bf2000 /usr/lib/swift/libswiftCoreGraphics.dylib -0x00000001c3135000 /usr/lib/swift/libswiftCoreImage.dylib -0x00000001c0bf9000 /usr/lib/swift/libswiftDarwin.dylib -0x00000001b30f9000 /usr/lib/swift/libswiftDispatch.dylib -0x00000001c3136000 /usr/lib/swift/libswiftIOKit.dylib -0x00000001ceb3b000 /usr/lib/swift/libswiftMetal.dylib -0x00000001dc1af000 /usr/lib/swift/libswiftOSLog.dylib -0x00000001b5968000 /usr/lib/swift/libswiftObjectiveC.dylib -0x00000001d325c000 /usr/lib/swift/libswiftQuartzCore.dylib -0x00000001d7343000 /usr/lib/swift/libswiftUniformTypeIdentifiers.dylib -0x00000001c30fd000 /usr/lib/swift/libswiftXPC.dylib -0x0000000263e4d000 /usr/lib/swift/libswift_Concurrency.dylib -0x00000001b596c000 /usr/lib/swift/libswiftos.dylib -0x00000001c63a7000 /usr/lib/swift/libswiftsimd.dylib -0x00000001aa5d3000 /usr/lib/libcompression.dylib -0x00000001ac9b2000 /System/Library/PrivateFrameworks/TextureIO.framework/Versions/A/TextureIO -0x00000001ab9ce000 /usr/lib/libate.dylib -0x00000001aa418000 /usr/lib/system/libcache.dylib -0x00000001aa3d3000 /usr/lib/system/libcommonCrypto.dylib -0x00000001aa3ff000 /usr/lib/system/libcompiler_rt.dylib -0x00000001aa3f5000 /usr/lib/system/libcopyfile.dylib -0x000000019d514000 /usr/lib/system/libcorecrypto.dylib -0x000000019d5fa000 /usr/lib/system/libdispatch.dylib -0x000000019d7b5000 /usr/lib/system/libdyld.dylib -0x00000001aa40e000 /usr/lib/system/libkeymgr.dylib -0x00000001aa3ab000 /usr/lib/system/libmacho.dylib -0x00000001a988c000 /usr/lib/system/libquarantine.dylib -0x00000001aa40b000 /usr/lib/system/libremovefile.dylib -0x00000001a2f2c000 /usr/lib/system/libsystem_asl.dylib -0x000000019d4a9000 /usr/lib/system/libsystem_blocks.dylib -0x000000019d645000 /usr/lib/system/libsystem_c.dylib -0x00000001aa403000 /usr/lib/system/libsystem_collections.dylib -0x00000001a8a66000 /usr/lib/system/libsystem_configuration.dylib -0x00000001a7aa4000 /usr/lib/system/libsystem_containermanager.dylib -0x00000001aa042000 /usr/lib/system/libsystem_coreservices.dylib -0x00000001a0bcb000 /usr/lib/system/libsystem_darwin.dylib -0x000000026419a000 /usr/lib/system/libsystem_darwindirectory.dylib -0x00000001aa40f000 /usr/lib/system/libsystem_dnssd.dylib -0x000000026419e000 /usr/lib/system/libsystem_eligibility.dylib -0x000000019d642000 /usr/lib/system/libsystem_featureflags.dylib -0x000000019d7e3000 /usr/lib/system/libsystem_info.dylib -0x00000001aa370000 /usr/lib/system/libsystem_m.dylib -0x000000019d5bd000 /usr/lib/system/libsystem_malloc.dylib -0x00000001a2e9a000 /usr/lib/system/libsystem_networkextension.dylib -0x00000001a1041000 /usr/lib/system/libsystem_notify.dylib -0x00000001a8a6b000 /usr/lib/system/libsystem_sandbox.dylib -0x00000002641a2000 /usr/lib/system/libsystem_sanitizers.dylib -0x00000001aa408000 /usr/lib/system/libsystem_secinit.dylib -0x000000019d76d000 /usr/lib/system/libsystem_kernel.dylib -0x000000019d7db000 /usr/lib/system/libsystem_platform.dylib -0x000000019d7a8000 /usr/lib/system/libsystem_pthread.dylib -0x00000001a491a000 /usr/lib/system/libsystem_symptoms.dylib -0x000000019d4f9000 /usr/lib/system/libsystem_trace.dylib -0x00000001aa3e1000 /usr/lib/system/libunwind.dylib -0x000000019d4ae000 /usr/lib/system/libxpc.dylib -0x000000019d751000 /usr/lib/libc++abi.dylib -0x00000001aa3ed000 /usr/lib/liboah.dylib -0x00000001ab87a000 /usr/lib/liblzma.5.dylib -0x00000001aa420000 /usr/lib/libfakelink.dylib -0x00000001a2ac5000 /System/Library/Frameworks/CFNetwork.framework/Versions/A/CFNetwork -0x00000001aa474000 /usr/lib/libarchive.2.dylib -0x00000001af996000 /System/Library/Frameworks/Combine.framework/Versions/A/Combine -0x000000023805a000 /System/Library/PrivateFrameworks/CollectionsInternal.framework/Versions/A/CollectionsInternal -0x000000024e0eb000 /System/Library/PrivateFrameworks/ReflectionInternal.framework/Versions/A/ReflectionInternal -0x000000024e671000 /System/Library/PrivateFrameworks/RuntimeInternal.framework/Versions/A/RuntimeInternal -0x0000000263fa1000 /usr/lib/swift/libswift_StringProcessing.dylib -0x00000001a0edc000 /System/Library/PrivateFrameworks/CoreServicesInternal.framework/Versions/A/CoreServicesInternal -0x00000001a98b3000 /usr/lib/libbsm.0.dylib -0x00000001aa3b3000 /usr/lib/system/libkxld.dylib -0x00000001a6112000 /System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/FSEvents.framework/Versions/A/FSEvents -0x00000001a0bd6000 /System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/CarbonCore.framework/Versions/A/CarbonCore -0x00000001a55ea000 /System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/Metadata.framework/Versions/A/Metadata -0x00000001aa048000 /System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/OSServices.framework/Versions/A/OSServices -0x00000001aa4fd000 /System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/SearchKit.framework/Versions/A/SearchKit -0x00000001a489b000 /System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/AE.framework/Versions/A/AE -0x000000019dce9000 /System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/LaunchServices -0x00000001ab823000 /System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/DictionaryServices.framework/Versions/A/DictionaryServices -0x00000001a611f000 /System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/SharedFileList.framework/Versions/A/SharedFileList -0x00000001aa59d000 /usr/lib/libapple_nghttp2.dylib -0x00000001a450f000 /usr/lib/libsqlite3.dylib -0x00000001a4923000 /System/Library/Frameworks/Network.framework/Versions/A/Network -0x000000026285a000 /usr/lib/libCoreEntitlements.dylib -0x000000024901d000 /System/Library/PrivateFrameworks/MessageSecurity.framework/Versions/A/MessageSecurity -0x00000001a44f5000 /System/Library/PrivateFrameworks/ProtocolBuffer.framework/Versions/A/ProtocolBuffer -0x00000001a9f71000 /System/Library/PrivateFrameworks/AppleFSCompression.framework/Versions/A/AppleFSCompression -0x00000001a989b000 /usr/lib/libcoretls.dylib -0x00000001ab899000 /usr/lib/libcoretls_cfhelpers.dylib -0x00000001aa5cd000 /usr/lib/libpam.2.dylib -0x00000001ab90b000 /usr/lib/libxar.1.dylib -0x00000001abcf9000 /usr/lib/libheimdal-asn1.dylib -0x00000001a2ac4000 /usr/lib/libnetwork.dylib -0x00000001aa425000 /usr/lib/libpcap.A.dylib -0x00000001a490f000 /usr/lib/libdns_services.dylib -0x00000001a8a73000 /System/Library/PrivateFrameworks/AppleSystemInfo.framework/Versions/A/AppleSystemInfo -0x00000001a959b000 /System/Library/PrivateFrameworks/IOMobileFramebuffer.framework/Versions/A/IOMobileFramebuffer -0x0000000263ef8000 /usr/lib/swift/libswift_RegexParser.dylib -0x00000001aa035000 /usr/lib/libbz2.1.0.dylib -0x00000001a988f000 /usr/lib/libCheckFix.dylib -0x00000001a2f44000 /System/Library/PrivateFrameworks/TCC.framework/Versions/A/TCC -0x00000001a8a79000 /System/Library/PrivateFrameworks/CoreNLP.framework/Versions/A/CoreNLP -0x00000001a55a3000 /System/Library/PrivateFrameworks/MetadataUtilities.framework/Versions/A/MetadataUtilities -0x00000001a98c5000 /usr/lib/libmecab.dylib -0x000000019e678000 /usr/lib/libCRFSuite.dylib -0x00000001a9921000 /usr/lib/libgermantok.dylib -0x00000001aa574000 /usr/lib/libThaiTokenizer.dylib -0x00000001a568e000 /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vImage.framework/Versions/A/vImage -0x00000001b1a6b000 /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/vecLib -0x00000001ab952000 /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libvMisc.dylib -0x00000001a9476000 /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libvDSP.dylib -0x000000019e0ef000 /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib -0x00000001aa6a8000 /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libLAPACK.dylib -0x00000001a9924000 /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libLinearAlgebra.dylib -0x00000001aa5b8000 /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libSparseBLAS.dylib -0x00000001aa6a3000 /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libQuadrature.dylib -0x00000001a8ba2000 /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBNNS.dylib -0x000000019e57d000 /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libSparse.dylib -0x000000024776c000 /System/Library/PrivateFrameworks/MIL.framework/Versions/A/MIL -0x00000001aa45b000 /usr/lib/libiconv.2.dylib -0x00000001aa3a7000 /usr/lib/libcharset.1.dylib -0x00000001a60f2000 /System/Library/Frameworks/OpenDirectory.framework/Versions/A/Frameworks/CFOpenDirectory.framework/Versions/A/CFOpenDirectory -0x00000001a60e2000 /System/Library/Frameworks/OpenDirectory.framework/Versions/A/OpenDirectory -0x00000001ab89b000 /System/Library/PrivateFrameworks/APFS.framework/Versions/A/APFS -0x00000001a97cc000 /System/Library/Frameworks/SecurityFoundation.framework/Versions/A/SecurityFoundation -0x00000001ab91a000 /usr/lib/libutil.dylib -0x00000002458f1000 /System/Library/PrivateFrameworks/InstalledContentLibrary.framework/Versions/A/InstalledContentLibrary -0x00000001a0f1d000 /System/Library/PrivateFrameworks/CoreServicesStore.framework/Versions/A/CoreServicesStore -0x000000023581f000 /System/Library/PrivateFrameworks/AppleMobileFileIntegrity.framework/Versions/A/AppleMobileFileIntegrity -0x00000001c30b9000 /usr/lib/libmis.dylib -0x00000001d3742000 /System/Library/PrivateFrameworks/MobileSystemServices.framework/Versions/A/MobileSystemServices -0x00000001f11bb000 /System/Library/PrivateFrameworks/ConfigProfileHelper.framework/Versions/A/ConfigProfileHelper -0x00000001aa576000 /System/Library/PrivateFrameworks/AppleSauce.framework/Versions/A/AppleSauce -0x000000019f5c2000 /System/Library/PrivateFrameworks/LanguageModeling.framework/Versions/A/LanguageModeling -0x00000001ab91e000 /usr/lib/libxslt.1.dylib -0x00000001aa462000 /usr/lib/libcmph.dylib -0x00000001a9567000 /System/Library/PrivateFrameworks/CoreEmoji.framework/Versions/A/CoreEmoji -0x00000001a8b9c000 /System/Library/PrivateFrameworks/LinguisticData.framework/Versions/A/LinguisticData -0x000000019e48b000 /System/Library/PrivateFrameworks/Lexicon.framework/Versions/A/Lexicon -0x00000001a985a000 /System/Library/PrivateFrameworks/BackgroundTaskManagement.framework/Versions/A/BackgroundTaskManagement -0x0000000262a48000 /usr/lib/libTLE.dylib -0x00000001ac1e8000 /System/Library/PrivateFrameworks/AppleJPEG.framework/Versions/A/AppleJPEG -0x00000001abcde000 /usr/lib/libexpat.1.dylib -0x00000001ac810000 /System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libPng.dylib -0x00000001ac83b000 /System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libTIFF.dylib -0x00000001ac926000 /System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libGIF.dylib -0x00000001ac22e000 /System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libJP2.dylib -0x00000001ac8cb000 /System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libJPEG.dylib -0x00000001ac8c2000 /System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libRadiance.dylib -0x00000001a7e8f000 /System/Library/PrivateFrameworks/FontServices.framework/libFontParser.dylib -0x00000001a4834000 /System/Library/PrivateFrameworks/RunningBoardServices.framework/Versions/A/RunningBoardServices -0x00000001b839d000 /System/Library/PrivateFrameworks/IOSurfaceAccelerator.framework/Versions/A/IOSurfaceAccelerator -0x00000001ac31e000 /System/Library/PrivateFrameworks/WatchdogClient.framework/Versions/A/WatchdogClient -0x000000019f7a8000 /System/Library/Frameworks/CoreDisplay.framework/Versions/A/CoreDisplay -0x00000001a7d47000 /System/Library/Frameworks/CoreMedia.framework/Versions/A/CoreMedia -0x00000001a7ae7000 /System/Library/PrivateFrameworks/IOAccelerator.framework/Versions/A/IOAccelerator -0x00000001a6285000 /System/Library/Frameworks/CoreVideo.framework/Versions/A/CoreVideo -0x00000001aa5cb000 /System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/MetalPerformanceShaders -0x00000001ac361000 /System/Library/Frameworks/VideoToolbox.framework/Versions/A/VideoToolbox -0x00000001a6321000 /System/Library/PrivateFrameworks/UserManagement.framework/Versions/A/UserManagement -0x00000001a475b000 /System/Library/PrivateFrameworks/BaseBoard.framework/Versions/A/BaseBoard -0x00000001a8a71000 /System/Library/PrivateFrameworks/AggregateDictionary.framework/Versions/A/AggregateDictionary -0x0000000235659000 /System/Library/PrivateFrameworks/AppleKeyStore.framework/Versions/A/AppleKeyStore -0x00000001ac8bd000 /System/Library/PrivateFrameworks/GPUWrangler.framework/Versions/A/GPUWrangler -0x00000001ac89d000 /System/Library/PrivateFrameworks/IOPresentment.framework/Versions/A/IOPresentment -0x00000001ac8c5000 /System/Library/PrivateFrameworks/DSExternalDisplay.framework/Versions/A/DSExternalDisplay -0x00000002419dd000 /System/Library/PrivateFrameworks/GPUCompiler.framework/Versions/32023/Libraries/libllvm-flatbuffers.dylib -0x00000001f3da0000 /System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libCoreFSCache.dylib -0x000000023ebf9000 /System/Library/PrivateFrameworks/GPUCompiler.framework/Versions/32023/Libraries/libGPUCompilerUtils.dylib -0x00000001ac92c000 /System/Library/PrivateFrameworks/CMCaptureCore.framework/Versions/A/CMCaptureCore -0x00000001f8bed000 /System/Library/Frameworks/ExtensionFoundation.framework/Versions/A/ExtensionFoundation -0x00000001b3961000 /System/Library/PrivateFrameworks/CoreTime.framework/Versions/A/CoreTime -0x00000001abeb1000 /System/Library/PrivateFrameworks/AppServerSupport.framework/Versions/A/AppServerSupport -0x00000001ae443000 /System/Library/PrivateFrameworks/perfdata.framework/Versions/A/perfdata -0x000000019f8cc000 /System/Library/PrivateFrameworks/AudioToolboxCore.framework/Versions/A/AudioToolboxCore -0x00000001a7d1d000 /System/Library/PrivateFrameworks/caulk.framework/Versions/A/caulk -0x00000001adcb1000 /usr/lib/libAudioStatistics.dylib -0x00000001c23f6000 /System/Library/PrivateFrameworks/SystemPolicy.framework/Versions/A/SystemPolicy -0x00000001adf8a000 /usr/lib/libSMC.dylib -0x00000001b794b000 /System/Library/Frameworks/CoreMIDI.framework/Versions/A/CoreMIDI -0x00000001ac7d7000 /usr/lib/libAudioToolboxUtility.dylib -0x00000001bd867000 /System/Library/PrivateFrameworks/OSAServicesClient.framework/Versions/A/OSAServicesClient -0x00000001ae451000 /usr/lib/libperfcheck.dylib -0x00000001b3419000 /System/Library/PrivateFrameworks/BoardServices.framework/Versions/A/BoardServices -0x00000001abbc6000 /System/Library/PrivateFrameworks/PlugInKit.framework/Versions/A/PlugInKit -0x00000001a97be000 /System/Library/PrivateFrameworks/AssertionServices.framework/Versions/A/AssertionServices -0x00000001b6a65000 /System/Library/PrivateFrameworks/ASEProcessing.framework/Versions/A/ASEProcessing -0x00000001df625000 /System/Library/PrivateFrameworks/Symbolication.framework/Versions/A/Symbolication -0x000000024cee1000 /System/Library/PrivateFrameworks/PhotosensitivityProcessing.framework/Versions/A/PhotosensitivityProcessing -0x00000001abe62000 /System/Library/PrivateFrameworks/GraphVisualizer.framework/Versions/A/GraphVisualizer -0x00000001f3e02000 /System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGLU.dylib -0x00000001f3dc1000 /System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGFXShared.dylib -0x00000001f3f9a000 /System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGL.dylib -0x00000001f3dca000 /System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGLImage.dylib -0x00000001f3dbe000 /System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libCVMSPluginSupport.dylib -0x0000000262a00000 /usr/lib/libRosetta.dylib -0x00000001f3da7000 /System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libCoreVMClient.dylib -0x000000023cf93000 /System/Library/PrivateFrameworks/FontServices.framework/Versions/A/FontServices -0x00000001abe6e000 /System/Library/PrivateFrameworks/OTSVG.framework/Versions/A/OTSVG -0x00000001a5e26000 /System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ATS.framework/Versions/A/Resources/libFontRegistry.dylib -0x00000001abebc000 /System/Library/PrivateFrameworks/FontServices.framework/libhvf.dylib -0x000000023cf94000 /System/Library/PrivateFrameworks/FontServices.framework/libXTFontStaticRegistryData.dylib -0x00000001a89b8000 /System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSCore.framework/Versions/A/MPSCore -0x00000001a9edf000 /System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSImage.framework/Versions/A/MPSImage -0x00000001a9939000 /System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSNeuralNetwork.framework/Versions/A/MPSNeuralNetwork -0x00000001a9d84000 /System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSMatrix.framework/Versions/A/MPSMatrix -0x00000001a9b8f000 /System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSRayIntersector.framework/Versions/A/MPSRayIntersector -0x00000001a9db6000 /System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSNDArray.framework/Versions/A/MPSNDArray -0x00000001fa432000 /System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSFunctions.framework/Versions/A/MPSFunctions -0x00000001fa417000 /System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSBenchmarkLoop.framework/Versions/A/MPSBenchmarkLoop -0x000000019dfa3000 /System/Library/PrivateFrameworks/MetalTools.framework/Versions/A/MetalTools -0x00000001c757b000 /System/Library/PrivateFrameworks/IOAccelMemoryInfo.framework/Versions/A/IOAccelMemoryInfo -0x00000001d363c000 /System/Library/PrivateFrameworks/kperf.framework/Versions/A/kperf -0x00000001c30de000 /System/Library/PrivateFrameworks/GPURawCounter.framework/Versions/A/GPURawCounter -0x00000001b3833000 /System/Library/PrivateFrameworks/CoreSymbolication.framework/Versions/A/CoreSymbolication -0x00000001c308a000 /System/Library/PrivateFrameworks/MallocStackLogging.framework/Versions/A/MallocStackLogging -0x00000001aba68000 /System/Library/PrivateFrameworks/CrashReporterSupport.framework/Versions/A/CrashReporterSupport -0x00000001b37ee000 /System/Library/PrivateFrameworks/DebugSymbols.framework/Versions/A/DebugSymbols -0x00000001d1fac000 /System/Library/PrivateFrameworks/OSAnalytics.framework/Versions/A/OSAnalytics -0x0000000259c57000 /System/Library/PrivateFrameworks/VideoToolboxParavirtualizationSupport.framework/Versions/A/VideoToolboxParavirtualizationSupport -0x00000001abc92000 /System/Library/PrivateFrameworks/AppleVA.framework/Versions/A/AppleVA -0x00000001adcf9000 /System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ATS.framework/Versions/A/ATS -0x00000001a3c93000 /System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/HIServices.framework/Versions/A/HIServices -0x00000001ac93a000 /System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/PrintCore.framework/Versions/A/PrintCore -0x00000001ae0e3000 /System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/QD.framework/Versions/A/QD -0x00000001ae0d7000 /System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ColorSyncLegacy.framework/Versions/A/ColorSyncLegacy -0x00000001adcc9000 /System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/SpeechSynthesis.framework/Versions/A/SpeechSynthesis -0x00000001ac8f6000 /System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ATSUI.framework/Versions/A/ATSUI -0x00000001ae06a000 /usr/lib/libcups.2.dylib -0x00000001ae45f000 /System/Library/Frameworks/Kerberos.framework/Versions/A/Kerberos -0x00000001ae470000 /System/Library/Frameworks/GSS.framework/Versions/A/GSS -0x00000001add78000 /usr/lib/libresolv.9.dylib -0x00000001abecd000 /System/Library/PrivateFrameworks/Heimdal.framework/Versions/A/Heimdal -0x00000001b58d6000 /System/Library/Frameworks/Kerberos.framework/Versions/A/Libraries/libHeimdalProxy.dylib -0x00000001ae4ca000 /System/Library/PrivateFrameworks/CommonAuth.framework/Versions/A/CommonAuth -0x00000001f79da000 /System/Library/Frameworks/AVFAudio.framework/Versions/A/AVFAudio -0x00000001bd8b6000 /System/Library/PrivateFrameworks/AXCoreUtilities.framework/Versions/A/AXCoreUtilities -0x0000000246e64000 /System/Library/PrivateFrameworks/IsolatedCoreAudioClient.framework/Versions/A/IsolatedCoreAudioClient -0x00000001adc35000 /System/Library/PrivateFrameworks/AudioSession.framework/Versions/A/AudioSession -0x00000001af733000 /System/Library/Frameworks/IOBluetooth.framework/Versions/A/IOBluetooth -0x00000001abd9d000 /System/Library/PrivateFrameworks/MediaExperience.framework/Versions/A/MediaExperience -0x00000001ada74000 /System/Library/PrivateFrameworks/AudioSession.framework/libSessionUtility.dylib -0x00000001ae0ef000 /System/Library/PrivateFrameworks/AudioResourceArbitration.framework/Versions/A/AudioResourceArbitration -0x00000001b27a9000 /System/Library/PrivateFrameworks/PowerLog.framework/Versions/A/PowerLog -0x00000001b26c8000 /System/Library/Frameworks/CoreBluetooth.framework/Versions/A/CoreBluetooth -0x00000001b58d7000 /System/Library/Frameworks/AudioUnit.framework/Versions/A/AudioUnit -0x00000001a9628000 /System/Library/PrivateFrameworks/CoreUtils.framework/Versions/A/CoreUtils -0x000000023b2db000 /System/Library/PrivateFrameworks/CoreUtilsExtras.framework/Versions/A/CoreUtilsExtras -0x000000024577f000 /System/Library/PrivateFrameworks/IO80211.framework/Versions/A/IO80211 -0x00000001b335d000 /System/Library/PrivateFrameworks/FrontBoardServices.framework/Versions/A/FrontBoardServices -0x00000001b4ea1000 /System/Library/PrivateFrameworks/BackBoardServices.framework/Versions/A/BackBoardServices -0x00000001abd04000 /System/Library/PrivateFrameworks/IconFoundation.framework/Versions/A/IconFoundation -0x00000001b16ac000 /System/Library/PrivateFrameworks/SpeechRecognitionCore.framework/Versions/A/SpeechRecognitionCore -0x00000001bd8d5000 /usr/lib/libAccessibility.dylib -0x00000001b1d9f000 /System/Library/Frameworks/MediaAccessibility.framework/Versions/A/MediaAccessibility -0x00000001e2cb6000 /System/Library/Frameworks/OSLog.framework/Versions/A/OSLog -0x00000001c3018000 /System/Library/PrivateFrameworks/LoggingSupport.framework/Versions/A/LoggingSupport -0x0000000103a34000 /Users/sungmin/Library/Java/JavaVirtualMachines/corretto-17.0.8.1-1/Contents/Home/lib/server/libjvm.dylib -0x0000000102528000 /Users/sungmin/Library/Java/JavaVirtualMachines/corretto-17.0.8.1-1/Contents/Home/lib/libjimage.dylib -0x0000000102558000 /Users/sungmin/Library/Java/JavaVirtualMachines/corretto-17.0.8.1-1/Contents/Home/lib/libinstrument.dylib -0x00000001025a8000 /Users/sungmin/Library/Java/JavaVirtualMachines/corretto-17.0.8.1-1/Contents/Home/lib/libjava.dylib -0x00000001026e4000 /private/var/folders/c3/rqkh7s3n2c1gdz85gpftmpjr0000gn/T/libasyncProfiler.so -0x00000001026a4000 /Users/sungmin/Library/Java/JavaVirtualMachines/corretto-17.0.8.1-1/Contents/Home/lib/libzip.dylib -0x000000010276c000 /Users/sungmin/Library/Java/JavaVirtualMachines/corretto-17.0.8.1-1/Contents/Home/lib/libnio.dylib -0x000000010278c000 /Users/sungmin/Library/Java/JavaVirtualMachines/corretto-17.0.8.1-1/Contents/Home/lib/libnet.dylib -0x0000000102588000 /Users/sungmin/Library/Java/JavaVirtualMachines/corretto-17.0.8.1-1/Contents/Home/lib/libextnet.dylib -0x00000001026cc000 /Users/sungmin/Library/Java/JavaVirtualMachines/corretto-17.0.8.1-1/Contents/Home/lib/libmanagement.dylib -0x00000001027b0000 /Users/sungmin/Library/Java/JavaVirtualMachines/corretto-17.0.8.1-1/Contents/Home/lib/libmanagement_ext.dylib -0x00000001ad409000 /usr/lib/libusrtcp.dylib -0x00000001a29fb000 /usr/lib/libboringssl.dylib - - -VM Arguments: -jvm_args: -XX:TieredStopAtLevel=1 -Dspring.output.ansi.enabled=always -Dcom.sun.management.jmxremote -Dspring.jmx.enabled=true -Dspring.liveBeansView.mbeanDomain -Dspring.application.admin.enabled=true -Dmanagement.endpoints.jmx.exposure.include=* -javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=57802:/Applications/IntelliJ IDEA.app/Contents/bin -agentpath:/private/var/folders/c3/rqkh7s3n2c1gdz85gpftmpjr0000gn/T/libasyncProfiler.so=start,jfr,event=cpu,interval=10ms,jfrsync=profile,cstack=no,file=/Users/sungmin/IdeaSnapshots/ComNCheckApplication_2025_01_26_233554.jfr,log=/private/var/folders/c3/rqkh7s3n2c1gdz85gpftmpjr0000gn/T/ComNCheckApplication_2025_01_26_233554.jfr.log.txt,logLevel=DEBUG -Dfile.encoding=UTF-8 -java_command: com.ComNCheck.ComNCheck.ComNCheckApplication -java_class_path (initial): /Users/sungmin/Desktop/ComNCheck/Spring/ComNCheck-backend/build/classes/java/main:/Users/sungmin/Desktop/ComNCheck/Spring/ComNCheck-backend/build/resources/main:/Users/sungmin/.gradle/caches/modules-2/files-2.1/org.projectlombok/lombok/1.18.36/5a30490a6e14977d97d9c73c924c1f1b5311ea95/lombok-1.18.36.jar:/Users/sungmin/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter-oauth2-client/3.4.1/9d39fbd91d9231bf0c9d3259e5152b9bda733f1a/spring-boot-starter-oauth2-client-3.4.1.jar:/Users/sungmin/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter-security/3.4.1/6a82a9f484d265c73a203d551b614cd8bdde5825/spring-boot-starter-security-3.4.1.jar:/Users/sungmin/.gradle/caches/modules-2/files-2.1/io.jsonwebtoken/jjwt-impl/0.12.3/e850d2b3f53bd82355cd9ee1c471054aa602b320/jjwt-impl-0.12.3.jar:/Users/sungmin/.gradle/caches/modules-2/files-2.1/io.jsonwebtoken/jjwt-jackson/0.12.3/5e6d0e45441547d892d3273a4ce5dd042e91d162/jjwt-jackson-0.12.3.jar:/Users/sungmin/.gradle/caches/modules-2/files-2.1/io.jsonwebtoken/jjwt-api/0.12.3/30b7de9176d17fa347eef14b85480825dab76b58/jjwt-api-0.12.3.jar:/Users/sungmin/.gradle/caches/modules-2/files-2.1/io.github.cdimascio/java-dotenv/5.2.2/f77d54ff193ed4b07415ab8d7b3d0550716aa8c/java-dotenv-5.2.2.jar:/Users/sungmin/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter-data-jpa/3.4.1/f06be4354c339f3f880a5c66a6913cd2366eb225/spring-boot-starter-data-jpa-3.4.1.jar:/Users/sungmin/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter-web/3.4.1/ff7227fc62338e0f6eba3f9f94c12eb952d4da95/spring-boot-starter-web-3.4.1.jar:/Users/sungmin/.gradle/caches/modules-2/files-2.1/org.springdoc/springdoc-openapi-starter-webmvc-ui/2.7.0/4426174e7fa7428a1c5f2edbd352c89e3e6b9794/springdoc-openapi-starter-webmvc-ui-2.7.0.jar:/Users/sungmin/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter/3.4.1/2c97b6fdc451ea69cd04dcfa -Launcher Type: SUN_STANDARD - -[Global flags] - intx CICompilerCount = 4 {product} {ergonomic} - uint ConcGCThreads = 2 {product} {ergonomic} - bool FlightRecorder = true {product} {management} - uint G1ConcRefinementThreads = 8 {product} {ergonomic} - size_t G1HeapRegionSize = 1048576 {product} {ergonomic} - uintx GCDrainStackTargetSize = 64 {product} {ergonomic} - size_t InitialHeapSize = 134217728 {product} {ergonomic} - bool ManagementServer = true {product} {command line} - size_t MarkStackSize = 4194304 {product} {ergonomic} - size_t MaxHeapSize = 2147483648 {product} {ergonomic} - size_t MaxNewSize = 1287651328 {product} {ergonomic} - size_t MinHeapDeltaBytes = 1048576 {product} {ergonomic} - size_t MinHeapSize = 8388608 {product} {ergonomic} - uintx NonProfiledCodeHeapSize = 0 {pd product} {ergonomic} - bool ProfileInterpreter = false {pd product} {command line} - uintx ProfiledCodeHeapSize = 0 {pd product} {ergonomic} - size_t SoftMaxHeapSize = 2147483648 {manageable} {ergonomic} - intx TieredStopAtLevel = 1 {product} {command line} - bool UseCompressedClassPointers = true {product lp64_product} {ergonomic} - bool UseCompressedOops = true {product lp64_product} {ergonomic} - bool UseG1GC = true {product} {ergonomic} - bool UseNUMA = false {product} {ergonomic} - bool UseNUMAInterleaving = false {product} {ergonomic} - -Logging: -Log output configuration: - #0: stdout all=warning,jni+resolve=error uptime,level,tags (reconfigured) - #1: stderr all=off uptime,level,tags - -Environment Variables: -PATH=/Users/sungmin/opt/anaconda3/bin:/Library/Frameworks/Python.framework/Versions/3.9/bin:/Library/Frameworks/Python.framework/Versions/3.11/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin -SHELL=/bin/zsh -LC_CTYPE=ko_KR.UTF-8 -TMPDIR=/var/folders/c3/rqkh7s3n2c1gdz85gpftmpjr0000gn/T/ - -Active Locale: -LC_ALL=C/ko_KR.UTF-8/C/C/C/C -LC_COLLATE=C -LC_CTYPE=ko_KR.UTF-8 -LC_MESSAGES=C -LC_MONETARY=C -LC_NUMERIC=C -LC_TIME=C - -Signal Handlers: - SIGSEGV: crash_handler in libjvm.dylib, mask=11100110000111110111111111111111, flags=SA_RESTART|SA_SIGINFO, unblocked - SIGBUS: crash_handler in libjvm.dylib, mask=11100110000111110111111111111111, flags=SA_RESTART|SA_SIGINFO, unblocked - SIGFPE: crash_handler in libjvm.dylib, mask=11100110000111110111111111111111, flags=SA_RESTART|SA_SIGINFO, unblocked - SIGPIPE: javaSignalHandler in libjvm.dylib, mask=11100110000111110111111111111111, flags=SA_RESTART|SA_SIGINFO, blocked - SIGXFSZ: javaSignalHandler in libjvm.dylib, mask=11100110000111110111111111111111, flags=SA_RESTART|SA_SIGINFO, blocked - SIGILL: crash_handler in libjvm.dylib, mask=11100110000111110111111111111111, flags=SA_RESTART|SA_SIGINFO, unblocked - SIGUSR2: SR_handler in libjvm.dylib, mask=00100000000000000000000000000000, flags=SA_RESTART|SA_SIGINFO, blocked - SIGHUP: UserHandler in libjvm.dylib, mask=11100110000111110111111111111111, flags=SA_RESTART|SA_SIGINFO, blocked - SIGINT: UserHandler in libjvm.dylib, mask=11100110000111110111111111111111, flags=SA_RESTART|SA_SIGINFO, blocked - SIGTERM: UserHandler in libjvm.dylib, mask=11100110000111110111111111111111, flags=SA_RESTART|SA_SIGINFO, blocked - SIGQUIT: UserHandler in libjvm.dylib, mask=11100110000111110111111111111111, flags=SA_RESTART|SA_SIGINFO, blocked - SIGTRAP: crash_handler in libjvm.dylib, mask=11100110000111110111111111111111, flags=SA_RESTART|SA_SIGINFO, unblocked - - ---------------- S Y S T E M --------------- - -OS: -uname: Darwin 23.4.0 Darwin Kernel Version 23.4.0: Fri Mar 15 00:12:41 PDT 2024; root:xnu-10063.101.17~1/RELEASE_ARM64_T8103 arm64 -OS uptime: 3 days 23:18 hours -rlimit (soft/hard): STACK 8176k/65520k , CORE 0k/infinity , NPROC 1333/2000 , NOFILE 10240/infinity , AS infinity/infinity , CPU infinity/infinity , DATA infinity/infinity , FSIZE infinity/infinity , MEMLOCK infinity/infinity , RSS infinity/infinity -load average: 4.29 3.02 3.73 - -CPU: total 8 (initial active 8) 0x61:0x0:0x1b588bb3:0, fp, simd, crc, lse -machdep.cpu.brand_string:Apple M1 -hw.cachelinesize:128 -hw.l1icachesize:131072 -hw.l1dcachesize:65536 -hw.l2cachesize:4194304 - -Memory: 16k page, physical 8388608k(149104k free), swap 9437184k(1607680k free) - -vm_info: OpenJDK 64-Bit Server VM (17.0.8.1+8-LTS) for bsd-aarch64 JRE (17.0.8.1+8-LTS), built on Aug 18 2023 22:52:47 by "ec2user" with clang Apple LLVM 13.0.0 (clang-1300.0.29.30) - -END. diff --git a/src/main/java/com/ComNCheck/ComNCheck/ComNCheckApplication.java b/src/main/java/com/ComNCheck/ComNCheck/ComNCheckApplication.java index 160ade7..f71d3c8 100644 --- a/src/main/java/com/ComNCheck/ComNCheck/ComNCheckApplication.java +++ b/src/main/java/com/ComNCheck/ComNCheck/ComNCheckApplication.java @@ -1,6 +1,5 @@ package com.ComNCheck.ComNCheck; -import io.github.cdimascio.dotenv.Dotenv; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @@ -9,16 +8,6 @@ public class ComNCheckApplication { public static void main(String[] args) { - //Load .env file - Dotenv dotenv = Dotenv.load(); - System.setProperty("H2_DB_URL", dotenv.get("H2_DB_URL")); - System.setProperty("DB_USERNAME", dotenv.get("DB_USERNAME")); - System.setProperty("DB_PASSWORD", dotenv.get("DB_PASSWORD")); - System.setProperty("JWT_SECRET", dotenv.get("JWT_SECRET")); - System.setProperty("GOOGLE_CLIENT_ID", dotenv.get("GOOGLE_CLIENT_ID")); - System.setProperty("GOOGLE_CLIENT_SECRET", dotenv.get("GOOGLE_CLIENT_SECRET")); - System.setProperty("JWT_EXPIRATIONMS",dotenv.get("JWT_EXPIRATIONMS")); - System.setProperty("GOOGLE_REDIRECT_URI",dotenv.get("GOOGLE_REDIRECT_URI")); SpringApplication.run(ComNCheckApplication.class, args); } diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/.DS_Store b/src/main/java/com/ComNCheck/ComNCheck/domain/.DS_Store new file mode 100644 index 0000000..3848306 Binary files /dev/null and b/src/main/java/com/ComNCheck/ComNCheck/domain/.DS_Store differ diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/anotherEvent/controller/AnotherEventController.java b/src/main/java/com/ComNCheck/ComNCheck/domain/anotherEvent/controller/AnotherEventController.java new file mode 100644 index 0000000..9ad8d03 --- /dev/null +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/anotherEvent/controller/AnotherEventController.java @@ -0,0 +1,90 @@ +package com.ComNCheck.ComNCheck.domain.anotherEvent.controller; + +import com.ComNCheck.ComNCheck.domain.anotherEvent.model.dto.request.EventCreateRequestDTO; +import com.ComNCheck.ComNCheck.domain.anotherEvent.model.dto.request.EventUpdateRequestDTO; +import com.ComNCheck.ComNCheck.domain.anotherEvent.model.dto.response.EventListResponseDTO; +import com.ComNCheck.ComNCheck.domain.anotherEvent.model.dto.response.EventResponseDTO; +import com.ComNCheck.ComNCheck.domain.anotherEvent.service.AnotherEventService; +import com.ComNCheck.ComNCheck.domain.security.oauth.CustomOAuth2Member; +import io.swagger.v3.oas.annotations.Operation; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.*; + +@RequestMapping("/api/v1/another-event") +@RequiredArgsConstructor +@RestController +public class AnotherEventController { + + private final AnotherEventService anotherEventService; + + @Operation( + summary = "AnotherEvent 게시글 작성", + description = "AnotherEvent 게시글을 작성한다. 관리자, 과회장, 학생회만 가능하다." + ) + @PostMapping + public ResponseEntity createAnotherEvent( + @ModelAttribute EventCreateRequestDTO requestDTO, + Authentication authentication + ) { + CustomOAuth2Member principal = (CustomOAuth2Member) authentication.getPrincipal(); + Long memberId = principal.getMemberDTO().getMemberId(); + EventResponseDTO responseDTO = anotherEventService.createAnotherEvent(requestDTO, memberId); + return ResponseEntity.ok(responseDTO); + } + + @Operation( + summary = "특정 AnotherEvent 게시글 조회", + description = "특정 AnotherEvent 게시글을 조회한다." + ) + @GetMapping("/{anotherEventId}") + public ResponseEntity getAnotherEvent( + @PathVariable Long anotherEventId + ) { + EventResponseDTO responseDTO = anotherEventService.getAnotherEvent(anotherEventId); + return ResponseEntity.ok(responseDTO); + } + + @Operation( + summary = "AnotherEvent 게시글 목록 조회", + description = "AnotherEvent 게시글 목록을 조회한다. 이미 지난 행사는 제외된다." + ) + @GetMapping + public ResponseEntity> getAllAnotherEventsNotPassed() { + List list = anotherEventService.getAllAnotherEventsNotPassed(); + return ResponseEntity.ok(list); + } + + @Operation( + summary = "AnotherEvent 게시글 수정", + description = "작성된 AnotherEvent 게시글을 수정한다. 관리자, 과회장, 학생회만 가능하다." + ) + @PutMapping("/{anotherEventId}") + public ResponseEntity updateAnotherEvent( + @PathVariable Long anotherEventId, + @ModelAttribute EventUpdateRequestDTO requestDTO, + Authentication authentication + ) { + CustomOAuth2Member principal = (CustomOAuth2Member) authentication.getPrincipal(); + Long memberId = principal.getMemberDTO().getMemberId(); + EventResponseDTO updateDTO = anotherEventService.updateAnotherEvent(anotherEventId, requestDTO, memberId); + return ResponseEntity.ok(updateDTO); + } + + @Operation( + summary = "AnotherEvent 게시글 삭제", + description = "작성된 AnotherEvent 게시글을 삭제한다. 관리자, 과회장, 학생회만 가능하다." + ) + @DeleteMapping("/{anotherEventId}") + public ResponseEntity deleteAnotherEvent( + @PathVariable Long anotherEventId, + Authentication authentication + ) { + CustomOAuth2Member principal = (CustomOAuth2Member) authentication.getPrincipal(); + Long memberId = principal.getMemberDTO().getMemberId(); + anotherEventService.deleteAnotherEvent(anotherEventId, memberId); + return ResponseEntity.noContent().build(); + } +} diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/anotherEvent/model/dto/request/EventCreateRequestDTO.java b/src/main/java/com/ComNCheck/ComNCheck/domain/anotherEvent/model/dto/request/EventCreateRequestDTO.java new file mode 100644 index 0000000..0b45482 --- /dev/null +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/anotherEvent/model/dto/request/EventCreateRequestDTO.java @@ -0,0 +1,31 @@ +package com.ComNCheck.ComNCheck.domain.anotherEvent.model.dto.request; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.List; +import lombok.Getter; +import lombok.Setter; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.web.multipart.MultipartFile; + +@Getter +@Setter +public class EventCreateRequestDTO { + + private String eventName; + private String date; + private String time; + private String location; + private String notice; + private String googleFormLink; + + private List cardNewsImages; + + public LocalDate getParsedDate() { + return date != null ? LocalDate.parse(date) : null; + } + + public LocalTime getParsedTime() { + return time != null ? LocalTime.parse(time) : null; + } +} diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/anotherEvent/model/dto/request/EventUpdateRequestDTO.java b/src/main/java/com/ComNCheck/ComNCheck/domain/anotherEvent/model/dto/request/EventUpdateRequestDTO.java new file mode 100644 index 0000000..c2e261c --- /dev/null +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/anotherEvent/model/dto/request/EventUpdateRequestDTO.java @@ -0,0 +1,33 @@ +package com.ComNCheck.ComNCheck.domain.anotherEvent.model.dto.request; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.web.multipart.MultipartFile; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.List; + +@Getter +@Setter +public class EventUpdateRequestDTO { + + private String eventName; + private String date; + private String time; + private String location; + private String notice; + private String googleFormLink; + private String firstImageUrl; + + private List cardNewsImages; + + public LocalDate getParsedDate() { + return date != null ? LocalDate.parse(date) : null; + } + + public LocalTime getParsedTime() { + return time != null ? LocalTime.parse(time) : null; + } +} diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/anotherEvent/model/dto/response/EventListResponseDTO.java b/src/main/java/com/ComNCheck/ComNCheck/domain/anotherEvent/model/dto/response/EventListResponseDTO.java new file mode 100644 index 0000000..7b7d9a5 --- /dev/null +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/anotherEvent/model/dto/response/EventListResponseDTO.java @@ -0,0 +1,29 @@ +package com.ComNCheck.ComNCheck.domain.anotherEvent.model.dto.response; + +import com.ComNCheck.ComNCheck.domain.anotherEvent.model.entity.AnotherEvent; +import lombok.Builder; +import lombok.Getter; + +import java.time.LocalDate; +import java.time.LocalTime; + +@Getter +@Builder +public class EventListResponseDTO { + + private Long id; + private String eventName; + private LocalDate date; + private LocalTime time; + private String location; + + public static EventListResponseDTO of(AnotherEvent anotherEvent) { + return EventListResponseDTO.builder() + .id(anotherEvent.getAnotherEventId()) + .eventName(anotherEvent.getEventName()) + .date(anotherEvent.getDate()) + .time(anotherEvent.getTime()) + .location(anotherEvent.getLocation()) + .build(); + } +} diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/anotherEvent/model/dto/response/EventResponseDTO.java b/src/main/java/com/ComNCheck/ComNCheck/domain/anotherEvent/model/dto/response/EventResponseDTO.java new file mode 100644 index 0000000..7ae08f7 --- /dev/null +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/anotherEvent/model/dto/response/EventResponseDTO.java @@ -0,0 +1,36 @@ +package com.ComNCheck.ComNCheck.domain.anotherEvent.model.dto.response; + +import com.ComNCheck.ComNCheck.domain.anotherEvent.model.entity.AnotherEvent; +import lombok.Builder; +import lombok.Getter; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.List; + +@Getter +@Builder +public class EventResponseDTO { + + private Long id; + private String eventName; + private LocalDate date; + private LocalTime time; + private String location; + private String notice; + private String googleFormLink; + private List cardNewsImageUrls; + + public static EventResponseDTO of(AnotherEvent anotherEvent) { + return EventResponseDTO.builder() + .id(anotherEvent.getAnotherEventId()) + .eventName(anotherEvent.getEventName()) + .date(anotherEvent.getDate()) + .time(anotherEvent.getTime()) + .location(anotherEvent.getLocation()) + .notice(anotherEvent.getNotice()) + .googleFormLink(anotherEvent.getGoogleFormLink()) + .cardNewsImageUrls(anotherEvent.getCardNewsImageUrls()) + .build(); + } +} diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/anotherEvent/model/dto/response/PagedEventListResponseDTO.java b/src/main/java/com/ComNCheck/ComNCheck/domain/anotherEvent/model/dto/response/PagedEventListResponseDTO.java new file mode 100644 index 0000000..7b97da6 --- /dev/null +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/anotherEvent/model/dto/response/PagedEventListResponseDTO.java @@ -0,0 +1,25 @@ +package com.ComNCheck.ComNCheck.domain.anotherEvent.model.dto.response; + +import java.util.List; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class PagedEventListResponseDTO { + private int currentPage; + private int totalPages; + private long totalElements; + private int size; + private List content; + + public PagedEventListResponseDTO(int currentPage, int totalPages, long totalElements, + int size, List content) + { + this.currentPage = currentPage; + this.totalPages = totalPages; + this.totalElements = totalElements; + this.size = size; + this.content = content; + } +} diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/anotherEvent/model/entity/AnotherEvent.java b/src/main/java/com/ComNCheck/ComNCheck/domain/anotherEvent/model/entity/AnotherEvent.java new file mode 100644 index 0000000..a970c3b --- /dev/null +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/anotherEvent/model/entity/AnotherEvent.java @@ -0,0 +1,90 @@ +package com.ComNCheck.ComNCheck.domain.anotherEvent.model.entity; + +import com.ComNCheck.ComNCheck.domain.member.model.entity.Member; +import jakarta.persistence.CollectionTable; +import jakarta.persistence.Column; +import jakarta.persistence.ElementCollection; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.ArrayList; +import java.util.List; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Entity +public class AnotherEvent { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long anotherEventId; + + @Column(name = "event_name", nullable = false) + private String eventName; + + @Column(name = "date", nullable = false) + private LocalDate date; + + @Column(name = "time", nullable = false) + private LocalTime time; + + @Column(name = "location", nullable = false) + private String location; + + @Column(name = "notice", nullable = false, columnDefinition = "TEXT") + private String notice; + + @Column(name = "google_form_link") + private String googleFormLink; + + @ElementCollection(fetch = FetchType.LAZY) + @CollectionTable(name = "another_event_card_news_images", joinColumns = @JoinColumn(name = "another_event_id")) + @Column(name = "image_url") + private List cardNewsImageUrls = new ArrayList<>(); + + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn(name = "writer_id") + private Member writer; + + @Builder + public AnotherEvent(Member writer, String eventName, LocalDate date, LocalTime time, + String location, String notice, String googleFormLink, + List cardNewsImageUrls) { + this.writer = writer; + this.eventName = eventName; + this.date = date; + this.time = time; + this.location = location; + this.notice = notice; + this.googleFormLink = googleFormLink; + if (cardNewsImageUrls != null) { + this.cardNewsImageUrls = cardNewsImageUrls; + } + } + + public void updateEvent(String eventName, LocalDate date, LocalTime time, + String location, String notice, String googleFormLink) { + this.eventName = eventName; + this.date = date; + this.time = time; + this.location = location; + this.notice = notice; + this.googleFormLink = googleFormLink; + } + + public void updateCardNewsImages(List newImageUrls) { + this.cardNewsImageUrls.clear(); + this.cardNewsImageUrls.addAll(newImageUrls); + } +} diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/anotherEvent/repository/AnotherEventRepository.java b/src/main/java/com/ComNCheck/ComNCheck/domain/anotherEvent/repository/AnotherEventRepository.java new file mode 100644 index 0000000..5dfe0b3 --- /dev/null +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/anotherEvent/repository/AnotherEventRepository.java @@ -0,0 +1,7 @@ +package com.ComNCheck.ComNCheck.domain.anotherEvent.repository; + +import com.ComNCheck.ComNCheck.domain.anotherEvent.model.entity.AnotherEvent; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface AnotherEventRepository extends JpaRepository { +} diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/anotherEvent/service/AnotherEventService.java b/src/main/java/com/ComNCheck/ComNCheck/domain/anotherEvent/service/AnotherEventService.java new file mode 100644 index 0000000..7b78413 --- /dev/null +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/anotherEvent/service/AnotherEventService.java @@ -0,0 +1,178 @@ +package com.ComNCheck.ComNCheck.domain.anotherEvent.service; + +import com.ComNCheck.ComNCheck.domain.fcm.service.FcmService; +import com.ComNCheck.ComNCheck.domain.global.exception.ForbiddenException; +import com.ComNCheck.ComNCheck.domain.global.exception.MemberNotFoundException; +import com.ComNCheck.ComNCheck.domain.global.exception.PostNotFoundException; +import com.ComNCheck.ComNCheck.domain.anotherEvent.model.dto.request.EventCreateRequestDTO; +import com.ComNCheck.ComNCheck.domain.anotherEvent.model.dto.request.EventUpdateRequestDTO; +import com.ComNCheck.ComNCheck.domain.anotherEvent.model.dto.response.EventListResponseDTO; +import com.ComNCheck.ComNCheck.domain.anotherEvent.model.dto.response.EventResponseDTO; +import com.ComNCheck.ComNCheck.domain.anotherEvent.model.entity.AnotherEvent; +import com.ComNCheck.ComNCheck.domain.anotherEvent.repository.AnotherEventRepository; +import com.ComNCheck.ComNCheck.domain.member.model.entity.Member; +import com.ComNCheck.ComNCheck.domain.member.model.entity.Role; +import com.ComNCheck.ComNCheck.domain.member.repository.MemberRepository; +import com.google.cloud.storage.BlobInfo; +import com.google.cloud.storage.Storage; +import com.google.firebase.messaging.FirebaseMessagingException; +import java.io.IOException; +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +@RequiredArgsConstructor +@Transactional(readOnly = true) +@Service +public class AnotherEventService { + + private final AnotherEventRepository anotherEventRepository; + private final MemberRepository memberRepository; + private final FcmService fcmService; + + @Value("${spring.cloud.gcp.storage.bucket}") + private String bucketName; + + private final Storage storage; + + @Transactional + public EventResponseDTO createAnotherEvent(EventCreateRequestDTO requestDTO, Long writerId) { + Member writer = memberRepository.findByMemberId(writerId) + .orElseThrow(() -> new MemberNotFoundException("존재하지 않는 회원입니다.")); + + isCheckRole(writer); + + List imageUrls = uploadImagesToGcs(requestDTO.getCardNewsImages()); + + LocalDate eventDate = requestDTO.getParsedDate(); + LocalTime eventTime = requestDTO.getParsedTime(); + AnotherEvent anotherEvent = AnotherEvent.builder() + .writer(writer) + .eventName(requestDTO.getEventName()) + .date(eventDate) + .time(eventTime) + .location(requestDTO.getLocation()) + .notice(requestDTO.getNotice()) + .googleFormLink(requestDTO.getGoogleFormLink()) + .cardNewsImageUrls(imageUrls) + .build(); + AnotherEvent savedAnotherEvent = anotherEventRepository.save(anotherEvent); + + String title = "새로운 행사 등록"; + String body = requestDTO.getEventName() + " 행사가 등록되었습니다."; + memberRepository.findAll().forEach(member -> { + member.getFcmTokens().forEach(fcmToken -> { + if (fcmToken.isValid() && fcmToken.getToken() != null && !fcmToken.getToken().isBlank()) { + try { + fcmService.sendMessageToToken(fcmToken.getToken(), title, body); + } catch (FirebaseMessagingException e) { + System.out.println("전송 실패"); + } + } + }); + }); + + return EventResponseDTO.of(savedAnotherEvent); + } + + @Transactional + public void deleteAnotherEvent(Long anotherEventId, Long memberId) { + Member member = memberRepository.findByMemberId(memberId) + .orElseThrow(() -> new MemberNotFoundException("등록된 회원이 없습니다.")); + isCheckRole(member); + + AnotherEvent anotherEvent = anotherEventRepository.findById(anotherEventId) + .orElseThrow(() -> new PostNotFoundException("요청하신 행사가 없습니다.")); + anotherEventRepository.delete(anotherEvent); + } + + public EventResponseDTO getAnotherEvent(Long anotherEventId) { + AnotherEvent anotherEvent = anotherEventRepository.findById(anotherEventId) + .orElseThrow(() -> new PostNotFoundException("요청하신 행사가 없습니다.")); + return EventResponseDTO.of(anotherEvent); + } + + public List getAllAnotherEventsNotPassed() { + List all = anotherEventRepository.findAll(); + + LocalDate today = LocalDate.now(); + LocalTime currentTime = LocalTime.now(); + + all.sort( + Comparator + .comparing(AnotherEvent::getDate, Comparator.reverseOrder()) + .thenComparing(AnotherEvent::getTime, Comparator.reverseOrder()) + ); + + return all.stream() + .map(EventListResponseDTO::of) + .collect(Collectors.toList()); + } + + @Transactional + public EventResponseDTO updateAnotherEvent(Long anotherEventId, EventUpdateRequestDTO requestDTO, Long memberId) { + Member member = memberRepository.findByMemberId(memberId) + .orElseThrow(() -> new MemberNotFoundException("등록된 회원이 없습니다.")); + isCheckRole(member); + + AnotherEvent anotherEvent = anotherEventRepository.findById(anotherEventId) + .orElseThrow(() -> new PostNotFoundException("요청하신 행사가 없습니다.")); + + LocalDate eventDate = requestDTO.getParsedDate(); + LocalTime eventTime = requestDTO.getParsedTime(); + + anotherEvent.updateEvent( + requestDTO.getEventName(), + eventDate, + eventTime, + requestDTO.getLocation(), + requestDTO.getNotice(), + requestDTO.getGoogleFormLink() + ); + if (requestDTO.getCardNewsImages() != null && !requestDTO.getCardNewsImages().isEmpty()) { + List newImageUrls = uploadImagesToGcs(requestDTO.getCardNewsImages()); + anotherEvent.updateCardNewsImages(newImageUrls); + } + + return EventResponseDTO.of(anotherEvent); + } + + private List uploadImagesToGcs(List images) { + if (images == null || images.isEmpty()) { + return new ArrayList<>(); + } + List uploadUrls = new ArrayList<>(); + for (MultipartFile file : images) { + try { + String uuid = UUID.randomUUID().toString(); + String contentType = file.getContentType() != null ? file.getContentType() : "application/octet-stream"; + BlobInfo blobInfo = BlobInfo.newBuilder(bucketName, uuid) + .setContentType(contentType) + .build(); + storage.create(blobInfo, file.getInputStream()); + uploadUrls.add("https://storage.googleapis.com/" + bucketName + "/" + uuid); + } catch (IOException e) { + throw new RuntimeException("이미지 업로드 실패", e); + } + } + return uploadUrls; + } + + public void isCheckRole(Member member) { + Role role = member.getRole(); + if (role != Role.ROLE_ADMIN + && role != Role.ROLE_MAJOR_PRESIDENT + && role != Role.ROLE_STUDENT_COUNCIL) { + throw new ForbiddenException("접근 권한이 없습니다."); + } + } +} diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/developerQuestion/.DS_Store b/src/main/java/com/ComNCheck/ComNCheck/domain/developerQuestion/.DS_Store new file mode 100644 index 0000000..112e67d Binary files /dev/null and b/src/main/java/com/ComNCheck/ComNCheck/domain/developerQuestion/.DS_Store differ diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/developerQuestion/controller/DeveloperQuestionController.java b/src/main/java/com/ComNCheck/ComNCheck/domain/developerQuestion/controller/DeveloperQuestionController.java index ebbba67..ee354f6 100644 --- a/src/main/java/com/ComNCheck/ComNCheck/domain/developerQuestion/controller/DeveloperQuestionController.java +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/developerQuestion/controller/DeveloperQuestionController.java @@ -5,6 +5,7 @@ import com.ComNCheck.ComNCheck.domain.developerQuestion.model.dto.response.DeveloperQuestionResponseDTO; import com.ComNCheck.ComNCheck.domain.developerQuestion.service.DeveloperQuestionService; import com.ComNCheck.ComNCheck.domain.security.oauth.CustomOAuth2Member; +import io.swagger.v3.oas.annotations.Operation; import java.net.URI; import java.util.List; import lombok.RequiredArgsConstructor; @@ -19,7 +20,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -@RequestMapping("api/v1/developer/question") +@RequestMapping("api/v1/developer/questions") @RequiredArgsConstructor @RestController public class DeveloperQuestionController { @@ -27,15 +28,21 @@ public class DeveloperQuestionController { private final DeveloperQuestionService developerQuestionService; @PostMapping + @Operation(summary = "개발자 질문 게시글 작성", description = "개발자에게 질문글을 작성할 수 있다.") public ResponseEntity createDeveloperQuestion( - @RequestBody DeveloperQuestionRequestDTO requestDTO + @RequestBody DeveloperQuestionRequestDTO requestDTO, + Authentication authentication ) { - DeveloperQuestionResponseDTO createdDTO = developerQuestionService.createDeveloperQuestion(requestDTO); - URI location = URI.create("api/v1/developer/question/" + createdDTO.getId()); + CustomOAuth2Member principal = (CustomOAuth2Member) authentication.getPrincipal(); + Long memberId = principal.getMemberDTO().getMemberId(); + DeveloperQuestionResponseDTO createdDTO = developerQuestionService + .createDeveloperQuestion(memberId, requestDTO); + URI location = URI.create("api/v1/developer/questions/" + createdDTO.getId()); return ResponseEntity.created(location).body(createdDTO); } @GetMapping("/{developerQuestionId}") + @Operation(summary = "특정 개발자 질문 게시글 조회", description = "특정 개발자 질문 게시글 조회한다.") public ResponseEntity getDeveloperQuestion( @PathVariable Long developerQuestionId ) { @@ -44,12 +51,14 @@ public ResponseEntity getDeveloperQuestion( } @GetMapping + @Operation(summary = "개발자 질문 게시글 목록 조회", description = "개발자 질문 게시글 목록 조회한다.") public ResponseEntity> getAllDeveloperQuestions() { List questionList = developerQuestionService.getAllQuestion(); return ResponseEntity.ok(questionList); } @PutMapping("/{developerQuestionId}") + @Operation(summary = "개발자 질문 게시글 수정", description = "개발자 질문 게시글을 수정한다.") public ResponseEntity updateDeveloperQuestion( @PathVariable Long developerQuestionId, @RequestBody DeveloperQuestionResponseDTO requestDTO, @@ -64,6 +73,7 @@ public ResponseEntity updateDeveloperQuestion( } @DeleteMapping("/{developerQuestionId}") + @Operation(summary = "개발자 질문 게시글 삭제", description = "개발자 질문 게시글을 삭제한다.") public ResponseEntity deleteDeveloperQuestion( @PathVariable Long developerQuestionId, Authentication authentication @@ -73,4 +83,15 @@ public ResponseEntity deleteDeveloperQuestion( developerQuestionService.deleteDeveloperQuestion(developerQuestionId, writerId); return ResponseEntity.noContent().build(); } + + @GetMapping("/my") + @Operation(summary = "내가 쓴 개발자 질문 게시글 보기", description = "내가 쓴 글만 조회한다.") + public ResponseEntity> getAllMyDeveloperQuestion( + Authentication authentication + ) { + CustomOAuth2Member principal = (CustomOAuth2Member) authentication.getPrincipal(); + Long writerId = principal.getMemberDTO().getMemberId(); + List developerQuestions = developerQuestionService.getAllMyDeveloperQuestion(writerId); + return ResponseEntity.ok(developerQuestions); + } } diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/developerQuestion/model/.DS_Store b/src/main/java/com/ComNCheck/ComNCheck/domain/developerQuestion/model/.DS_Store new file mode 100644 index 0000000..4d55781 Binary files /dev/null and b/src/main/java/com/ComNCheck/ComNCheck/domain/developerQuestion/model/.DS_Store differ diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/developerQuestion/model/dto/request/DeveloperQuestionRequestDTO.java b/src/main/java/com/ComNCheck/ComNCheck/domain/developerQuestion/model/dto/request/DeveloperQuestionRequestDTO.java index 980b2f3..1aea267 100644 --- a/src/main/java/com/ComNCheck/ComNCheck/domain/developerQuestion/model/dto/request/DeveloperQuestionRequestDTO.java +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/developerQuestion/model/dto/request/DeveloperQuestionRequestDTO.java @@ -8,5 +8,4 @@ @Setter public class DeveloperQuestionRequestDTO { private String content; - private Long writerId; } diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/developerQuestion/model/entity/DeveloperQuestion.java b/src/main/java/com/ComNCheck/ComNCheck/domain/developerQuestion/model/entity/DeveloperQuestion.java index f6d19fa..5091989 100644 --- a/src/main/java/com/ComNCheck/ComNCheck/domain/developerQuestion/model/entity/DeveloperQuestion.java +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/developerQuestion/model/entity/DeveloperQuestion.java @@ -10,6 +10,8 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; +import jakarta.persistence.PrePersist; +import java.time.LocalDateTime; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -32,9 +34,20 @@ public class DeveloperQuestion { @JoinColumn(name = "writer_id") private Member writer; + @Column + private LocalDateTime createdAt; + @Column + private LocalDateTime updatedAt; + public void updateDeveloperQuestion(String content) { this.content = content; } + @PrePersist + public void prePersist() { + this.createdAt = LocalDateTime.now(); + this.updatedAt = LocalDateTime.now(); + } + } diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/developerQuestion/repository/DeveloperQuestionRepository.java b/src/main/java/com/ComNCheck/ComNCheck/domain/developerQuestion/repository/DeveloperQuestionRepository.java index ca2ac92..cf2000a 100644 --- a/src/main/java/com/ComNCheck/ComNCheck/domain/developerQuestion/repository/DeveloperQuestionRepository.java +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/developerQuestion/repository/DeveloperQuestionRepository.java @@ -1,6 +1,8 @@ package com.ComNCheck.ComNCheck.domain.developerQuestion.repository; import com.ComNCheck.ComNCheck.domain.developerQuestion.model.entity.DeveloperQuestion; +import com.ComNCheck.ComNCheck.domain.member.model.entity.Member; +import java.util.List; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; @@ -9,6 +11,6 @@ public interface DeveloperQuestionRepository extends JpaRepository { //Optional findByDeveloperQuestionId(Long id); - + List findAllByWriter(Member writer); } diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/developerQuestion/service/DeveloperQuestionService.java b/src/main/java/com/ComNCheck/ComNCheck/domain/developerQuestion/service/DeveloperQuestionService.java index 81341d2..13b3ee4 100644 --- a/src/main/java/com/ComNCheck/ComNCheck/domain/developerQuestion/service/DeveloperQuestionService.java +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/developerQuestion/service/DeveloperQuestionService.java @@ -1,14 +1,15 @@ package com.ComNCheck.ComNCheck.domain.developerQuestion.service; -import com.ComNCheck.ComNCheck.domain.member.exception.MemberNotFoundException; +import com.ComNCheck.ComNCheck.domain.global.exception.ForbiddenException; +import com.ComNCheck.ComNCheck.domain.global.exception.MemberNotFoundException; +import com.ComNCheck.ComNCheck.domain.global.exception.PostNotFoundException; import com.ComNCheck.ComNCheck.domain.member.model.entity.Member; import com.ComNCheck.ComNCheck.domain.member.repository.MemberRepository; import com.ComNCheck.ComNCheck.domain.developerQuestion.model.dto.request.DeveloperQuestionRequestDTO; import com.ComNCheck.ComNCheck.domain.developerQuestion.model.dto.response.DeveloperQuestionResponseDTO; import com.ComNCheck.ComNCheck.domain.developerQuestion.model.entity.DeveloperQuestion; import com.ComNCheck.ComNCheck.domain.developerQuestion.repository.DeveloperQuestionRepository; -import com.ComNCheck.ComNCheck.domain.global.exception.UnauthorizedException; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -23,8 +24,8 @@ public class DeveloperQuestionService { private final DeveloperQuestionRepository developerQuestionRepository; @Transactional - public DeveloperQuestionResponseDTO createDeveloperQuestion(DeveloperQuestionRequestDTO requestDTO) { - Member writer = memberRepository.findById(requestDTO.getWriterId()) + public DeveloperQuestionResponseDTO createDeveloperQuestion(Long memberId, DeveloperQuestionRequestDTO requestDTO) { + Member writer = memberRepository.findById(memberId) .orElseThrow(() -> new MemberNotFoundException("회원이 존재하지 않습니다.")); DeveloperQuestion developerQuestion = DeveloperQuestion.builder() @@ -38,7 +39,7 @@ public DeveloperQuestionResponseDTO createDeveloperQuestion(DeveloperQuestionReq public DeveloperQuestionResponseDTO getDeveloperQuestion(Long developerQuestionId) { DeveloperQuestion developerQuestion = developerQuestionRepository.findById(developerQuestionId) - .orElseThrow(() -> new IllegalArgumentException("질문글을 찾을 수 없습니다.")); + .orElseThrow(() -> new PostNotFoundException("질문글을 찾을 수 없습니다.")); return DeveloperQuestionResponseDTO.of(developerQuestion); } public List getAllQuestion() { @@ -53,10 +54,10 @@ public DeveloperQuestionResponseDTO updateDeveloperQuestion(Long developerQuesti DeveloperQuestionResponseDTO requestDTO, Long writerId) { DeveloperQuestion developerQuestion = developerQuestionRepository.findById(developerQuestionId) - .orElseThrow(() -> new IllegalArgumentException("질문글을 찾을 수 없습니다.")); + .orElseThrow(() -> new PostNotFoundException("질문글을 찾을 수 없습니다.")); if(!developerQuestion.getWriter().getMemberId().equals(writerId)) - throw new UnauthorizedException("게시글 작성자가 아닙니다."); + throw new ForbiddenException("게시글 작성자가 아닙니다."); developerQuestion.updateDeveloperQuestion(requestDTO.getContent()); return DeveloperQuestionResponseDTO.of(developerQuestion); @@ -65,14 +66,24 @@ public DeveloperQuestionResponseDTO updateDeveloperQuestion(Long developerQuesti @Transactional public void deleteDeveloperQuestion(Long developerQuestionId, Long writerId) { DeveloperQuestion developerQuestion = developerQuestionRepository.findById(developerQuestionId) - .orElseThrow(() -> new IllegalArgumentException("질문글을 찾을 수 없습니다.")); + .orElseThrow(() -> new PostNotFoundException("질문글을 찾을 수 없습니다.")); if(!developerQuestion.getWriter().getMemberId().equals(writerId)) - throw new UnauthorizedException("게시글 작성자가 아닙니다."); + throw new ForbiddenException("게시글 작성자가 아닙니다."); developerQuestionRepository.delete(developerQuestion); } + public List getAllMyDeveloperQuestion(Long writerId) { + Member writer = memberRepository.findById(writerId) + .orElseThrow(() -> new MemberNotFoundException("회원이 존재하지 않습니다.")); + + return developerQuestionRepository.findAllByWriter(writer) + .stream() + .map(DeveloperQuestionResponseDTO::of) + .toList(); + } + diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/employmentNotice/.DS_Store b/src/main/java/com/ComNCheck/ComNCheck/domain/employmentNotice/.DS_Store new file mode 100644 index 0000000..7c31c49 Binary files /dev/null and b/src/main/java/com/ComNCheck/ComNCheck/domain/employmentNotice/.DS_Store differ diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/employmentNotice/config/scheduler/EmploymentNoticeScheduler.java b/src/main/java/com/ComNCheck/ComNCheck/domain/employmentNotice/config/scheduler/EmploymentNoticeScheduler.java new file mode 100644 index 0000000..afd7b41 --- /dev/null +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/employmentNotice/config/scheduler/EmploymentNoticeScheduler.java @@ -0,0 +1,27 @@ +package com.ComNCheck.ComNCheck.domain.employmentNotice.config.scheduler; + + +import com.ComNCheck.ComNCheck.domain.employmentNotice.service.EmploymentNoticeService; +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; + +@Configuration +@EnableScheduling +@RequiredArgsConstructor +public class EmploymentNoticeScheduler { + + private final EmploymentNoticeService employmentNoticeService; + + @Scheduled(cron = "0 0 * * * *") + public void syncEmploymentNoticePeriodically() { + employmentNoticeService.syncEmploymentNotices(); + } + + @PostConstruct + public void initLoad() { + employmentNoticeService.syncEmploymentNotices(); + } +} diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/employmentNotice/controller/EmploymentController.java b/src/main/java/com/ComNCheck/ComNCheck/domain/employmentNotice/controller/EmploymentController.java new file mode 100644 index 0000000..50b0417 --- /dev/null +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/employmentNotice/controller/EmploymentController.java @@ -0,0 +1,40 @@ +package com.ComNCheck.ComNCheck.domain.employmentNotice.controller; + +import com.ComNCheck.ComNCheck.domain.employmentNotice.model.dto.response.EmploymentNoticeResponseDTO; +import com.ComNCheck.ComNCheck.domain.employmentNotice.model.dto.response.PageEmploymentNoticeResponseDTO; +import com.ComNCheck.ComNCheck.domain.employmentNotice.service.EmploymentNoticeService; +import io.swagger.v3.oas.annotations.Operation; +import jakarta.xml.bind.annotation.XmlType.DEFAULT; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RequiredArgsConstructor +@RequestMapping("/api/v1/employment/notices") +@RestController +public class EmploymentController { + + private final EmploymentNoticeService employmentNoticeService; + + @GetMapping + @Operation(summary = "취업정보 게시판 목록 조회", description = "취업정보 게시판 목록을 조회한다.") + public ResponseEntity> getAllEmploymentNotice() { + List lists = employmentNoticeService.getAllEmploymentNotices(); + return ResponseEntity.ok(lists); + } + + @GetMapping("/pages") + @Operation(summary = "취업정보 게시판 목록 조회(페이지네이션)", description = "페이지네이션으로 취업정보 게시판 목록을 조회한다.") + public ResponseEntity getEmploymentNoticePage( + @RequestParam(defaultValue = "1") int page, + @RequestParam(defaultValue = "10") int size + ) { + PageEmploymentNoticeResponseDTO pageResponse = employmentNoticeService.getEmploymentNoticesPage(page, size); + return ResponseEntity.ok(pageResponse); + + } +} diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/employmentNotice/model/.DS_Store b/src/main/java/com/ComNCheck/ComNCheck/domain/employmentNotice/model/.DS_Store new file mode 100644 index 0000000..3f0a476 Binary files /dev/null and b/src/main/java/com/ComNCheck/ComNCheck/domain/employmentNotice/model/.DS_Store differ diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/employmentNotice/model/dto/response/EmploymentNoticeResponseDTO.java b/src/main/java/com/ComNCheck/ComNCheck/domain/employmentNotice/model/dto/response/EmploymentNoticeResponseDTO.java new file mode 100644 index 0000000..60d6da8 --- /dev/null +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/employmentNotice/model/dto/response/EmploymentNoticeResponseDTO.java @@ -0,0 +1,29 @@ +package com.ComNCheck.ComNCheck.domain.employmentNotice.model.dto.response; + +import com.ComNCheck.ComNCheck.domain.employmentNotice.model.entity.EmploymentNotice; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonFormat.Shape; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.time.LocalDate; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class EmploymentNoticeResponseDTO { + @JsonProperty("notice_id") + private int employmentNoticeId; + private String title; + @JsonFormat(shape = Shape.STRING, pattern = "yyyy.MM.dd") + private LocalDate date; + private String link; + + public static EmploymentNoticeResponseDTO of(EmploymentNotice employmentNotice) { + return EmploymentNoticeResponseDTO.builder() + .employmentNoticeId(employmentNotice.getEmploymentNoticeId()) + .title(employmentNotice.getTitle()) + .date(employmentNotice.getDate()) + .link(employmentNotice.getLink()) + .build(); + } +} diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/employmentNotice/model/dto/response/FastAPIEmploymentNoticeResponseListDTO.java b/src/main/java/com/ComNCheck/ComNCheck/domain/employmentNotice/model/dto/response/FastAPIEmploymentNoticeResponseListDTO.java new file mode 100644 index 0000000..ed475f9 --- /dev/null +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/employmentNotice/model/dto/response/FastAPIEmploymentNoticeResponseListDTO.java @@ -0,0 +1,12 @@ +package com.ComNCheck.ComNCheck.domain.employmentNotice.model.dto.response; + +import com.ComNCheck.ComNCheck.domain.majorNotice.model.dto.response.MajorNoticeResponseDTO; +import java.util.List; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class FastAPIEmploymentNoticeResponseListDTO { + private List notices; +} diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/employmentNotice/model/dto/response/PageEmploymentNoticeResponseDTO.java b/src/main/java/com/ComNCheck/ComNCheck/domain/employmentNotice/model/dto/response/PageEmploymentNoticeResponseDTO.java new file mode 100644 index 0000000..ba94486 --- /dev/null +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/employmentNotice/model/dto/response/PageEmploymentNoticeResponseDTO.java @@ -0,0 +1,16 @@ +package com.ComNCheck.ComNCheck.domain.employmentNotice.model.dto.response; + +import com.ComNCheck.ComNCheck.domain.majorNotice.model.dto.response.MajorNoticeResponseDTO; +import java.util.List; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class PageEmploymentNoticeResponseDTO { + private int currentPage; + private int totalPages; + private long totalElements; + private int size; + private List content; +} diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/employmentNotice/model/entity/EmploymentNotice.java b/src/main/java/com/ComNCheck/ComNCheck/domain/employmentNotice/model/entity/EmploymentNotice.java new file mode 100644 index 0000000..6a71558 --- /dev/null +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/employmentNotice/model/entity/EmploymentNotice.java @@ -0,0 +1,57 @@ +package com.ComNCheck.ComNCheck.domain.employmentNotice.model.entity; + +import com.ComNCheck.ComNCheck.domain.employmentNotice.model.dto.response.EmploymentNoticeResponseDTO; +import com.ComNCheck.ComNCheck.domain.majorNotice.model.dto.response.MajorNoticeResponseDTO; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import java.time.LocalDate; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + + +@Getter +@Table(name = "employment_notices") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Entity +public class EmploymentNotice { + @Id + @Column(name = "employment_notice_id") + private int employmentNoticeId; + + @Column + private String title; + + @Column + private LocalDate date; + + @Column + private String link; + + public EmploymentNotice(EmploymentNoticeResponseDTO dto) { + this.employmentNoticeId = dto.getEmploymentNoticeId(); + this.title = dto.getTitle(); + this.date = dto.getDate(); + this.link = dto.getLink(); + } + + public boolean updateFromDto(EmploymentNoticeResponseDTO dto) { + boolean changed = false; + + if (!this.title.equals(dto.getTitle())) { + this.title = dto.getTitle(); + changed = true; + } + if (!this.date.isEqual(dto.getDate())) { + this.date = dto.getDate(); + changed = true; + } + if (!this.link.equals(dto.getLink())) { + this.link = dto.getLink(); + changed = true; + } + return changed; + } +} diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/employmentNotice/repository/EmploymentNoticeRepository.java b/src/main/java/com/ComNCheck/ComNCheck/domain/employmentNotice/repository/EmploymentNoticeRepository.java new file mode 100644 index 0000000..1db1641 --- /dev/null +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/employmentNotice/repository/EmploymentNoticeRepository.java @@ -0,0 +1,14 @@ +package com.ComNCheck.ComNCheck.domain.employmentNotice.repository; + +import com.ComNCheck.ComNCheck.domain.employmentNotice.model.entity.EmploymentNotice; +import java.util.List; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +public interface EmploymentNoticeRepository extends JpaRepository { + Optional findByEmploymentNoticeId(int employmentNoticeId); + + @Query("SELECT e FROM EmploymentNotice e ORDER BY e.employmentNoticeId DESC") + List findAllOrderedById(); +} diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/employmentNotice/service/EmploymentNoticeService.java b/src/main/java/com/ComNCheck/ComNCheck/domain/employmentNotice/service/EmploymentNoticeService.java new file mode 100644 index 0000000..436ef44 --- /dev/null +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/employmentNotice/service/EmploymentNoticeService.java @@ -0,0 +1,122 @@ +package com.ComNCheck.ComNCheck.domain.employmentNotice.service; + +import com.ComNCheck.ComNCheck.domain.employmentNotice.model.dto.response.EmploymentNoticeResponseDTO; +import com.ComNCheck.ComNCheck.domain.employmentNotice.model.dto.response.FastAPIEmploymentNoticeResponseListDTO; +import com.ComNCheck.ComNCheck.domain.employmentNotice.model.dto.response.PageEmploymentNoticeResponseDTO; +import com.ComNCheck.ComNCheck.domain.employmentNotice.model.entity.EmploymentNotice; +import com.ComNCheck.ComNCheck.domain.employmentNotice.repository.EmploymentNoticeRepository; +import com.ComNCheck.ComNCheck.domain.fcm.service.FcmService; +import com.ComNCheck.ComNCheck.domain.global.infrastructure.FastApiClient; +import com.ComNCheck.ComNCheck.domain.member.model.entity.Member; +import com.ComNCheck.ComNCheck.domain.member.repository.MemberRepository; +import com.google.firebase.messaging.FirebaseMessagingException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@RequiredArgsConstructor +@Transactional(readOnly = true) +@Service +public class EmploymentNoticeService { + + private final EmploymentNoticeRepository employmentNoticeRepository; + private final FastApiClient fastApiClient; + private final MemberRepository memberRepository; + private final FcmService fcmService; + + @Transactional + public void syncEmploymentNotices() { + FastAPIEmploymentNoticeResponseListDTO response = fastApiClient.fetchEmploymentNotices(); + + List changeEmploymentNotices = new ArrayList<>(); + + if(response != null && response.getNotices() != null) { + for(EmploymentNoticeResponseDTO dto : response.getNotices()) { + Optional findEmployment = employmentNoticeRepository + .findByEmploymentNoticeId(dto.getEmploymentNoticeId()); + if(findEmployment.isEmpty()) { + EmploymentNotice newEmploymentNotice = new EmploymentNotice(dto); + employmentNoticeRepository.save(newEmploymentNotice); + changeEmploymentNotices.add(newEmploymentNotice); + } else { + EmploymentNotice existEmploymentNotice = findEmployment.get(); + if (existEmploymentNotice.updateFromDto(dto)) { + employmentNoticeRepository.save(existEmploymentNotice); + changeEmploymentNotices.add(existEmploymentNotice); + } + } + } + } + if (!changeEmploymentNotices.isEmpty()) { + // fcm 기능 구현 + System.out.println("알림 전송"); + List members = memberRepository.findByAlarmEmploymentNoticeTrue(); + + if(!members.isEmpty()) { + String title = "취업 공지사항"; + String body = "새로운 컴퓨터공학부 취업 글이 등록되었습니다."; + + for(Member member : members) { + if(!member.getFcmTokens().isEmpty()) { + member.getFcmTokens().forEach(fcmToken -> { + if(fcmToken.isValid() && fcmToken.getToken() != null + && !fcmToken.getToken().isBlank()) { + try { + fcmService.sendMessageToToken(fcmToken.getToken(), title,body); + } catch(FirebaseMessagingException e) { // 예외처리 이후 확인 + System.out.println("전송 실패"); + } + } + }); + } + } + } + } + } + + public List getAllEmploymentNotices() { + return employmentNoticeRepository.findAll() + .stream() + .map(EmploymentNoticeResponseDTO::of) + .toList(); + } + + public PageEmploymentNoticeResponseDTO getEmploymentNoticesPage(int page, int size) { + List allEmploymentNotices = employmentNoticeRepository.findAllOrderedById(); + + + long totalElements = allEmploymentNotices.size(); + int totalPages = (int) Math.ceil((double) totalElements / size); + + if (page < 1) { + page = 1; + } else if (page > totalPages && totalPages > 0) { + page = totalPages; + } + + int zeroBasedPage = page - 1; + int startIndex = zeroBasedPage * size; + int endIndex = Math.min(startIndex + size, (int) totalElements); + + List pageList = (startIndex < endIndex) + ? allEmploymentNotices.subList(startIndex, endIndex) + : Collections.emptyList(); + + List content = pageList.stream() + .map(EmploymentNoticeResponseDTO::of) + .collect(Collectors.toList()); + + return PageEmploymentNoticeResponseDTO.builder() + .currentPage(page) + .totalPages(totalPages) + .totalElements(totalElements) + .size(size) + .content(content) + .build(); + } +} diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/fcm/.DS_Store b/src/main/java/com/ComNCheck/ComNCheck/domain/fcm/.DS_Store new file mode 100644 index 0000000..3a07632 Binary files /dev/null and b/src/main/java/com/ComNCheck/ComNCheck/domain/fcm/.DS_Store differ diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/fcm/config/FCMConfig.java b/src/main/java/com/ComNCheck/ComNCheck/domain/fcm/config/FCMConfig.java new file mode 100644 index 0000000..0759894 --- /dev/null +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/fcm/config/FCMConfig.java @@ -0,0 +1,40 @@ +package com.ComNCheck.ComNCheck.domain.fcm.config; + +import com.google.auth.oauth2.GoogleCredentials; +import com.google.firebase.FirebaseApp; +import com.google.firebase.FirebaseOptions; +import com.google.firebase.messaging.FirebaseMessaging; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.ClassPathResource; + +@Configuration +public class FCMConfig { + @Bean + FirebaseMessaging firebaseMessaging() throws IOException { + ClassPathResource resource = new ClassPathResource("firebase/comncheck-firebase-adminsdk-fbsvc-1255c6f2ed.json"); + + InputStream refreshToken = resource.getInputStream(); + + FirebaseApp firebaseApp = null; + List firebaseAppList = FirebaseApp.getApps(); + + if(firebaseAppList != null && !firebaseAppList.isEmpty()) { + for(FirebaseApp app : firebaseAppList) { + if (app.getName().equals(FirebaseApp.DEFAULT_APP_NAME)) { + firebaseApp = app; + } + } + } + else { + FirebaseOptions options = FirebaseOptions.builder() + .setCredentials(GoogleCredentials.fromStream(refreshToken)) + .build(); + firebaseApp = FirebaseApp.initializeApp(options); + } + return FirebaseMessaging.getInstance(firebaseApp); + } +} diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/fcm/controller/FcmController.java b/src/main/java/com/ComNCheck/ComNCheck/domain/fcm/controller/FcmController.java new file mode 100644 index 0000000..eba7348 --- /dev/null +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/fcm/controller/FcmController.java @@ -0,0 +1,31 @@ +package com.ComNCheck.ComNCheck.domain.fcm.controller; + +import com.ComNCheck.ComNCheck.domain.fcm.service.FcmService; +import com.ComNCheck.ComNCheck.domain.security.oauth.CustomOAuth2Member; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/v1/fcm") +public class FcmController { + + private final FcmService fcmTokenService; + + @PostMapping + public ResponseEntity registerToken(@RequestParam String token, + Authentication authentication) { + CustomOAuth2Member principal = (CustomOAuth2Member) authentication.getPrincipal(); + Long memberId = principal.getMemberDTO().getMemberId(); + fcmTokenService.registerFcmToken(memberId, token); + return ResponseEntity.ok("토큰 등록 완료"); + } + + + +} diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/fcm/model/.DS_Store b/src/main/java/com/ComNCheck/ComNCheck/domain/fcm/model/.DS_Store new file mode 100644 index 0000000..db040e5 Binary files /dev/null and b/src/main/java/com/ComNCheck/ComNCheck/domain/fcm/model/.DS_Store differ diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/fcm/model/entity/FcmToken.java b/src/main/java/com/ComNCheck/ComNCheck/domain/fcm/model/entity/FcmToken.java new file mode 100644 index 0000000..e702fa6 --- /dev/null +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/fcm/model/entity/FcmToken.java @@ -0,0 +1,42 @@ +package com.ComNCheck.ComNCheck.domain.fcm.model.entity; + +import com.ComNCheck.ComNCheck.domain.member.model.entity.Member; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToMany; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Entity +public class FcmToken { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "fcm_token") + private String token; + + @Column(name = "fcm_valid") + private boolean valid = true; + + @Setter + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name ="member_id") + private Member member; + + public FcmToken(String token) { + this.token = token; + } +} diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/fcm/repository/FcmRepository.java b/src/main/java/com/ComNCheck/ComNCheck/domain/fcm/repository/FcmRepository.java new file mode 100644 index 0000000..fa5e7f8 --- /dev/null +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/fcm/repository/FcmRepository.java @@ -0,0 +1,9 @@ +package com.ComNCheck.ComNCheck.domain.fcm.repository; + +import com.ComNCheck.ComNCheck.domain.fcm.model.entity.FcmToken; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface FcmRepository extends JpaRepository { + Optional findByToken(String token); +} diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/fcm/service/FcmService.java b/src/main/java/com/ComNCheck/ComNCheck/domain/fcm/service/FcmService.java new file mode 100644 index 0000000..fa59763 --- /dev/null +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/fcm/service/FcmService.java @@ -0,0 +1,49 @@ +package com.ComNCheck.ComNCheck.domain.fcm.service; + +import com.ComNCheck.ComNCheck.domain.fcm.model.entity.FcmToken; +import com.ComNCheck.ComNCheck.domain.fcm.repository.FcmRepository; +import com.ComNCheck.ComNCheck.domain.global.exception.MemberNotFoundException; +import com.ComNCheck.ComNCheck.domain.member.model.entity.Member; +import com.ComNCheck.ComNCheck.domain.member.repository.MemberRepository; +import com.google.firebase.messaging.FirebaseMessaging; +import com.google.firebase.messaging.FirebaseMessagingException; +import com.google.firebase.messaging.Message; +import com.google.firebase.messaging.Notification; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@RequiredArgsConstructor +@Transactional(readOnly = true) +@Service +public class FcmService { + + private final MemberRepository memberRepository; + private final FcmRepository fcmRepository; + + public void sendMessageToToken(String token, String title, String body) throws FirebaseMessagingException { + Message message = Message.builder() + .setToken(token) + .setNotification( + Notification.builder() + .setTitle(title) + .setBody(body) + .build() + ) + .build(); + String response = FirebaseMessaging.getInstance().send(message); + } + @Transactional + public void registerFcmToken(Long memberId, String token) { + Member member = memberRepository.findById(memberId) + .orElseThrow(() -> new MemberNotFoundException("존재하지 않는 회원입니다.")); + + FcmToken existing = fcmRepository.findByToken(token).orElse(null); + if(existing != null) return; // 학습 후 추가 기능 구현해야함 + + FcmToken newFcmToken = new FcmToken(token); + member.addFcmToken(newFcmToken); + + memberRepository.save(member); + } +} diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/global/.DS_Store b/src/main/java/com/ComNCheck/ComNCheck/domain/global/.DS_Store new file mode 100644 index 0000000..f5daaeb Binary files /dev/null and b/src/main/java/com/ComNCheck/ComNCheck/domain/global/.DS_Store differ diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/global/config/MethodSecurityConfig.java b/src/main/java/com/ComNCheck/ComNCheck/domain/global/config/MethodSecurityConfig.java new file mode 100644 index 0000000..b9b52ef --- /dev/null +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/global/config/MethodSecurityConfig.java @@ -0,0 +1,10 @@ +package com.ComNCheck.ComNCheck.domain.global.config; + + +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; + +@Configuration +@EnableGlobalMethodSecurity(prePostEnabled = true) +public class MethodSecurityConfig { +} diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/global/config/RoleHierarchyConfig.java b/src/main/java/com/ComNCheck/ComNCheck/domain/global/config/RoleHierarchyConfig.java new file mode 100644 index 0000000..e732a29 --- /dev/null +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/global/config/RoleHierarchyConfig.java @@ -0,0 +1,25 @@ +package com.ComNCheck.ComNCheck.domain.global.config; + + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.access.hierarchicalroles.RoleHierarchy; +import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl; + +@Configuration +public class RoleHierarchyConfig { + + + @Bean + public RoleHierarchy roleHierarchy() { + RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl(); + String hierarchy = "ROLE_ADMIN > ROLE_MAJOR_PRESIDENT \n" + + "ROLE_MAJOR_PRESIDENT > ROLE_STUDENT_COUNCIL \n" + + "ROLE_STUDENT_COUNCIL > ROLE_STUDENT \n" + + "ROLE_STUDENT > ROLE_GRADUATE_STUDENT"; + roleHierarchy.setHierarchy(hierarchy); + return roleHierarchy; + } + + +} diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/global/config/SwaggerConfig.java b/src/main/java/com/ComNCheck/ComNCheck/domain/global/config/SwaggerConfig.java index ccd7de2..dac2fdf 100644 --- a/src/main/java/com/ComNCheck/ComNCheck/domain/global/config/SwaggerConfig.java +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/global/config/SwaggerConfig.java @@ -3,15 +3,24 @@ import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; +import io.swagger.v3.oas.models.security.SecurityScheme.In; +import io.swagger.v3.oas.models.security.SecurityScheme.Type; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class SwaggerConfig { + SecurityScheme securityScheme = new SecurityScheme() + .type(Type.APIKEY) + .in(In.COOKIE).name("AccessToken"); + SecurityRequirement securityRequirement = new SecurityRequirement().addList("cookieAuth"); @Bean public OpenAPI openAPI() { return new OpenAPI() - .components(new Components()) + .components(new Components().addSecuritySchemes("cookieAuth", securityScheme)) + .addSecurityItem(securityRequirement) .info(apiInfo()); } diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/global/exception/AnswerNotFoundException.java b/src/main/java/com/ComNCheck/ComNCheck/domain/global/exception/AnswerNotFoundException.java new file mode 100644 index 0000000..8075724 --- /dev/null +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/global/exception/AnswerNotFoundException.java @@ -0,0 +1,7 @@ +package com.ComNCheck.ComNCheck.domain.global.exception; + +public class AnswerNotFoundException extends RuntimeException{ + public AnswerNotFoundException(String message) { + super(message); + } +} diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/global/exception/ApplyNotFoundException.java b/src/main/java/com/ComNCheck/ComNCheck/domain/global/exception/ApplyNotFoundException.java new file mode 100644 index 0000000..06cf02a --- /dev/null +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/global/exception/ApplyNotFoundException.java @@ -0,0 +1,7 @@ +package com.ComNCheck.ComNCheck.domain.global.exception; + +public class ApplyNotFoundException extends RuntimeException{ + public ApplyNotFoundException(String message) { + super(message); + } +} diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/global/exception/BadRequestException.java b/src/main/java/com/ComNCheck/ComNCheck/domain/global/exception/BadRequestException.java deleted file mode 100644 index f195630..0000000 --- a/src/main/java/com/ComNCheck/ComNCheck/domain/global/exception/BadRequestException.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.ComNCheck.ComNCheck.domain.global.exception; - -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.ResponseStatus; - -@ResponseStatus(HttpStatus.BAD_REQUEST) -public class BadRequestException extends RuntimeException { - public BadRequestException(String message) { - super(message); - } -} diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/global/exception/ForbiddenException.java b/src/main/java/com/ComNCheck/ComNCheck/domain/global/exception/ForbiddenException.java new file mode 100644 index 0000000..38f1729 --- /dev/null +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/global/exception/ForbiddenException.java @@ -0,0 +1,7 @@ +package com.ComNCheck.ComNCheck.domain.global.exception; + +public class ForbiddenException extends RuntimeException { + public ForbiddenException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/global/exception/GlobalExceptionHandler.java b/src/main/java/com/ComNCheck/ComNCheck/domain/global/exception/GlobalExceptionHandler.java index d1a57ce..169744a 100644 --- a/src/main/java/com/ComNCheck/ComNCheck/domain/global/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/global/exception/GlobalExceptionHandler.java @@ -1,7 +1,5 @@ package com.ComNCheck.ComNCheck.domain.global.exception; -import com.ComNCheck.ComNCheck.domain.member.exception.MemberNotFoundException; -import com.ComNCheck.ComNCheck.domain.member.exception.ValidationException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; @@ -15,6 +13,21 @@ public ResponseEntity handleMemberNotFound(MemberNotFoundException ex) { return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getMessage()); } + @ExceptionHandler(PostNotFoundException.class) + public ResponseEntity handlePostNotFound(PostNotFoundException ex) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getMessage()); + } + + @ExceptionHandler(ForbiddenException.class) + public ResponseEntity handleForbiddenException(ForbiddenException ex) { + return ResponseEntity.status(HttpStatus.FORBIDDEN).body(ex.getMessage()); + } + + @ExceptionHandler(AnswerNotFoundException.class) + public ResponseEntity handleAnswerNotFoundException(AnswerNotFoundException ex) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getMessage()); + } + @ExceptionHandler(FastApiException.class) public ResponseEntity handleFastApiException(FastApiException ex) { return ResponseEntity.status(HttpStatus.BAD_GATEWAY).body(ex.getMessage()); @@ -25,9 +38,9 @@ public ResponseEntity handleValidationException(ValidationException ex) return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ex.getMessage()); } - @ExceptionHandler(Exception.class) - public ResponseEntity handleGeneralException(Exception ex) { - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("서버 내부 오류가 발생했습니다."); + @ExceptionHandler(ApplyNotFoundException.class) + public ResponseEntity handleApplyNotFoundException(ApplyNotFoundException ex) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getMessage()); } @ExceptionHandler(ResourceNotFoundException.class) @@ -37,12 +50,6 @@ public ResponseEntity handleResourceNotFound(ResourceNotFoundExce } - @ExceptionHandler(BadRequestException.class) - public ResponseEntity handleBadRequest(BadRequestException ex) { - ErrorResponse error = new ErrorResponse(HttpStatus.BAD_REQUEST.value(), ex.getMessage()); - return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST); - } - public static class ErrorResponse { private int status; private String message; diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/member/exception/MemberNotFoundException.java b/src/main/java/com/ComNCheck/ComNCheck/domain/global/exception/MemberNotFoundException.java similarity index 72% rename from src/main/java/com/ComNCheck/ComNCheck/domain/member/exception/MemberNotFoundException.java rename to src/main/java/com/ComNCheck/ComNCheck/domain/global/exception/MemberNotFoundException.java index a20acba..cfb3e75 100644 --- a/src/main/java/com/ComNCheck/ComNCheck/domain/member/exception/MemberNotFoundException.java +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/global/exception/MemberNotFoundException.java @@ -1,4 +1,4 @@ -package com.ComNCheck.ComNCheck.domain.member.exception; +package com.ComNCheck.ComNCheck.domain.global.exception; public class MemberNotFoundException extends RuntimeException { public MemberNotFoundException(String message) { diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/global/exception/PostNotFoundException.java b/src/main/java/com/ComNCheck/ComNCheck/domain/global/exception/PostNotFoundException.java new file mode 100644 index 0000000..7ed2d41 --- /dev/null +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/global/exception/PostNotFoundException.java @@ -0,0 +1,7 @@ +package com.ComNCheck.ComNCheck.domain.global.exception; + +public class PostNotFoundException extends RuntimeException{ + public PostNotFoundException(String message) { + super(message); + } +} diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/global/exception/UnauthorizedException.java b/src/main/java/com/ComNCheck/ComNCheck/domain/global/exception/UnauthorizedException.java deleted file mode 100644 index c15755a..0000000 --- a/src/main/java/com/ComNCheck/ComNCheck/domain/global/exception/UnauthorizedException.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.ComNCheck.ComNCheck.domain.global.exception; - -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.ResponseStatus; - - -@ResponseStatus(HttpStatus.FORBIDDEN) -public class UnauthorizedException extends RuntimeException { - public UnauthorizedException(String message) { - super(message); - } -} diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/member/exception/ValidationException.java b/src/main/java/com/ComNCheck/ComNCheck/domain/global/exception/ValidationException.java similarity index 71% rename from src/main/java/com/ComNCheck/ComNCheck/domain/member/exception/ValidationException.java rename to src/main/java/com/ComNCheck/ComNCheck/domain/global/exception/ValidationException.java index f742872..d92df8a 100644 --- a/src/main/java/com/ComNCheck/ComNCheck/domain/member/exception/ValidationException.java +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/global/exception/ValidationException.java @@ -1,4 +1,4 @@ -package com.ComNCheck.ComNCheck.domain.member.exception; +package com.ComNCheck.ComNCheck.domain.global.exception; public class ValidationException extends RuntimeException { public ValidationException(String message) { diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/global/infrastructure/FastApiClient.java b/src/main/java/com/ComNCheck/ComNCheck/domain/global/infrastructure/FastApiClient.java index cf3d163..d49333d 100644 --- a/src/main/java/com/ComNCheck/ComNCheck/domain/global/infrastructure/FastApiClient.java +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/global/infrastructure/FastApiClient.java @@ -1,11 +1,16 @@ package com.ComNCheck.ComNCheck.domain.global.infrastructure; +import com.ComNCheck.ComNCheck.domain.employmentNotice.model.dto.response.FastAPIEmploymentNoticeResponseListDTO; import com.ComNCheck.ComNCheck.domain.global.exception.FastApiException; +import com.ComNCheck.ComNCheck.domain.majorNotice.model.dto.response.FastAPIMajorNoticesResponseListDTO; import com.ComNCheck.ComNCheck.domain.member.model.dto.response.FastApiStudentCardDTO; +import jakarta.annotation.PostConstruct; import java.io.IOException; import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.ByteArrayResource; import org.springframework.http.HttpEntity; +import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -15,12 +20,29 @@ import org.springframework.web.client.RestTemplate; import org.springframework.web.multipart.MultipartFile; import org.springframework.http.HttpHeaders; + @Component @RequiredArgsConstructor public class FastApiClient { private final RestTemplate restTemplate; - private static final String FAST_API_URL_OCR= "http://localhost:8000/api/vi/compare-and-ocr"; + private static final String LOCAL = "http://localhost:8000"; + private static final String TEST_DISTRIBUTION = "http://comncheck.iptime.org:8000"; + @Value("${target.server.ip}") + private String PROD_FASTAPI_IP; + + private String PROD_FASTAPI; + private String FAST_API_URL_OCR; + private String FAST_API_URL_SCRAPE_NOTICE; + private String Fast_API_URL_EMPLOYMENT; + + @PostConstruct + public void init() { + PROD_FASTAPI = "http://" + PROD_FASTAPI_IP; + FAST_API_URL_OCR= PROD_FASTAPI + "/api/v1/compare-and-ocr"; + FAST_API_URL_SCRAPE_NOTICE = PROD_FASTAPI + "/api/v1/scrape/notice"; + Fast_API_URL_EMPLOYMENT = PROD_FASTAPI + "/api/v1/scrape/employment"; + } public FastApiStudentCardDTO sendImage(MultipartFile imageFile) { MultiValueMap body = new LinkedMultiValueMap<>(); @@ -54,4 +76,54 @@ public String getFilename() { throw new FastApiException("이미지 처리 중 오류 발생: " + e.getMessage(), e); } } + + public FastAPIMajorNoticesResponseListDTO fetchMajorNotices() { + try { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + + HttpEntity requestEntity = new HttpEntity<>(headers); + + ResponseEntity response = restTemplate.exchange( + FAST_API_URL_SCRAPE_NOTICE, + HttpMethod.GET, + requestEntity, + FastAPIMajorNoticesResponseListDTO.class + ); + + if (response.getStatusCode() != HttpStatus.OK || response.getBody() == null) { + throw new FastApiException("FastAPI 호출 실패: " + response.getStatusCode()); + } + + return response.getBody(); + + } catch (Exception e) { + throw new FastApiException("공지사항 가져오기 중 오류 발생: " + e.getMessage(), e); + } + } + + public FastAPIEmploymentNoticeResponseListDTO fetchEmploymentNotices() { + try { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + + HttpEntity requestEntity = new HttpEntity<>(headers); + + ResponseEntity response = restTemplate.exchange( + Fast_API_URL_EMPLOYMENT, + HttpMethod.GET, + requestEntity, + FastAPIEmploymentNoticeResponseListDTO.class + ); + + if (response.getStatusCode() != HttpStatus.OK || response.getBody() == null) { + throw new FastApiException("FastAPI 호출 실패: " + response.getStatusCode()); + } + + return response.getBody(); + + } catch (Exception e) { + throw new FastApiException("공지사항 가져오기 중 오류 발생: " + e.getMessage(), e); + } + } } diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/majorEvent/.DS_Store b/src/main/java/com/ComNCheck/ComNCheck/domain/majorEvent/.DS_Store new file mode 100644 index 0000000..f531e70 Binary files /dev/null and b/src/main/java/com/ComNCheck/ComNCheck/domain/majorEvent/.DS_Store differ diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/majorEvent/controller/MajorEventController.java b/src/main/java/com/ComNCheck/ComNCheck/domain/majorEvent/controller/MajorEventController.java index f1cecca..406dda8 100644 --- a/src/main/java/com/ComNCheck/ComNCheck/domain/majorEvent/controller/MajorEventController.java +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/majorEvent/controller/MajorEventController.java @@ -7,9 +7,13 @@ import com.ComNCheck.ComNCheck.domain.majorEvent.model.dto.response.PagedEventListResponseDTO; import com.ComNCheck.ComNCheck.domain.majorEvent.service.MajorEventService; import com.ComNCheck.ComNCheck.domain.security.oauth.CustomOAuth2Member; +import io.swagger.v3.oas.annotations.Operation; +import java.time.LocalDate; +import java.time.LocalTime; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -18,7 +22,6 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RequestMapping("/api/v1/major-event") @@ -29,47 +32,54 @@ public class MajorEventController { private final MajorEventService majorEventService; @PostMapping + @Operation(summary = "과행사 게시글 작성", description = "과행사 게시글을 작성한다. 학생회, 과회장만 가능하다.") public ResponseEntity createMajorEvent(@ModelAttribute EventCreateRequestDTO requestDTO, Authentication authentication) { + // 문제 발생시 쌍따음표 일수도 있음 CustomOAuth2Member principal = (CustomOAuth2Member) authentication.getPrincipal(); Long memberId = principal.getMemberDTO().getMemberId(); EventResponseDTO responseDTO = majorEventService.createMajorEvent(requestDTO, memberId); return ResponseEntity.ok(responseDTO); } + @GetMapping("/{majorEventId}") + @Operation(summary = "특정 과행사 게시글 조회", description = "특정 과행사 게시글을 조회한다.") public ResponseEntity getMajorEvent(@PathVariable Long majorEventId) { EventResponseDTO responseDTO = majorEventService.getMajorEvent(majorEventId); return ResponseEntity.ok(responseDTO); } + @GetMapping + @Operation(summary = "과행사 게시글 목록 조회", description = "과행사 게시글 목록을 조회한다. 이미 지난 행사는 보여주지 않는다.") public ResponseEntity> getAllMajorEventsNotPassed() { List allMajorEventsNotPassed = majorEventService.getAllMajorEventsNotPassed(); return ResponseEntity.ok(allMajorEventsNotPassed); } - @GetMapping("/pages") - public ResponseEntity getAllMajorEventPage( - @RequestParam(defaultValue = "1") int page, - @RequestParam(defaultValue = "10") int size - ) { - PagedEventListResponseDTO responseDTO = majorEventService.getAllMajorEventPage(page, size); - return ResponseEntity.ok(responseDTO); - } @PutMapping("/{majorEventId}") + @Operation(summary = "과행사 게시글 수정", description = "작성된 과행사 게시글을 수정한다. 작성자가 누구든 과회장과, 학생회는 수정할 수 있다.") public ResponseEntity updateMajorEvent( @PathVariable Long majorEventId, - @ModelAttribute EventUpdateRequestDTO requestDTO + @ModelAttribute EventUpdateRequestDTO requestDTO, + Authentication authentication ) { - EventResponseDTO updateDTO = majorEventService.updateMajorEvent(majorEventId, requestDTO); + CustomOAuth2Member principal = (CustomOAuth2Member) authentication.getPrincipal(); + Long memberId = principal.getMemberDTO().getMemberId(); + EventResponseDTO updateDTO = majorEventService.updateMajorEvent(majorEventId, requestDTO, memberId); return ResponseEntity.ok(updateDTO); } + @DeleteMapping("/{majorEventId}") - public ResponseEntity deleteMajorEvent(@PathVariable Long majorEventId) { - majorEventService.deleteMajorEvent(majorEventId); + @Operation(summary = "과행사 게시글 삭제 ", description = "작성된 과행사 게시글을 삭제한다. 작성자가 누구든 과회장과, 학생회는 삭제할 수 있다.") + public ResponseEntity deleteMajorEvent(@PathVariable Long majorEventId, + Authentication authentication) { + CustomOAuth2Member principal = (CustomOAuth2Member) authentication.getPrincipal(); + Long memberId = principal.getMemberDTO().getMemberId(); + majorEventService.deleteMajorEvent(majorEventId, memberId); return ResponseEntity.noContent().build(); } diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/majorEvent/model/.DS_Store b/src/main/java/com/ComNCheck/ComNCheck/domain/majorEvent/model/.DS_Store new file mode 100644 index 0000000..20a2c97 Binary files /dev/null and b/src/main/java/com/ComNCheck/ComNCheck/domain/majorEvent/model/.DS_Store differ diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/majorEvent/model/dto/request/EventCreateRequestDTO.java b/src/main/java/com/ComNCheck/ComNCheck/domain/majorEvent/model/dto/request/EventCreateRequestDTO.java index 816074a..06e5d4c 100644 --- a/src/main/java/com/ComNCheck/ComNCheck/domain/majorEvent/model/dto/request/EventCreateRequestDTO.java +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/majorEvent/model/dto/request/EventCreateRequestDTO.java @@ -14,14 +14,20 @@ public class EventCreateRequestDTO { private String eventName; - @DateTimeFormat(pattern = "yyyy-MM-dd") - private LocalDate date; - @DateTimeFormat(pattern = "HH:mm") - private LocalTime time; + private String date; + private String time; private String location; private String notice; private String googleFormLink; private List cardNewsImages; + public LocalDate getParsedDate() { + return date != null ? LocalDate.parse(date) : null; + } + + public LocalTime getParsedTime() { + return time != null ? LocalTime.parse(time) : null; + } + } diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/majorEvent/model/dto/request/EventUpdateRequestDTO.java b/src/main/java/com/ComNCheck/ComNCheck/domain/majorEvent/model/dto/request/EventUpdateRequestDTO.java index c61d41a..796c7aa 100644 --- a/src/main/java/com/ComNCheck/ComNCheck/domain/majorEvent/model/dto/request/EventUpdateRequestDTO.java +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/majorEvent/model/dto/request/EventUpdateRequestDTO.java @@ -14,13 +14,20 @@ public class EventUpdateRequestDTO { private String eventName; - @DateTimeFormat(pattern = "yyyy-MM-dd") - private LocalDate date; - @DateTimeFormat(pattern = "HH:mm") - private LocalTime time; + private String date; + private String time; private String location; private String notice; private String googleFormLink; + private String firstImageUrl; private List cardNewsImages; + + public LocalDate getParsedDate() { + return date != null ? LocalDate.parse(date) : null; + } + + public LocalTime getParsedTime() { + return time != null ? LocalTime.parse(time) : null; + } } \ No newline at end of file diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/majorEvent/model/dto/response/EventListResponseDTO.java b/src/main/java/com/ComNCheck/ComNCheck/domain/majorEvent/model/dto/response/EventListResponseDTO.java index 56aa2bd..b32745a 100644 --- a/src/main/java/com/ComNCheck/ComNCheck/domain/majorEvent/model/dto/response/EventListResponseDTO.java +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/majorEvent/model/dto/response/EventListResponseDTO.java @@ -10,15 +10,26 @@ @Getter @Builder public class EventListResponseDTO { + private Long id; private String eventName; private LocalDate date; private LocalTime time; + private String googleFormLink; + private String firstImageUrl; + public static EventListResponseDTO of(MajorEvent majorEvent) { + String firstImage = null; + if (majorEvent.getCardNewsImageUrls() != null && !majorEvent.getCardNewsImageUrls().isEmpty()) { + firstImage = majorEvent.getCardNewsImageUrls().get(0); + } return EventListResponseDTO.builder() + .id(majorEvent.getMajorEventId()) .eventName(majorEvent.getEventName()) .date(majorEvent.getDate()) .time(majorEvent.getTime()) + .googleFormLink(majorEvent.getGoogleFormLink()) + .firstImageUrl(firstImage) .build(); } } diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/majorEvent/model/entity/MajorEvent.java b/src/main/java/com/ComNCheck/ComNCheck/domain/majorEvent/model/entity/MajorEvent.java index 9876800..1b1300e 100644 --- a/src/main/java/com/ComNCheck/ComNCheck/domain/majorEvent/model/entity/MajorEvent.java +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/majorEvent/model/entity/MajorEvent.java @@ -39,10 +39,10 @@ public class MajorEvent { @Column(name = "time", nullable = false) private LocalTime time; - @Column(name = "localtion", nullable = false) + @Column(name = "location", nullable = false) private String location; - @Column(name = "notice", nullable = false) + @Column(name = "notice", nullable = false, columnDefinition = "TEXT") private String notice; @Column(name = "google_form_link") diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/majorEvent/repository/MajorEventRepository.java b/src/main/java/com/ComNCheck/ComNCheck/domain/majorEvent/repository/MajorEventRepository.java index fa7abd0..ee14a28 100644 --- a/src/main/java/com/ComNCheck/ComNCheck/domain/majorEvent/repository/MajorEventRepository.java +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/majorEvent/repository/MajorEventRepository.java @@ -9,15 +9,15 @@ import org.springframework.data.repository.query.Param; public interface MajorEventRepository extends JpaRepository { - @Query(""" - SELECT e - FROM MajorEvent e - WHERE (e.date > :today) - OR (e.date = :today AND e.time >= :currentTime) - ORDER BY e.date ASC, e.time ASC - """) - List findUpcomingEvents( - @Param("today") LocalDate today, - @Param("currentTime") LocalTime currentTime - ); +// @Query(""" +// SELECT e +// FROM MajorEvent e +// WHERE (e.date > :today) +// OR (e.date = :today AND e.time >= :currentTime) +// ORDER BY e.date ASC, e.time ASC +// """) +// List findUpcomingEvents( +// @Param("today") LocalDate today, +// @Param("currentTime") LocalTime currentTime +// ); } diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/majorEvent/service/MajorEventService.java b/src/main/java/com/ComNCheck/ComNCheck/domain/majorEvent/service/MajorEventService.java index 3e9b6f4..7458cab 100644 --- a/src/main/java/com/ComNCheck/ComNCheck/domain/majorEvent/service/MajorEventService.java +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/majorEvent/service/MajorEventService.java @@ -1,22 +1,31 @@ package com.ComNCheck.ComNCheck.domain.majorEvent.service; +import com.ComNCheck.ComNCheck.domain.fcm.service.FcmService; +import com.ComNCheck.ComNCheck.domain.global.exception.ForbiddenException; +import com.ComNCheck.ComNCheck.domain.global.exception.MemberNotFoundException; +import com.ComNCheck.ComNCheck.domain.global.exception.PostNotFoundException; import com.ComNCheck.ComNCheck.domain.majorEvent.model.dto.request.EventCreateRequestDTO; import com.ComNCheck.ComNCheck.domain.majorEvent.model.dto.request.EventUpdateRequestDTO; import com.ComNCheck.ComNCheck.domain.majorEvent.model.dto.response.EventListResponseDTO; import com.ComNCheck.ComNCheck.domain.majorEvent.model.dto.response.EventResponseDTO; -import com.ComNCheck.ComNCheck.domain.majorEvent.model.dto.response.PagedEventListResponseDTO; import com.ComNCheck.ComNCheck.domain.majorEvent.model.entity.MajorEvent; import com.ComNCheck.ComNCheck.domain.majorEvent.repository.MajorEventRepository; import com.ComNCheck.ComNCheck.domain.member.model.entity.Member; +import com.ComNCheck.ComNCheck.domain.member.model.entity.Role; import com.ComNCheck.ComNCheck.domain.member.repository.MemberRepository; +import com.google.cloud.storage.Storage; +import com.google.cloud.storage.BlobInfo; +import com.google.firebase.messaging.FirebaseMessagingException; +import java.io.IOException; import java.time.LocalDate; import java.time.LocalTime; import java.util.ArrayList; -import java.util.Collections; import java.util.Comparator; import java.util.List; +import java.util.UUID; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; @@ -28,19 +37,27 @@ public class MajorEventService { private final MajorEventRepository majorEventRepository; private final MemberRepository memberRepository; + private final FcmService fcmService; + @Value("${spring.cloud.gcp.storage.bucket}") + private String bucketName; + private final Storage storage; @Transactional public EventResponseDTO createMajorEvent(EventCreateRequestDTO requestDTO, Long writerId) { Member writer = memberRepository.findByMemberId(writerId) - .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 회원입니다.")); + .orElseThrow(() -> new MemberNotFoundException("존재하지 않는 회원입니다.")); + + isCheckRole(writer); List imageUrls = uploadImagesToGcs(requestDTO.getCardNewsImages()); + LocalDate eventDate = requestDTO.getParsedDate(); + LocalTime eventTime = requestDTO.getParsedTime(); MajorEvent majorEvent = MajorEvent.builder() .writer(writer) .eventName(requestDTO.getEventName()) - .date(requestDTO.getDate()) - .time(requestDTO.getTime()) + .date(eventDate) + .time(eventTime) .location(requestDTO.getLocation()) .notice(requestDTO.getNotice()) .googleFormLink(requestDTO.getGoogleFormLink()) @@ -48,85 +65,74 @@ public EventResponseDTO createMajorEvent(EventCreateRequestDTO requestDTO, Long .build(); MajorEvent savedMajorEvent = majorEventRepository.save(majorEvent); + + List members = memberRepository.findByAlarmMajorEventTrue(); + + if(!members.isEmpty()) { + String title = "공지사항"; + String body = "새로운 과행사 글이 등록되었습니다."; + + for(Member member : members) { + if(!member.getFcmTokens().isEmpty()) { + member.getFcmTokens().forEach(fcmToken -> { + if(fcmToken.isValid() && fcmToken.getToken() != null + && !fcmToken.getToken().isBlank()) { + try { + fcmService.sendMessageToToken(fcmToken.getToken(), title,body); + } catch(FirebaseMessagingException e) { + System.out.println("전송 실패"); + } + } + }); + } + } + } return EventResponseDTO.of(savedMajorEvent); } public EventResponseDTO getMajorEvent(Long majorEventId) { MajorEvent majorEvent = majorEventRepository.findById(majorEventId) - .orElseThrow(() -> new IllegalArgumentException("해당 학부 행사 정보가 없습니다.")); + .orElseThrow(() -> new PostNotFoundException("요청하신 학부 행사가 없습니다.")); return EventResponseDTO.of(majorEvent); } public List getAllMajorEventsNotPassed() { + // 코드 상에서 정렬 보다는 디비에서 정렬하고 보내는 것이 더 효율적일꺼같음 추후 리펙토링 필요 List all = majorEventRepository.findAll(); LocalDate today = LocalDate.now(); LocalTime currentTime = LocalTime.now(); - List filtered = all.stream() - .filter(e -> isNotPassed(e, today, currentTime)) - .collect(Collectors.toList()); - - filtered.sort(Comparator.comparing(MajorEvent::getDate) - .thenComparing(MajorEvent::getTime)); +// List filtered = all.stream() // 기간 지난 행사 제외 +// .filter(e -> isNotPassed(e, today, currentTime)) +// .collect(Collectors.toList()); + all.sort( + Comparator + .comparing(MajorEvent::getDate, Comparator.reverseOrder()) // 날짜 내림차순 + .thenComparing(MajorEvent::getTime, Comparator.reverseOrder()) // 시간 내림차순 + ); - return filtered.stream() + return all.stream() .map(EventListResponseDTO::of) .collect(Collectors.toList()); } - public PagedEventListResponseDTO getAllMajorEventPage(int page, int size) { - List all = majorEventRepository.findAll(); - - LocalDate today = LocalDate.now(); - LocalTime currentTime = LocalTime.now(); - - List filtered = all.stream() - .filter(e -> isNotPassed(e, today, currentTime)) - .collect(Collectors.toList()); - - filtered.sort(Comparator.comparing(MajorEvent::getDate) - .thenComparing(MajorEvent::getTime)); - - long totalElements = filtered.size(); - int totalPages = (int) Math.ceil((double) totalElements / size); - - if (page < 1) { - page = 1; - } else if (page >= totalPages && totalPages > 0) { - page = totalPages -1; - } - int zeroBasedPage = page - 1; - int startIndex = zeroBasedPage * size; - int endIndex = Math.min(startIndex + size, (int) totalElements); - - List pageList = (startIndex < endIndex) - ? filtered.subList(startIndex, endIndex) - : Collections.emptyList(); - - List content = pageList.stream() - .map(EventListResponseDTO::of) - .toList(); - - return PagedEventListResponseDTO.builder() - .currentPage(page) - .totalPages(totalPages) - .totalElements(totalElements) - .size(size) - .content(content) - .build(); - - } - @Transactional - public EventResponseDTO updateMajorEvent(Long majorEventId, EventUpdateRequestDTO requestDTO) { + public EventResponseDTO updateMajorEvent(Long majorEventId, EventUpdateRequestDTO requestDTO, Long memberId) { + Member member = memberRepository.findByMemberId(memberId) + .orElseThrow(() -> new MemberNotFoundException("등록된 회원이 없습니다.")); + isCheckRole(member); + MajorEvent majorEvent = majorEventRepository.findById(majorEventId) - .orElseThrow(() -> new IllegalArgumentException("해당 학부 행사 정보가 없습니다.")); + .orElseThrow(() -> new PostNotFoundException("요청하신 학부 행사가 없습니다.")); + + LocalDate eventDate = requestDTO.getParsedDate(); + LocalTime eventTime = requestDTO.getParsedTime(); majorEvent.updateEvent( requestDTO.getEventName(), - requestDTO.getDate(), - requestDTO.getTime(), + eventDate, + eventTime, requestDTO.getLocation(), requestDTO.getNotice(), requestDTO.getGoogleFormLink() @@ -140,9 +146,13 @@ public EventResponseDTO updateMajorEvent(Long majorEventId, EventUpdateRequestDT } @Transactional - public void deleteMajorEvent(Long majorEventId) { + public void deleteMajorEvent(Long majorEventId, Long memberId) { + Member member = memberRepository.findByMemberId(memberId) + .orElseThrow(() -> new MemberNotFoundException("등록된 회원이 없습니다.")); + isCheckRole(member); + MajorEvent majorEvent = majorEventRepository.findById(majorEventId) - .orElseThrow(() -> new IllegalArgumentException("해당 학부 행사 정보가 없습니다.")); + .orElseThrow(() -> new PostNotFoundException("요청하신 학부 행사가 없습니다.")); majorEventRepository.delete(majorEvent); } @@ -151,11 +161,27 @@ private List uploadImagesToGcs(List images) { if (images == null || images.isEmpty()) { return new ArrayList<>(); } + List uploadUrls = new ArrayList<>(); - for(MultipartFile file : images) { - // gcs 업로드 호출 - String url = "https://gcs.com" + file.getOriginalFilename(); - uploadUrls.add(url); + for (MultipartFile file : images) { + try { + String uuid = UUID.randomUUID().toString(); + String contentType = file.getContentType(); + if (contentType == null) { + contentType = "application/octet-stream"; + } + BlobInfo blobInfo = storage.create( + BlobInfo.newBuilder(bucketName, uuid) + .setContentType(contentType) + .build(), + file.getInputStream() + ); + String url = "https://storage.googleapis.com/" + bucketName + "/" + uuid; + uploadUrls.add(url); + + } catch (IOException e) { + throw new RuntimeException("이미지 업로드 실패", e); + } } return uploadUrls; } @@ -164,4 +190,11 @@ private boolean isNotPassed(MajorEvent majorEvent, LocalDate today, LocalTime cu return majorEvent.getDate().isAfter(today) || (majorEvent.getDate().isEqual(today) && majorEvent.getTime().isBefore(currentTime)); } + + public void isCheckRole(Member member) { + Role checkRole = member.getRole(); + if(checkRole != Role.ROLE_ADMIN && checkRole != Role.ROLE_MAJOR_PRESIDENT && checkRole != Role.ROLE_STUDENT_COUNCIL) { + throw new ForbiddenException("접근 권한이 없습니다."); + } + } } diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/majorNotice/.DS_Store b/src/main/java/com/ComNCheck/ComNCheck/domain/majorNotice/.DS_Store new file mode 100644 index 0000000..29bbb6e Binary files /dev/null and b/src/main/java/com/ComNCheck/ComNCheck/domain/majorNotice/.DS_Store differ diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/majorNotice/config/scheduler/MajorScheduler.java b/src/main/java/com/ComNCheck/ComNCheck/domain/majorNotice/config/scheduler/MajorScheduler.java new file mode 100644 index 0000000..fd28d87 --- /dev/null +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/majorNotice/config/scheduler/MajorScheduler.java @@ -0,0 +1,25 @@ +package com.ComNCheck.ComNCheck.domain.majorNotice.config.scheduler; + +import com.ComNCheck.ComNCheck.domain.majorNotice.service.MajorNoticeService; +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; + +@Configuration +@EnableScheduling +@RequiredArgsConstructor +public class MajorScheduler { + private final MajorNoticeService majorNoticeService; + + @Scheduled(cron = "0 0 * * * *") + public void syncNoticesPeriodically() { + majorNoticeService.syncMajorNotices(); + } + + @PostConstruct + public void initLoad() { + majorNoticeService.syncMajorNotices(); + } +} diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/majorNotice/controller/MajorNoticeController.java b/src/main/java/com/ComNCheck/ComNCheck/domain/majorNotice/controller/MajorNoticeController.java new file mode 100644 index 0000000..4a258ca --- /dev/null +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/majorNotice/controller/MajorNoticeController.java @@ -0,0 +1,38 @@ +package com.ComNCheck.ComNCheck.domain.majorNotice.controller; + +import com.ComNCheck.ComNCheck.domain.majorNotice.model.dto.response.MajorNoticeResponseDTO; +import com.ComNCheck.ComNCheck.domain.majorNotice.model.dto.response.PageMajorNoticeResponseDTO; +import com.ComNCheck.ComNCheck.domain.majorNotice.service.MajorNoticeService; +import io.swagger.v3.oas.annotations.Operation; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RequiredArgsConstructor +@RequestMapping("/api/v1/major/notices") +@RestController +public class MajorNoticeController { + + private final MajorNoticeService majorNoticeService; + + @GetMapping + @Operation(summary = "학부 공지사항 목록 조회", description = "학부 공지사항 목록을 조회한다.") + public ResponseEntity> getAllMajorNotices() { + List lists = majorNoticeService.getAllMajorNotices(); + return ResponseEntity.ok(lists); + } + + @GetMapping("/pages") + @Operation(summary = "학부 공지사항 목록 조회(페이지네이션)", description = "페이지네이션으로 학부 공지사항 목록을 조회한다.") + public ResponseEntity getMajorNoticesPage( + @RequestParam(defaultValue = "1") int page, + @RequestParam(defaultValue = "10") int size + ) { + PageMajorNoticeResponseDTO pageResponse = majorNoticeService.getMajorNoticesPage(page, size); + return ResponseEntity.ok(pageResponse); + } +} diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/majorNotice/model/.DS_Store b/src/main/java/com/ComNCheck/ComNCheck/domain/majorNotice/model/.DS_Store new file mode 100644 index 0000000..a2f8e3e Binary files /dev/null and b/src/main/java/com/ComNCheck/ComNCheck/domain/majorNotice/model/.DS_Store differ diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/majorNotice/model/dto/response/FastAPIMajorNoticesResponseListDTO.java b/src/main/java/com/ComNCheck/ComNCheck/domain/majorNotice/model/dto/response/FastAPIMajorNoticesResponseListDTO.java new file mode 100644 index 0000000..6440c46 --- /dev/null +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/majorNotice/model/dto/response/FastAPIMajorNoticesResponseListDTO.java @@ -0,0 +1,11 @@ +package com.ComNCheck.ComNCheck.domain.majorNotice.model.dto.response; + +import java.util.List; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class FastAPIMajorNoticesResponseListDTO { + private List notices; +} diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/majorNotice/model/dto/response/MajorNoticeResponseDTO.java b/src/main/java/com/ComNCheck/ComNCheck/domain/majorNotice/model/dto/response/MajorNoticeResponseDTO.java new file mode 100644 index 0000000..deecca7 --- /dev/null +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/majorNotice/model/dto/response/MajorNoticeResponseDTO.java @@ -0,0 +1,29 @@ +package com.ComNCheck.ComNCheck.domain.majorNotice.model.dto.response; + + +import com.ComNCheck.ComNCheck.domain.majorNotice.model.entity.MajorNotice; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.time.LocalDate; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class MajorNoticeResponseDTO { + @JsonProperty("notice_id") + private int noticeId; + private String title; + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy.MM.dd") + private LocalDate date; + private String link; + + public static MajorNoticeResponseDTO of(MajorNotice majorNotice) { + return MajorNoticeResponseDTO.builder() + .noticeId(majorNotice.getNoticeId()) + .title(majorNotice.getTitle()) + .date(majorNotice.getDate()) + .link(majorNotice.getLink()) + .build(); + } +} diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/majorNotice/model/dto/response/PageMajorNoticeResponseDTO.java b/src/main/java/com/ComNCheck/ComNCheck/domain/majorNotice/model/dto/response/PageMajorNoticeResponseDTO.java new file mode 100644 index 0000000..0635760 --- /dev/null +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/majorNotice/model/dto/response/PageMajorNoticeResponseDTO.java @@ -0,0 +1,16 @@ +package com.ComNCheck.ComNCheck.domain.majorNotice.model.dto.response; + + +import java.util.List; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class PageMajorNoticeResponseDTO { + private int currentPage; + private int totalPages; + private long totalElements; + private int size; + private List content; +} diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/majorNotice/model/entity/MajorNotice.java b/src/main/java/com/ComNCheck/ComNCheck/domain/majorNotice/model/entity/MajorNotice.java new file mode 100644 index 0000000..30959ed --- /dev/null +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/majorNotice/model/entity/MajorNotice.java @@ -0,0 +1,64 @@ +package com.ComNCheck.ComNCheck.domain.majorNotice.model.entity; + +import com.ComNCheck.ComNCheck.domain.majorNotice.model.dto.response.MajorNoticeResponseDTO; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import java.time.LocalDate; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + + +@Getter +@Table(name = "major_notices") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Entity +public class MajorNotice { + + @Id + @Column(name = "major_notice_id") + private int noticeId; + + @Column + private String title; + + @Column + private LocalDate date; + + @Column + private String link; + + public MajorNotice(MajorNoticeResponseDTO dto) { + this.noticeId = dto.getNoticeId(); + this.title = dto.getTitle(); + this.date = dto.getDate(); + this.link = dto.getLink(); + } + + public boolean equalsDTO(MajorNoticeResponseDTO dto) { + return this.noticeId== dto.getNoticeId() && + this.title.equals(dto.getTitle()) && + this.date.isEqual(dto.getDate()) && + this.link.equals(dto.getLink()); + } + public boolean updateFromDto(MajorNoticeResponseDTO dto) { + boolean changed = false; + + if (!this.title.equals(dto.getTitle())) { + this.title = dto.getTitle(); + changed = true; + } + if (!this.date.isEqual(dto.getDate())) { + this.date = dto.getDate(); + changed = true; + } + if (!this.link.equals(dto.getLink())) { + this.link = dto.getLink(); + changed = true; + } + return changed; + } + +} diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/majorNotice/repository/MajorNoticeRepository.java b/src/main/java/com/ComNCheck/ComNCheck/domain/majorNotice/repository/MajorNoticeRepository.java new file mode 100644 index 0000000..f602f06 --- /dev/null +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/majorNotice/repository/MajorNoticeRepository.java @@ -0,0 +1,16 @@ +package com.ComNCheck.ComNCheck.domain.majorNotice.repository; + + +import com.ComNCheck.ComNCheck.domain.majorNotice.model.entity.MajorNotice; +import java.util.List; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +public interface MajorNoticeRepository extends JpaRepository { + Optional findByNoticeId(int noticeId); + @Query("SELECT e FROM MajorNotice e ORDER BY e.noticeId DESC") + List findAllOrderedById(); + + +} diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/majorNotice/service/MajorNoticeService.java b/src/main/java/com/ComNCheck/ComNCheck/domain/majorNotice/service/MajorNoticeService.java new file mode 100644 index 0000000..41d28ab --- /dev/null +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/majorNotice/service/MajorNoticeService.java @@ -0,0 +1,122 @@ +package com.ComNCheck.ComNCheck.domain.majorNotice.service; + +import com.ComNCheck.ComNCheck.domain.fcm.service.FcmService; +import com.ComNCheck.ComNCheck.domain.global.infrastructure.FastApiClient; +import com.ComNCheck.ComNCheck.domain.majorNotice.model.dto.response.FastAPIMajorNoticesResponseListDTO; +import com.ComNCheck.ComNCheck.domain.majorNotice.model.dto.response.MajorNoticeResponseDTO; +import com.ComNCheck.ComNCheck.domain.majorNotice.model.dto.response.PageMajorNoticeResponseDTO; +import com.ComNCheck.ComNCheck.domain.majorNotice.model.entity.MajorNotice; +import com.ComNCheck.ComNCheck.domain.majorNotice.repository.MajorNoticeRepository; +import com.ComNCheck.ComNCheck.domain.member.model.entity.Member; +import com.ComNCheck.ComNCheck.domain.member.repository.MemberRepository; +import com.google.firebase.messaging.FirebaseMessagingException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@RequiredArgsConstructor +@Transactional(readOnly = true) +@Service +public class MajorNoticeService { + + private final FastApiClient fastApiClient; + private final MajorNoticeRepository majorNoticeRepository; + private final MemberRepository memberRepository; + private final FcmService fcmService; + + @Transactional + public void syncMajorNotices() { + FastAPIMajorNoticesResponseListDTO response = fastApiClient.fetchMajorNotices(); + + List changeMajorNotices = new ArrayList<>(); + + if(response != null && response.getNotices() != null) { + for(MajorNoticeResponseDTO dto : response.getNotices()) { + Optional findNotice = majorNoticeRepository.findByNoticeId(dto.getNoticeId()); + if(findNotice.isEmpty()) { + MajorNotice newMajorNotice = new MajorNotice(dto); + majorNoticeRepository.save(newMajorNotice); + changeMajorNotices.add(newMajorNotice); + } else { + MajorNotice existingMajorNotice = findNotice.get(); + if (existingMajorNotice.updateFromDto(dto)) { + majorNoticeRepository.save(existingMajorNotice); + changeMajorNotices.add(existingMajorNotice); + } + } + } + } + if(!changeMajorNotices.isEmpty()) { + // fcm 기능 구현 + System.out.println("알림 전송"); + List members = memberRepository.findByAlarmMajorNoticeTrue(); + + if(!members.isEmpty()) { + String title = "전공 공지사항"; + String body = "새로운 컴퓨터공학부 공지사항 글이 등록되었습니다."; + + for(Member member : members) { + if(!member.getFcmTokens().isEmpty()) { + member.getFcmTokens().forEach(fcmToken -> { + if(fcmToken.isValid() && fcmToken.getToken() != null + && !fcmToken.getToken().isBlank()) { + try { + fcmService.sendMessageToToken(fcmToken.getToken(), title,body); + } catch(FirebaseMessagingException e) { // 예외처리 이후 확인 + System.out.println("전송 실패"); + } + } + }); + } + } + } + } + } + + public List getAllMajorNotices() { + return majorNoticeRepository.findAll() + .stream() + .map(MajorNoticeResponseDTO::of) + .toList(); + } + + public PageMajorNoticeResponseDTO getMajorNoticesPage(int page, int size) { + List allNotices = majorNoticeRepository.findAllOrderedById(); + + + long totalElements = allNotices.size(); + int totalPages = (int) Math.ceil((double) totalElements / size); + + if (page < 1) { + page = 1; + } else if (page > totalPages && totalPages > 0) { + page = totalPages; + } + + int zeroBasedPage = page - 1; + int startIndex = zeroBasedPage * size; + int endIndex = Math.min(startIndex + size, (int) totalElements); + + List pageList = (startIndex < endIndex) + ? allNotices.subList(startIndex, endIndex) + : Collections.emptyList(); + + List content = pageList.stream() + .map(MajorNoticeResponseDTO::of) + .collect(Collectors.toList()); + + return PageMajorNoticeResponseDTO.builder() + .currentPage(page) + .totalPages(totalPages) + .totalElements(totalElements) + .size(size) + .content(content) + .build(); + } +} diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/majorQuestion/.DS_Store b/src/main/java/com/ComNCheck/ComNCheck/domain/majorQuestion/.DS_Store new file mode 100644 index 0000000..6af873e Binary files /dev/null and b/src/main/java/com/ComNCheck/ComNCheck/domain/majorQuestion/.DS_Store differ diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/majorQuestion/controller/AnswerController.java b/src/main/java/com/ComNCheck/ComNCheck/domain/majorQuestion/controller/AnswerController.java index 3314515..3abff5b 100644 --- a/src/main/java/com/ComNCheck/ComNCheck/domain/majorQuestion/controller/AnswerController.java +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/majorQuestion/controller/AnswerController.java @@ -1,11 +1,14 @@ package com.ComNCheck.ComNCheck.domain.majorQuestion.controller; import com.ComNCheck.ComNCheck.domain.majorQuestion.model.dto.request.AnswerRequestDTO; +import com.ComNCheck.ComNCheck.domain.majorQuestion.model.dto.request.AnswerUpdateRequestDTO; import com.ComNCheck.ComNCheck.domain.majorQuestion.model.dto.response.AnswerResponseDTO; import com.ComNCheck.ComNCheck.domain.majorQuestion.service.AnswerService; import com.ComNCheck.ComNCheck.domain.security.oauth.CustomOAuth2Member; +import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.*; @@ -16,33 +19,40 @@ public class AnswerController { private final AnswerService answerService; @PostMapping + @Operation(summary = "FAQ 댓글 작성", description = "학생회권한만 댓글을 달 수 있다.") public ResponseEntity createOrUpdateAnswer( @RequestBody AnswerRequestDTO requestDTO, Authentication authentication ) { CustomOAuth2Member principal = (CustomOAuth2Member) authentication.getPrincipal(); Long writerId = principal.getMemberDTO().getMemberId(); - requestDTO.setWriterId(writerId); - AnswerResponseDTO responseDTO = answerService.createOrUpdateAnswer(requestDTO); + AnswerResponseDTO responseDTO = answerService.createOrUpdateAnswer(requestDTO, writerId); return ResponseEntity.ok(responseDTO); } + @PutMapping("/{answerId}") + @Operation(summary = "FAQ 댓글 수정", description = "학생회권한만 댓글 작성자의 관계없이 댓글 수정이 가능하다.") public ResponseEntity updateAnswer( @PathVariable Long answerId, - @RequestBody String content, + @RequestBody AnswerUpdateRequestDTO answerUpdateRequestDTO, Authentication authentication ) { - AnswerResponseDTO responseDTO = answerService.updateAnswer(answerId, content); + CustomOAuth2Member principal = (CustomOAuth2Member) authentication.getPrincipal(); + Long writerId = principal.getMemberDTO().getMemberId(); + AnswerResponseDTO responseDTO = answerService.updateAnswer(answerId, answerUpdateRequestDTO.getContent(), writerId); return ResponseEntity.ok(responseDTO); } @DeleteMapping("/{answerId}") + @Operation(summary = "FAQ 댓글 삭제", description = "학생회권한만 작성자의 관계없이 댓글 삭제가 가능하다") public ResponseEntity deleteAnswer( @PathVariable Long answerId, Authentication authentication ) { - answerService.deleteAnswer(answerId); + CustomOAuth2Member principal = (CustomOAuth2Member) authentication.getPrincipal(); + Long writerId = principal.getMemberDTO().getMemberId(); + answerService.deleteAnswer(answerId, writerId); return ResponseEntity.noContent().build(); } } diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/majorQuestion/controller/QuestionController.java b/src/main/java/com/ComNCheck/ComNCheck/domain/majorQuestion/controller/QuestionController.java index 3d7c761..7fea237 100644 --- a/src/main/java/com/ComNCheck/ComNCheck/domain/majorQuestion/controller/QuestionController.java +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/majorQuestion/controller/QuestionController.java @@ -4,9 +4,9 @@ import com.ComNCheck.ComNCheck.domain.majorQuestion.model.dto.response.QuestionResponseDTO; import com.ComNCheck.ComNCheck.domain.majorQuestion.service.QuestionService; import com.ComNCheck.ComNCheck.domain.security.oauth.CustomOAuth2Member; +import io.swagger.v3.oas.annotations.Operation; import java.net.URI; import java.util.List; -import lombok.Getter; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.security.core.Authentication; @@ -27,30 +27,51 @@ public class QuestionController { private final QuestionService questionService; + @PostMapping - public ResponseEntity createQuestion(@RequestBody QuestionRequestDTO requestDTO) { - QuestionResponseDTO responseDTO = questionService.createQuestion(requestDTO); + @Operation(summary = "FAQ 게시글 작성", description = "학생회에게 질문글을 작성할 수 있다.") + public ResponseEntity createQuestion(@RequestBody QuestionRequestDTO requestDTO, + Authentication authentication) { + CustomOAuth2Member principal = (CustomOAuth2Member) authentication.getPrincipal(); + Long memberId = principal.getMemberDTO().getMemberId(); + QuestionResponseDTO responseDTO = questionService.createQuestion(requestDTO, memberId); URI location = ServletUriComponentsBuilder.fromCurrentRequest() - .path("/{id}") - .buildAndExpand(responseDTO.getId()) + .path("/{majorQuestionId}") + .buildAndExpand(responseDTO.getMajorQuestionId()) .toUri(); + return ResponseEntity.created(location).body(responseDTO); } - @GetMapping("/{id}") - public ResponseEntity getQuestion(@PathVariable Long id) { - QuestionResponseDTO responseDTO = questionService.getQuestion(id); + @GetMapping("/all") + @Operation(summary = "FAQ 모든 게시글 - 학생회만 열람 가능", description = "댓글 작성 여부, 공개 여부오 상관없는 모든 질문들") + public ResponseEntity> getAllQuestion( + Authentication authentication + ) { + CustomOAuth2Member principal = (CustomOAuth2Member) authentication.getPrincipal(); + Long memberId = principal.getMemberDTO().getMemberId(); + List questions = questionService.getAllQuestion(memberId); + return ResponseEntity.ok(questions); + } + + @GetMapping("/{majorQuestionId}") + @Operation(summary = "FAQ 특정 게시글 조회", description = "FAQ의 특정 게시글을 클릭했을 때 자세히 볼 수 있다") + public ResponseEntity getQuestion(@PathVariable Long majorQuestionId) { + QuestionResponseDTO responseDTO = questionService.getQuestion(majorQuestionId); return ResponseEntity.ok(responseDTO); } @GetMapping - public ResponseEntity> getAllQuestion() { - List questions = questionService.getAllQuestions(); + @Operation(summary = "FAQ의 답변이 달린 게시글 목록 조회 공유가 true 인 경우만" + , description = "댓글이 달린 모든 게시글 목록을 조회한다.") + public ResponseEntity> getAnsweredAllQuestions() { + List questions = questionService.getQuestionsWithAnswer(); return ResponseEntity.ok(questions); } @GetMapping("/my") + @Operation(summary = "내가 작성한 FAQ 게시글 목록 조회", description = "내가 작성한 FAQ 게시글 목록을 조회한다") public ResponseEntity> getMyQuestions(Authentication authentication) { CustomOAuth2Member principal = (CustomOAuth2Member) authentication.getPrincipal(); Long writerId = principal.getMemberDTO().getMemberId(); @@ -58,23 +79,34 @@ public ResponseEntity> getMyQuestions(Authentication a return ResponseEntity.ok(myQuestions); } - @PutMapping("/{id}") - public ResponseEntity updateQuestion(@PathVariable Long id, + @PutMapping("/{majorQuestionId}") + @Operation(summary = "FAQ 게시글 수정", description = "FAQ 게시글을 수정한다. 단, 본인이 작성한 게시글만 가능") + public ResponseEntity updateQuestion(@PathVariable Long majorQuestionId, @RequestBody QuestionRequestDTO requestDTO, Authentication authentication) { CustomOAuth2Member principal = (CustomOAuth2Member) authentication.getPrincipal(); Long memberId = principal.getMemberDTO().getMemberId(); - QuestionResponseDTO updateDTO = questionService.updateQuestion(id, requestDTO, memberId); + QuestionResponseDTO updateDTO = questionService.updateQuestion(majorQuestionId, requestDTO, memberId); return ResponseEntity.ok(updateDTO); } - @DeleteMapping - public ResponseEntity deleteQuestion(@PathVariable Long id, Authentication authentication) { + @DeleteMapping("/{majorQuestionId}") + @Operation(summary = "FAQ 게시글 삭제 ", description = "FAQ 게시글을 삭제한다. 단, 본인이 작성한 게시글만 가능") + public ResponseEntity deleteQuestion(@PathVariable Long majorQuestionId, Authentication authentication) { CustomOAuth2Member principal = (CustomOAuth2Member) authentication.getPrincipal(); Long memberId = principal.getMemberDTO().getMemberId(); - questionService.deleteQuestion(id, memberId); + questionService.deleteQuestion(majorQuestionId, memberId); return ResponseEntity.noContent().build(); } + @GetMapping("/all/unanswerd") + @Operation(summary = "FAQ 답변이 달리지 않는 게시글 목록 조회", description = "답변을 아직 하지 않느니 게시글 목록을 조회한다.") + public ResponseEntity> getUnansweredAllQuestions(Authentication authentication) { + CustomOAuth2Member principal = (CustomOAuth2Member) authentication.getPrincipal(); + Long memberId = principal.getMemberDTO().getMemberId(); + List questions = questionService.getUnanswerdAllQuestion(memberId); + return ResponseEntity.ok(questions); + + } } diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/majorQuestion/model/.DS_Store b/src/main/java/com/ComNCheck/ComNCheck/domain/majorQuestion/model/.DS_Store new file mode 100644 index 0000000..2417f74 Binary files /dev/null and b/src/main/java/com/ComNCheck/ComNCheck/domain/majorQuestion/model/.DS_Store differ diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/majorQuestion/model/dto/request/AnswerRequestDTO.java b/src/main/java/com/ComNCheck/ComNCheck/domain/majorQuestion/model/dto/request/AnswerRequestDTO.java index 2fb621b..df0d454 100644 --- a/src/main/java/com/ComNCheck/ComNCheck/domain/majorQuestion/model/dto/request/AnswerRequestDTO.java +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/majorQuestion/model/dto/request/AnswerRequestDTO.java @@ -7,7 +7,6 @@ @Setter public class AnswerRequestDTO { private String content; - private Long questionId; - private Long writerId; + private Long majorQuestionId; -} +} \ No newline at end of file diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/majorQuestion/model/dto/request/AnswerUpdateRequestDTO.java b/src/main/java/com/ComNCheck/ComNCheck/domain/majorQuestion/model/dto/request/AnswerUpdateRequestDTO.java new file mode 100644 index 0000000..e22a5d8 --- /dev/null +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/majorQuestion/model/dto/request/AnswerUpdateRequestDTO.java @@ -0,0 +1,11 @@ +package com.ComNCheck.ComNCheck.domain.majorQuestion.model.dto.request; + + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class AnswerUpdateRequestDTO { + private String content; +} diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/majorQuestion/model/dto/request/QuestionRequestDTO.java b/src/main/java/com/ComNCheck/ComNCheck/domain/majorQuestion/model/dto/request/QuestionRequestDTO.java index ae1d984..b5a79e7 100644 --- a/src/main/java/com/ComNCheck/ComNCheck/domain/majorQuestion/model/dto/request/QuestionRequestDTO.java +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/majorQuestion/model/dto/request/QuestionRequestDTO.java @@ -8,6 +8,5 @@ public class QuestionRequestDTO { private String title; private String content; - private Long writerId; - + private boolean shared; } diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/majorQuestion/model/dto/response/AnswerResponseDTO.java b/src/main/java/com/ComNCheck/ComNCheck/domain/majorQuestion/model/dto/response/AnswerResponseDTO.java index 264643f..a115830 100644 --- a/src/main/java/com/ComNCheck/ComNCheck/domain/majorQuestion/model/dto/response/AnswerResponseDTO.java +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/majorQuestion/model/dto/response/AnswerResponseDTO.java @@ -8,18 +8,18 @@ @Getter @Builder public class AnswerResponseDTO { - private Long id; + private Long answerId; private String content; - private Long questionId; + private Long majorQuestionId; private Long writerId; private LocalDateTime createdAt; private LocalDateTime updatedAt; public static AnswerResponseDTO of(Answer answer) { return AnswerResponseDTO.builder() - .id(answer.getId()) + .answerId(answer.getId()) .content(answer.getContent()) - .questionId(answer.getQuestion().getId()) + .majorQuestionId(answer.getQuestion().getId()) .writerId(answer.getWriter().getMemberId()) .createdAt(answer.getCreatedAt()) .updatedAt(answer.getUpdatedAt()) diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/majorQuestion/model/dto/response/QuestionResponseDTO.java b/src/main/java/com/ComNCheck/ComNCheck/domain/majorQuestion/model/dto/response/QuestionResponseDTO.java index 89b7b60..024218b 100644 --- a/src/main/java/com/ComNCheck/ComNCheck/domain/majorQuestion/model/dto/response/QuestionResponseDTO.java +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/majorQuestion/model/dto/response/QuestionResponseDTO.java @@ -8,19 +8,21 @@ @Getter @Builder public class QuestionResponseDTO { - private Long id; + private Long majorQuestionId; private String title; private String content; //private Long writerId; private LocalDateTime createdAt; private LocalDateTime updatedAt; private AnswerResponseDTO answer; + private boolean shared; public static QuestionResponseDTO of(Question question) { return QuestionResponseDTO.builder() - .id(question.getId()) + .majorQuestionId(question.getId()) .title(question.getTitle()) .content(question.getContent()) + .shared(question.isShared()) //.writerId(question.getWriter().getId()) .createdAt(question.getCreatedAt()) .updatedAt(question.getUpdatedAt()) diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/majorQuestion/model/entity/Question.java b/src/main/java/com/ComNCheck/ComNCheck/domain/majorQuestion/model/entity/Question.java index 483b49f..6dcd737 100644 --- a/src/main/java/com/ComNCheck/ComNCheck/domain/majorQuestion/model/entity/Question.java +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/majorQuestion/model/entity/Question.java @@ -1,6 +1,7 @@ package com.ComNCheck.ComNCheck.domain.majorQuestion.model.entity; +import com.ComNCheck.ComNCheck.domain.majorQuestion.model.dto.request.QuestionRequestDTO; import com.ComNCheck.ComNCheck.domain.member.model.entity.Member; import jakarta.persistence.CascadeType; import jakarta.persistence.Column; @@ -37,27 +38,33 @@ public class Question { @Column(nullable = false) private String content; + @Column(nullable = false) + private boolean shared; + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "writer_id") private Member writer; @OneToOne(mappedBy = "question", cascade = CascadeType.ALL, orphanRemoval = true) private Answer answer; + @Column private LocalDateTime createdAt; + @Column private LocalDateTime updatedAt; - /* 연관관계 편의 메서드 */ + public void setAnswer(Answer answer) { this.answer = answer; answer.setQuestion(this); } - public void updateQuestion(String title, String contest) { - this.title = title; - this.content = content; + public void updateQuestion(QuestionRequestDTO dto) { + this.title = dto.getTitle(); + this.content = dto.getContent(); + this.shared = dto.isShared(); this.updatedAt = LocalDateTime.now(); } diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/majorQuestion/repository/QuestionRepository.java b/src/main/java/com/ComNCheck/ComNCheck/domain/majorQuestion/repository/QuestionRepository.java index c4d83c6..0fc301e 100644 --- a/src/main/java/com/ComNCheck/ComNCheck/domain/majorQuestion/repository/QuestionRepository.java +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/majorQuestion/repository/QuestionRepository.java @@ -2,8 +2,18 @@ import com.ComNCheck.ComNCheck.domain.majorQuestion.model.entity.Question; import java.util.List; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; public interface QuestionRepository extends JpaRepository { List findAllByWriterMemberId(Long writerId); + List findByAnswerIsNotNull(); + Optional findByIdAndSharedTrue(Long id); + @Query("SELECT q FROM Question q WHERE q.answer IS NOT NULL AND q.shared = true ORDER BY q.updatedAt DESC") + List findByAnswerIsNotNullAndSharedTrue(); + List findByAnswerIsNull(); + + @Query("SELECT q FROM Question q ORDER BY q.updatedAt DESC") + List findAllOrderedByUpdatedAt(); } diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/majorQuestion/service/AnswerService.java b/src/main/java/com/ComNCheck/ComNCheck/domain/majorQuestion/service/AnswerService.java index 127500c..622eefe 100644 --- a/src/main/java/com/ComNCheck/ComNCheck/domain/majorQuestion/service/AnswerService.java +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/majorQuestion/service/AnswerService.java @@ -1,6 +1,11 @@ package com.ComNCheck.ComNCheck.domain.majorQuestion.service; +import com.ComNCheck.ComNCheck.domain.global.exception.AnswerNotFoundException; +import com.ComNCheck.ComNCheck.domain.global.exception.ForbiddenException; +import com.ComNCheck.ComNCheck.domain.global.exception.MemberNotFoundException; +import com.ComNCheck.ComNCheck.domain.global.exception.PostNotFoundException; import com.ComNCheck.ComNCheck.domain.member.model.entity.Member; +import com.ComNCheck.ComNCheck.domain.member.model.entity.Role; import com.ComNCheck.ComNCheck.domain.member.repository.MemberRepository; import com.ComNCheck.ComNCheck.domain.majorQuestion.model.dto.request.AnswerRequestDTO; import com.ComNCheck.ComNCheck.domain.majorQuestion.model.dto.response.AnswerResponseDTO; @@ -23,13 +28,13 @@ public class AnswerService { @Transactional - public AnswerResponseDTO createOrUpdateAnswer(AnswerRequestDTO requestDTO) { - // Role 체크 로직 - Member writer = memberRepository.findByMemberId(requestDTO.getWriterId()) - .orElseThrow(() -> new IllegalArgumentException("해당 회원이 존재하지 않습니다.")); + public AnswerResponseDTO createOrUpdateAnswer(AnswerRequestDTO requestDTO, Long memberId) { + Member writer = memberRepository.findByMemberId(memberId) + .orElseThrow(() -> new MemberNotFoundException("해당 회원이 존재하지 않습니다.")); + isCheckRole(writer); - Question question = questionRepository.findById(requestDTO.getQuestionId()) - .orElseThrow(() -> new IllegalArgumentException("해당 질문이 존재하지 않습니다.")); + Question question = questionRepository.findById(requestDTO.getMajorQuestionId()) + .orElseThrow(() -> new PostNotFoundException("요청하신 질문이 존재하지 않습니다.")); Answer answer = answerRepository.findByQuestionId(question.getId()).orElse(null); @@ -49,19 +54,32 @@ public AnswerResponseDTO createOrUpdateAnswer(AnswerRequestDTO requestDTO) { } } @Transactional - public AnswerResponseDTO updateAnswer(Long answerId, String content) { + public AnswerResponseDTO updateAnswer(Long answerId, String content, Long memberId) { + Member member = memberRepository.findByMemberId(memberId) + .orElseThrow(() -> new MemberNotFoundException("등록된 회원이 없습니다.")); + isCheckRole(member); Answer answer = answerRepository.findById(answerId) - .orElseThrow(() -> new IllegalArgumentException("해당 답글이 존재하지 않습니다.")); + .orElseThrow(() -> new AnswerNotFoundException("답변이 존재하지 않습니다.")); answer.updateAnswer(content); return AnswerResponseDTO.of(answer); } @Transactional - public void deleteAnswer(Long answerId) { + public void deleteAnswer(Long answerId, Long memberId) { + Member member = memberRepository.findByMemberId(memberId) + .orElseThrow(() -> new MemberNotFoundException("등록된 회원이 없습니다.")); + isCheckRole(member); Answer answer = answerRepository.findById(answerId) - .orElseThrow(() -> new IllegalArgumentException("해당 답글이 존재하지 않습니다.")); + .orElseThrow(() -> new AnswerNotFoundException("답변이 존재하지 않습니다.")); answerRepository.delete(answer); -} + } + + public void isCheckRole(Member member) { + Role checkRole = member.getRole(); + if(checkRole != Role.ROLE_ADMIN && checkRole != Role.ROLE_MAJOR_PRESIDENT && checkRole != Role.ROLE_STUDENT_COUNCIL) { + throw new ForbiddenException("접근 권한이 없습니다."); + } + } } diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/majorQuestion/service/QuestionService.java b/src/main/java/com/ComNCheck/ComNCheck/domain/majorQuestion/service/QuestionService.java index 044b64e..e5b319c 100644 --- a/src/main/java/com/ComNCheck/ComNCheck/domain/majorQuestion/service/QuestionService.java +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/majorQuestion/service/QuestionService.java @@ -1,15 +1,19 @@ package com.ComNCheck.ComNCheck.domain.majorQuestion.service; +import com.ComNCheck.ComNCheck.domain.global.exception.ForbiddenException; +import com.ComNCheck.ComNCheck.domain.global.exception.MemberNotFoundException; +import com.ComNCheck.ComNCheck.domain.global.exception.PostNotFoundException; import com.ComNCheck.ComNCheck.domain.member.model.entity.Member; +import com.ComNCheck.ComNCheck.domain.member.model.entity.Role; import com.ComNCheck.ComNCheck.domain.member.repository.MemberRepository; -import com.ComNCheck.ComNCheck.domain.global.exception.UnauthorizedException; import com.ComNCheck.ComNCheck.domain.majorQuestion.model.dto.request.QuestionRequestDTO; import com.ComNCheck.ComNCheck.domain.majorQuestion.model.dto.response.QuestionResponseDTO; import com.ComNCheck.ComNCheck.domain.majorQuestion.model.entity.Question; import com.ComNCheck.ComNCheck.domain.majorQuestion.repository.QuestionRepository; import java.util.List; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -22,13 +26,14 @@ public class QuestionService { private final MemberRepository memberRepository; @Transactional - public QuestionResponseDTO createQuestion(QuestionRequestDTO requestDTO) { - Member writer = memberRepository.findByMemberId(requestDTO.getWriterId()) - .orElseThrow(() -> new IllegalArgumentException("회원이 존재하지 않습니다.")); + public QuestionResponseDTO createQuestion(QuestionRequestDTO requestDTO, Long memberId) { + Member writer = memberRepository.findByMemberId(memberId) + .orElseThrow(() -> new MemberNotFoundException("회원이 존재하지 않습니다.")); Question question = Question.builder() .title(requestDTO.getTitle()) .content(requestDTO.getContent()) + .shared(requestDTO.isShared()) .writer(writer) .build(); @@ -36,14 +41,26 @@ public QuestionResponseDTO createQuestion(QuestionRequestDTO requestDTO) { return QuestionResponseDTO.of(saveQuestion); } + public List getAllQuestion(Long memberId) { + Member member = memberRepository.findByMemberId(memberId) + .orElseThrow(() -> new MemberNotFoundException("회원이 존재하지 않습니다.")); + isCheckRole(member); + + return questionRepository.findAllOrderedByUpdatedAt() + .stream() + .map(QuestionResponseDTO::of) + .toList(); + } + public QuestionResponseDTO getQuestion(Long questionId) { Question question = questionRepository.findById(questionId) - .orElseThrow(() -> new IllegalArgumentException("해당 질문이 존재하지 않습니다.")); + .orElseThrow(() -> new PostNotFoundException("질문이 존재하지 않습니다.")); return QuestionResponseDTO.of(question); } - public List getAllQuestions() { - return questionRepository.findAll() + public List getQuestionsWithAnswer() { + + return questionRepository.findByAnswerIsNotNullAndSharedTrue() .stream() .map(QuestionResponseDTO::of) .toList(); @@ -52,23 +69,23 @@ public List getAllQuestions() { @Transactional public QuestionResponseDTO updateQuestion(Long questionId, QuestionRequestDTO requestDTO, Long writerId) { Question question = questionRepository.findById(questionId) - .orElseThrow(() -> new IllegalArgumentException("해당 질문이 존재하지 않습니다.")); + .orElseThrow(() -> new PostNotFoundException("질문이 존재하지 않습니다.")); if (!question.getWriter().getMemberId().equals(writerId)) { - throw new UnauthorizedException("작성자가 아닙니다."); + throw new ForbiddenException("작성자가 아닙니다."); } - question.updateQuestion(requestDTO.getTitle(), requestDTO.getContent()); + question.updateQuestion(requestDTO); return QuestionResponseDTO.of(question); } @Transactional public void deleteQuestion(Long questionId, Long writerId) { Question question = questionRepository.findById(questionId) - .orElseThrow(() -> new IllegalArgumentException("해당 질문이 존재하지 않습니다.")); + .orElseThrow(() -> new PostNotFoundException("해당 질문이 존재하지 않습니다.")); - if(question.getWriter().getMemberId().equals(writerId)) { - throw new UnauthorizedException("작성자가 아닙니다."); + if(!question.getWriter().getMemberId().equals(writerId)) { + throw new ForbiddenException("작성자가 아닙니다."); } questionRepository.delete(question); } @@ -79,4 +96,22 @@ public List getMyQuestions(Long writerId) { .map(QuestionResponseDTO::of) .toList(); } + + @Transactional + public List getUnanswerdAllQuestion(Long memberId) { + Member member = memberRepository.findByMemberId(memberId) + .orElseThrow(() -> new MemberNotFoundException("회원이 존재하지 않습니다.")); + isCheckRole(member); + return questionRepository.findByAnswerIsNull() + .stream() + .map(QuestionResponseDTO::of) + .toList(); + } + + public void isCheckRole(Member member) { + Role checkRole = member.getRole(); + if(checkRole != Role.ROLE_ADMIN && checkRole != Role.ROLE_MAJOR_PRESIDENT && checkRole != Role.ROLE_STUDENT_COUNCIL) { + throw new ForbiddenException("접근 권한이 없습니다."); + } + } } diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/member/.DS_Store b/src/main/java/com/ComNCheck/ComNCheck/domain/member/.DS_Store new file mode 100644 index 0000000..10fdb6e Binary files /dev/null and b/src/main/java/com/ComNCheck/ComNCheck/domain/member/.DS_Store differ diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/member/controller/MemberController.java b/src/main/java/com/ComNCheck/ComNCheck/domain/member/controller/MemberController.java index 6a89151..4585e61 100644 --- a/src/main/java/com/ComNCheck/ComNCheck/domain/member/controller/MemberController.java +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/member/controller/MemberController.java @@ -1,9 +1,14 @@ package com.ComNCheck.ComNCheck.domain.member.controller; -import com.ComNCheck.ComNCheck.domain.member.model.dto.response.MemberDTO; +import com.ComNCheck.ComNCheck.domain.member.model.dto.response.MemberInformationResponseDTO; import com.ComNCheck.ComNCheck.domain.member.model.dto.response.PresidentCouncilResponseDTO; +import com.ComNCheck.ComNCheck.domain.member.service.CustomOAuthMemberService; import com.ComNCheck.ComNCheck.domain.member.service.MemberService; +import com.ComNCheck.ComNCheck.domain.security.handler.CustomSuccessHandler; import com.ComNCheck.ComNCheck.domain.security.oauth.CustomOAuth2Member; +import io.swagger.v3.oas.annotations.Operation; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.security.core.Authentication; @@ -20,27 +25,66 @@ @RequestMapping("/api/v1/member") public class MemberController { private final MemberService memberService; + private final CustomSuccessHandler customSuccessHandler; - @PostMapping("/{memberId}/student/number") - public ResponseEntity registerStudentNumber( - @PathVariable Long memberId, - @RequestParam("studentCardImage") MultipartFile studentCardImage) { - MemberDTO responseDTO = memberService.registerStudentNumber(memberId, studentCardImage); + @PostMapping("/student/number") + @Operation(summary = "학번 등록", description = "모바일 학생증으로 학번을 등록한다.") + public ResponseEntity registerStudentNumber( + @RequestParam("studentCardImage") MultipartFile studentCardImage, + Authentication authentication) { + CustomOAuth2Member principal = (CustomOAuth2Member) authentication.getPrincipal(); + Long memberId = principal.getMemberDTO().getMemberId(); + MemberInformationResponseDTO responseDTO = memberService.registerStudentNumber(memberId, studentCardImage); return ResponseEntity.ok(responseDTO); } @GetMapping("/members/president-council") + @Operation(summary = "학생회 목록 조회", description = "학생회 목록을 조회한다.") public ResponseEntity getPresidentCouncilList() { PresidentCouncilResponseDTO responseDTO = memberService.getPresidentAndCouncils(); return ResponseEntity.ok(responseDTO); } @GetMapping - public ResponseEntity getMemberInformation(Authentication authentication) { + @Operation(summary = "본인 정보 조회", description = "로그인 이후, 학번 변동 이후 본인 정보를 조회한다.") + public ResponseEntity getMemberInformation(Authentication authentication) { CustomOAuth2Member principal = (CustomOAuth2Member) authentication.getPrincipal(); Long memberId = principal.getMemberDTO().getMemberId(); - MemberDTO responseDTO = memberService.getMemberInformation(memberId); + MemberInformationResponseDTO responseDTO = memberService.getMemberInformation(memberId); return ResponseEntity.ok(responseDTO); } + @PostMapping("/logout") + @Operation(summary = "로그아웃", description = "쿠키의 jwt를 강제로 만료시켜 로그아웃 시킨다.") + public ResponseEntity logout(HttpServletRequest request, HttpServletResponse response) { + customSuccessHandler.clearAuthenticationSuccess(request, response); + return ResponseEntity.ok("로그아웃 성공"); + } + + @PostMapping("/alarm/major/events") + @Operation(summary = "과행사 알람 켜기 및 끄기", description = "과행사 알람이 오는 것을 켜거나 끌 수 있다.") + public ResponseEntity changeAlarmMajorEvent(Authentication authentication) { + CustomOAuth2Member principal = (CustomOAuth2Member) authentication.getPrincipal(); + Long memberId = principal.getMemberDTO().getMemberId(); + memberService.changeAlarmMajorEvent(memberId); + return ResponseEntity.ok("과행사 알람 변경"); + } + + @PostMapping("/alarm/major/notices") + @Operation(summary = "과공지 알람 켜기 및 끄기", description = "과행사 알람이 오는 것을 켜거나 끌 수 있다.") + public ResponseEntity changeAlarmMajorNotice(Authentication authentication) { + CustomOAuth2Member principal = (CustomOAuth2Member) authentication.getPrincipal(); + Long memberId = principal.getMemberDTO().getMemberId(); + memberService.changeAlarmMajorNotice(memberId); + return ResponseEntity.ok("과행사 알람 변경"); + } + + @PostMapping("/alarm/employment/notices") + @Operation(summary = "과 취업정보 알람 켜기 및 끄기", description = "과행사 알람이 오는 것을 켜거나 끌 수 있다.") + public ResponseEntity changeAlarmEmploymentNotice(Authentication authentication) { + CustomOAuth2Member principal = (CustomOAuth2Member) authentication.getPrincipal(); + Long memberId = principal.getMemberDTO().getMemberId(); + memberService.changeAlarmEmploymentNotice(memberId); + return ResponseEntity.ok("과행사 알람 변경"); + } } diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/member/model/.DS_Store b/src/main/java/com/ComNCheck/ComNCheck/domain/member/model/.DS_Store new file mode 100644 index 0000000..f882310 Binary files /dev/null and b/src/main/java/com/ComNCheck/ComNCheck/domain/member/model/.DS_Store differ diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/member/model/dto/response/MemberDTO.java b/src/main/java/com/ComNCheck/ComNCheck/domain/member/model/dto/response/MemberDTO.java index 6a1d602..f794d96 100644 --- a/src/main/java/com/ComNCheck/ComNCheck/domain/member/model/dto/response/MemberDTO.java +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/member/model/dto/response/MemberDTO.java @@ -18,6 +18,7 @@ public class MemberDTO { private String major; private int studentNumber; private Role role; + private boolean checkStudentCard; public MemberDTO() { } @@ -30,6 +31,8 @@ public static MemberDTO of(Member member) { .major(member.getMajor()) .studentNumber(member.getStudentNumber()) .role(member.getRole()) + .checkStudentCard(member.isCheckStudentCard()) .build(); } + } diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/member/model/dto/response/MemberInformationResponseDTO.java b/src/main/java/com/ComNCheck/ComNCheck/domain/member/model/dto/response/MemberInformationResponseDTO.java new file mode 100644 index 0000000..2c5727f --- /dev/null +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/member/model/dto/response/MemberInformationResponseDTO.java @@ -0,0 +1,35 @@ +package com.ComNCheck.ComNCheck.domain.member.model.dto.response; + +import com.ComNCheck.ComNCheck.domain.member.model.entity.Member; + +import com.ComNCheck.ComNCheck.domain.member.model.entity.Role; +import lombok.Builder; +import lombok.Getter; + +@Builder +@Getter +public class MemberInformationResponseDTO { + + private Long memberId; + private String name; + private String major; + private int studentNumber; + private Role role; + private boolean checkStudentCard; + private boolean alarmMajorEvent; + private boolean alarmMajorNotice; + private boolean alarmEmploymentNotice; + public static MemberInformationResponseDTO of(Member member) { + return MemberInformationResponseDTO.builder() + .memberId(member.getMemberId()) + .name(member.getName()) + .major(member.getMajor()) + .studentNumber(member.getStudentNumber()) + .role(member.getRole()) + .checkStudentCard(member.isCheckStudentCard()) + .alarmMajorEvent(member.isAlarmMajorEvent()) + .alarmMajorNotice(member.isAlarmMajorNotice()) + .alarmEmploymentNotice(member.isAlarmEmploymentNotice()) + .build(); + } +} diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/member/model/entity/Member.java b/src/main/java/com/ComNCheck/ComNCheck/domain/member/model/entity/Member.java index 2184e3b..a9d2f4c 100644 --- a/src/main/java/com/ComNCheck/ComNCheck/domain/member/model/entity/Member.java +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/member/model/entity/Member.java @@ -1,11 +1,15 @@ package com.ComNCheck.ComNCheck.domain.member.model.entity; +import com.ComNCheck.ComNCheck.domain.fcm.model.entity.FcmToken; import jakarta.persistence.*; +import java.util.ArrayList; +import java.util.List; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.Setter; @Getter @@ -24,10 +28,12 @@ public class Member { @Column(name = "name", nullable = false) private String name; - @Column(name = "major", nullable = false) + // 실제 배포에서는 nullable 설정 false 해야함 + @Column(name = "major", nullable = true) private String major; - @Column(name = "studnet_number", nullable = false) + @Column(name = "student_number") + @Setter private int studentNumber; @Column(name = "member_role", nullable = false) @@ -37,6 +43,21 @@ public class Member { @Column(name = "position") private String position; + @Column(name = "check_student_card", nullable = false) + private boolean checkStudentCard; + + @Column(name = "alarm_major_event", nullable = false) + private boolean alarmMajorEvent; + + @Column(name = "alarm_major_notice", nullable = false) + private boolean alarmMajorNotice; + + @Column(name = "alarm_employment_notice") + private boolean alarmEmploymentNotice; + + @OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true) + private List fcmTokens = new ArrayList<>(); + @Builder public Member(Long memberId, String email, String name, String major, int studentNumber, Role role) { this.memberId = memberId; @@ -46,20 +67,43 @@ public Member(Long memberId, String email, String name, String major, int studen this.studentNumber = studentNumber; this.position = null; this.role = role; + this.checkStudentCard = false; + this.alarmMajorNotice = false; + this.alarmMajorEvent = false; + this.alarmEmploymentNotice = false; } - /* - setter code - 어노테이션으로 안하고 필요한 경우만 setter 설정 - */ - public void setStudentNumber(int studentNumber) { - this.studentNumber = studentNumber; - } public void updatePosition(String requestPosition) { this.position = requestPosition; } public void updateRole(Role newRole) { this.role = newRole; } + public void addFcmToken(FcmToken token) { + this.fcmTokens.add(token); + token.setMember(this); + } + public void changeCheckStudentCard() { + this.checkStudentCard = true; + } + public void onAlarmMajorEvent() { + this.alarmMajorEvent = true; + } + public void offAlarmMajorEvent() { + this.alarmMajorEvent = false; + } + public void onAlarmMajorNotice() { + this.alarmMajorNotice = true; + } + public void offAlarmMajorNotice() { + this.alarmMajorNotice = false; + } + public void onAlarmEmploymentNotice() { + this.alarmEmploymentNotice = true; + } + public void offAlarmEmploymentNotice() { + this.alarmEmploymentNotice = false; + } + } diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/member/model/entity/Role.java b/src/main/java/com/ComNCheck/ComNCheck/domain/member/model/entity/Role.java index 938f7d6..b75fc9f 100644 --- a/src/main/java/com/ComNCheck/ComNCheck/domain/member/model/entity/Role.java +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/member/model/entity/Role.java @@ -12,6 +12,7 @@ public enum Role { ROLE_STUDENT_COUNCIL("ROLE_STUDENT_COUNCIL"), ROLE_STUDENT("ROLE_STUDENT"), ROLE_GRADUATE_STUDENT("ROLE_GRADUATE_STUDENT"); + private final String value; Role(String value) { diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/member/repository/MemberRepository.java b/src/main/java/com/ComNCheck/ComNCheck/domain/member/repository/MemberRepository.java index 1e9dbd3..f1f025e 100644 --- a/src/main/java/com/ComNCheck/ComNCheck/domain/member/repository/MemberRepository.java +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/member/repository/MemberRepository.java @@ -14,5 +14,8 @@ public interface MemberRepository extends JpaRepository { Optional findByRole(Role role); List findAllByRole(Role role); + List findByAlarmMajorNoticeTrue(); + List findByAlarmEmploymentNoticeTrue(); + List findByAlarmMajorEventTrue(); } diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/member/service/CustomOAuthMemberService.java b/src/main/java/com/ComNCheck/ComNCheck/domain/member/service/CustomOAuthMemberService.java index 9bac162..4da58a3 100644 --- a/src/main/java/com/ComNCheck/ComNCheck/domain/member/service/CustomOAuthMemberService.java +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/member/service/CustomOAuthMemberService.java @@ -21,48 +21,52 @@ public class CustomOAuthMemberService extends DefaultOAuth2UserService { private final MemberRepository memberRepository; + private final static String ADMIN_EMAIL_1 = "comncheck0306@gmail.com"; + private final static String ADMIN_EMAIL_2 = "another0306@gmail.com"; + @Override @Transactional public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { OAuth2User oAuth2User = super.loadUser(userRequest); - System.out.println( "oAuthUser 정보"+ oAuth2User); String email = oAuth2User.getAttribute("email"); String name = extractName(oAuth2User.getAttribute("name")); String major = extractMajor(oAuth2User.getAttribute("name")); //String sub = oAuth2User.getAttribute("sub"); 이메일 변경 여부 따지고 변경될경우 findByEmail 대신 findBySub 사용 String hd = oAuth2User.getAttribute("hd"); - if (!"hufs.ac.kr".equals(hd)) { + if (!isAllowedUser(email, hd)) { OAuth2Error oauth2Error = new OAuth2Error( "invalid_hosted_domain", - "허용되지 않은 호스팅 도메인입니다.", - null + "허용되지 않은 호스팅 도메인 혹은 계정입니다.", + "https://www.comncheck.com/login?error=invalid_domain" ); throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); } - // 이메일 변경 가능시 sub 변 + // 이메일 변경 가능시 sub 변수 Member member = memberRepository.findByEmail(email).orElseGet(() -> { Member newMember = Member.builder() .email(email) .name(name) .major(major) .role(Role.ROLE_STUDENT) - .studentNumber(12345678)// 난수 변수 값 만드는 메서드 만들어야함 + .studentNumber(123456789) .build(); memberRepository.save(newMember); return newMember; - }); - - MemberDTO memberDTO = new MemberDTO(); - memberDTO.setMemberId(member.getMemberId()); - memberDTO.setEmail(member.getEmail()); - memberDTO.setName(member.getName()); - memberDTO.setMajor(member.getMajor()); - memberDTO.setRole(member.getRole()); - memberDTO.setStudentNumber(member.getStudentNumber()); - return new CustomOAuth2Member(memberDTO); + }); + + return new CustomOAuth2Member(MemberDTO.of(member)); + } + + private boolean isAllowedUser(String email, String hd) { + if ("hufs.ac.kr".equals(hd) + || ADMIN_EMAIL_1.equals(email) + || ADMIN_EMAIL_2.equals(email)) { + return true; + } + return false; } private String cleanString(String input) { diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/member/service/MemberService.java b/src/main/java/com/ComNCheck/ComNCheck/domain/member/service/MemberService.java index da8b9b9..b062af5 100644 --- a/src/main/java/com/ComNCheck/ComNCheck/domain/member/service/MemberService.java +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/member/service/MemberService.java @@ -1,16 +1,20 @@ package com.ComNCheck.ComNCheck.domain.member.service; -import com.ComNCheck.ComNCheck.domain.member.exception.ValidationException; +import com.ComNCheck.ComNCheck.domain.global.exception.FastApiException; +import com.ComNCheck.ComNCheck.domain.global.exception.MemberNotFoundException; +import com.ComNCheck.ComNCheck.domain.global.exception.ValidationException; import com.ComNCheck.ComNCheck.domain.global.infrastructure.FastApiClient; import com.ComNCheck.ComNCheck.domain.member.model.dto.response.CouncilDTO; import com.ComNCheck.ComNCheck.domain.member.model.dto.response.FastApiStudentCardDTO; import com.ComNCheck.ComNCheck.domain.member.model.dto.response.FastApiStudentCardDTO.ExtractedText; -import com.ComNCheck.ComNCheck.domain.member.model.dto.response.MemberDTO; +import com.ComNCheck.ComNCheck.domain.member.model.dto.response.MemberInformationResponseDTO; import com.ComNCheck.ComNCheck.domain.member.model.dto.response.PresidentCouncilResponseDTO; import com.ComNCheck.ComNCheck.domain.member.model.dto.response.PresidentDTO; import com.ComNCheck.ComNCheck.domain.member.model.entity.Member; import com.ComNCheck.ComNCheck.domain.member.model.entity.Role; import com.ComNCheck.ComNCheck.domain.member.repository.MemberRepository; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletResponse; import java.util.List; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; @@ -26,9 +30,9 @@ public class MemberService { private final FastApiClient fastApiClient; @Transactional - public MemberDTO registerStudentNumber(Long id, MultipartFile studentCardImage) { + public MemberInformationResponseDTO registerStudentNumber(Long id, MultipartFile studentCardImage) { Member member = memberRepository.findByMemberId(id) - .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 학생입니다.")); + .orElseThrow(() -> new MemberNotFoundException("등록된 회원이 없습니다.")); FastApiStudentCardDTO fastApiResponse = fastApiClient.sendImage(studentCardImage); FastApiStudentCardDTO.ExtractedText extractedText = fastApiResponse.getExtractedText(); @@ -41,9 +45,10 @@ public MemberDTO registerStudentNumber(Long id, MultipartFile studentCardImage) throw new ValidationException("이름 또는 전공이 일치하지 않습니다."); } member.setStudentNumber(studentNumber); + member.changeCheckStudentCard(); Member savedMember = memberRepository.save(member); - return MemberDTO.of(savedMember); + return MemberInformationResponseDTO.of(savedMember); } public PresidentCouncilResponseDTO getPresidentAndCouncils() { @@ -73,14 +78,54 @@ private void validateFastApiResponse(ExtractedText extractedText) { extractedText.getName() == null || extractedText.getMajor() == null || extractedText.getStudentId() == null) { - throw new ValidationException("FastAPI 응답이 유효하지 않습니다."); + throw new FastApiException("FastAPI 응답이 유효하지 않습니다."); } } - public MemberDTO getMemberInformation(Long memberId) { + public MemberInformationResponseDTO getMemberInformation(Long memberId) { Member member = memberRepository.findByMemberId(memberId) - .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 학생입니다.")); - return MemberDTO.of(member); + .orElseThrow(() -> new MemberNotFoundException("등록된 회원이 없습니다.")); + return MemberInformationResponseDTO.of(member); } + + + @Transactional + public void changeAlarmMajorEvent(Long memberId) { + Member member = memberRepository.findByMemberId(memberId) + .orElseThrow(() -> new MemberNotFoundException("등록된 회원이 없습니다.")); + if(member.isAlarmMajorEvent()) { + member.offAlarmMajorEvent(); + } + else { + member.onAlarmMajorEvent(); + } + memberRepository.save(member); + } + + @Transactional + public void changeAlarmMajorNotice(Long memberId) { + Member member = memberRepository.findByMemberId(memberId) + .orElseThrow(() -> new MemberNotFoundException("등록된 회원이 없습니다.")); + if(member.isAlarmMajorNotice()) { + member.offAlarmMajorNotice(); + } + else { + member.onAlarmMajorNotice(); + } + memberRepository.save(member); + } + + @Transactional + public void changeAlarmEmploymentNotice(Long memberId) { + Member member = memberRepository.findByMemberId(memberId) + .orElseThrow(() -> new MemberNotFoundException("등록된 회원이 없습니다.")); + if(member.isAlarmEmploymentNotice()) { + member.offAlarmEmploymentNotice(); + } + else { + member.onAlarmEmploymentNotice(); + } + memberRepository.save(member); + } } diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/roleChange/.DS_Store b/src/main/java/com/ComNCheck/ComNCheck/domain/roleChange/.DS_Store new file mode 100644 index 0000000..3606361 Binary files /dev/null and b/src/main/java/com/ComNCheck/ComNCheck/domain/roleChange/.DS_Store differ diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/roleChange/controller/RoleChangeController.java b/src/main/java/com/ComNCheck/ComNCheck/domain/roleChange/controller/RoleChangeController.java index 3ced13f..314157d 100644 --- a/src/main/java/com/ComNCheck/ComNCheck/domain/roleChange/controller/RoleChangeController.java +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/roleChange/controller/RoleChangeController.java @@ -1,13 +1,19 @@ package com.ComNCheck.ComNCheck.domain.roleChange.controller; +import com.ComNCheck.ComNCheck.domain.member.model.entity.Role; import com.ComNCheck.ComNCheck.domain.roleChange.model.dto.request.RoleChangeRequestDTO; +import com.ComNCheck.ComNCheck.domain.roleChange.model.dto.request.RoleChangeUpdateRequestDTO; import com.ComNCheck.ComNCheck.domain.roleChange.model.dto.response.ApprovedRoleListDTO; import com.ComNCheck.ComNCheck.domain.roleChange.model.dto.response.RoleChangeListDTO; import com.ComNCheck.ComNCheck.domain.roleChange.model.dto.response.RoleChangeResponseDTO; import com.ComNCheck.ComNCheck.domain.roleChange.service.RoleChangeRequestService; +import com.ComNCheck.ComNCheck.domain.security.oauth.CustomOAuth2Member; +import io.swagger.v3.oas.annotations.Operation; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -27,50 +33,68 @@ public class RoleChangeController { private final RoleChangeRequestService roleChangeRequestService; @PostMapping + @Operation(summary = "학생회 등급 신청", description = "학생회 인원이 학생회 등급으로 신청한다") public ResponseEntity createRoleChangeRequest( - @RequestBody RoleChangeRequestDTO requestDTO) { - RoleChangeResponseDTO createDTO = roleChangeRequestService.createRoleChangeRequest(requestDTO); + @RequestBody RoleChangeRequestDTO requestDTO, + Authentication authentication) { + CustomOAuth2Member principal = (CustomOAuth2Member) authentication.getPrincipal(); + Long memberId = principal.getMemberDTO().getMemberId(); + RoleChangeResponseDTO createDTO = roleChangeRequestService.createRoleChangeRequest(memberId, requestDTO); URI location = URI.create("api/role-change-requests/" + createDTO.getRequestId()); return ResponseEntity.created(location).body(createDTO); } @GetMapping - public ResponseEntity> getAllRequest() { - List response = roleChangeRequestService.getAllRequests(); + @Operation(summary = "학생회 등급 신청 목록 조회", description = "학생회 등급 신청 목록을 조회한다.") + public ResponseEntity> getAllRequest(Authentication authentication) { + CustomOAuth2Member principal = (CustomOAuth2Member) authentication.getPrincipal(); + Long memberId = principal.getMemberDTO().getMemberId(); + List response = roleChangeRequestService.getAllRequests(memberId); return ResponseEntity.ok(response); } + @GetMapping("/{requestId}") - public ResponseEntity getRequestDetail(@PathVariable Long requestId) { - RoleChangeResponseDTO responseDTO = roleChangeRequestService.getRequestDetail(requestId); + @Operation(summary = "특정 학생회 등급 신청 조회", description = "특정 학생회 등급 신청을 조회한다.") + public ResponseEntity getRequestDetail(@PathVariable Long requestId, + Authentication authentication) { + CustomOAuth2Member principal = (CustomOAuth2Member) authentication.getPrincipal(); + Long memberId = principal.getMemberDTO().getMemberId(); + RoleChangeResponseDTO responseDTO = roleChangeRequestService.getRequestDetail(requestId, memberId); return ResponseEntity.ok(responseDTO); } @PostMapping("{requestId}/approve") - public ResponseEntity approveRequest(@PathVariable Long requestId) { - roleChangeRequestService.approveRequest(requestId); + @Operation(summary = "학생회 등급 신청 승인", description = "과회장 or 관리자가 학생회 등급 신청을 승인한다.") + public ResponseEntity approveRequest(@PathVariable Long requestId + , Authentication authentication) { + CustomOAuth2Member principal = (CustomOAuth2Member) authentication.getPrincipal(); + Long memberId = principal.getMemberDTO().getMemberId(); + roleChangeRequestService.approveRequest(requestId, memberId); return ResponseEntity.ok("승인완료"); } - @GetMapping("/approved") - public ResponseEntity> getApprovedRequests() { - List approvedList = roleChangeRequestService.getApproveRequests(); - return ResponseEntity.ok(approvedList); - } - - @PutMapping("/{requestId}/change-role") - public ResponseEntity changeMemberRole( - @PathVariable Long requestId, @RequestBody RoleChangeRequestDTO requestDTO - ) { - roleChangeRequestService.changeMemberRole(requestId, requestDTO); - return ResponseEntity.ok("등급 재변경 완료"); - } - @DeleteMapping("/{requestId}") - public ResponseEntity deleteRequest(@PathVariable Long requestId) { - roleChangeRequestService.deleteRequest(requestId); + @Operation(summary = "특정 학생회 등급 신청 요청 삭제", description = "요청온 학생회 등급 신청 요청을 삭제한다.") + public ResponseEntity deleteRequest(@PathVariable Long requestId, + Authentication authentication) { + CustomOAuth2Member principal = (CustomOAuth2Member) authentication.getPrincipal(); + Long memberId = principal.getMemberDTO().getMemberId(); + roleChangeRequestService.deleteRequest(requestId, memberId); return ResponseEntity.noContent().build(); } + @PutMapping("/{requestId}") + @Operation(summary = "특정 학생회 직책이나 등급을 변경", description = "과회장이 특정 학생회 인원의 직책이나 등급을 변경한다") + public ResponseEntity putRoleChange(@PathVariable Long requestId, + @RequestBody RoleChangeUpdateRequestDTO roleChangeUpdateRequestDTO, + Authentication authentication) { + CustomOAuth2Member principal = (CustomOAuth2Member) authentication.getPrincipal(); + Long memberId = principal.getMemberDTO().getMemberId(); + String updatePosition = roleChangeUpdateRequestDTO.getRequestPosition(); + Role updateRole = roleChangeUpdateRequestDTO.getRequestRole(); + RoleChangeResponseDTO responseDTO = roleChangeRequestService.updateRoleChange(requestId, memberId, updatePosition, updateRole); + return ResponseEntity.ok(responseDTO); + } } diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/roleChange/model/.DS_Store b/src/main/java/com/ComNCheck/ComNCheck/domain/roleChange/model/.DS_Store new file mode 100644 index 0000000..3187d2a Binary files /dev/null and b/src/main/java/com/ComNCheck/ComNCheck/domain/roleChange/model/.DS_Store differ diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/roleChange/model/dto/request/RoleChangeRequestDTO.java b/src/main/java/com/ComNCheck/ComNCheck/domain/roleChange/model/dto/request/RoleChangeRequestDTO.java index 64cc240..be91ed4 100644 --- a/src/main/java/com/ComNCheck/ComNCheck/domain/roleChange/model/dto/request/RoleChangeRequestDTO.java +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/roleChange/model/dto/request/RoleChangeRequestDTO.java @@ -7,7 +7,6 @@ @Getter @Setter public class RoleChangeRequestDTO { - private Long memberId; private String name; private int studentNumber; private String major; diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/roleChange/model/dto/request/RoleChangeUpdateRequestDTO.java b/src/main/java/com/ComNCheck/ComNCheck/domain/roleChange/model/dto/request/RoleChangeUpdateRequestDTO.java new file mode 100644 index 0000000..5dd4b7a --- /dev/null +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/roleChange/model/dto/request/RoleChangeUpdateRequestDTO.java @@ -0,0 +1,12 @@ +package com.ComNCheck.ComNCheck.domain.roleChange.model.dto.request; + +import com.ComNCheck.ComNCheck.domain.member.model.entity.Role; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class RoleChangeUpdateRequestDTO { + private String requestPosition; + private Role requestRole; +} \ No newline at end of file diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/roleChange/model/dto/response/RoleChangeListDTO.java b/src/main/java/com/ComNCheck/ComNCheck/domain/roleChange/model/dto/response/RoleChangeListDTO.java index 23ee679..884b2fb 100644 --- a/src/main/java/com/ComNCheck/ComNCheck/domain/roleChange/model/dto/response/RoleChangeListDTO.java +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/roleChange/model/dto/response/RoleChangeListDTO.java @@ -1,6 +1,7 @@ package com.ComNCheck.ComNCheck.domain.roleChange.model.dto.response; +import com.ComNCheck.ComNCheck.domain.roleChange.model.entity.RequestStatus; import com.ComNCheck.ComNCheck.domain.roleChange.model.entity.RoleChange; import lombok.Builder; import lombok.Getter; @@ -12,12 +13,14 @@ public class RoleChangeListDTO { private Long requestId; private String name; private String requestPosition; + private RequestStatus status; public static RoleChangeListDTO of(RoleChange roleChange) { return RoleChangeListDTO.builder() .requestId(roleChange.getRequestId()) .name(roleChange.getMember().getName()) .requestPosition(roleChange.getRequestPosition()) + .status(roleChange.getStatus()) .build(); } } diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/roleChange/model/dto/response/RoleChangeResponseDTO.java b/src/main/java/com/ComNCheck/ComNCheck/domain/roleChange/model/dto/response/RoleChangeResponseDTO.java index c027585..e64e9a7 100644 --- a/src/main/java/com/ComNCheck/ComNCheck/domain/roleChange/model/dto/response/RoleChangeResponseDTO.java +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/roleChange/model/dto/response/RoleChangeResponseDTO.java @@ -27,6 +27,7 @@ public static RoleChangeResponseDTO of(RoleChange entity) { .major(entity.getMember().getMajor()) .studentNumber(entity.getMember().getStudentNumber()) .position(entity.getRequestPosition()) + .requestedRole(entity.getRequestRole()) .status(entity.getStatus()) .build(); } diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/roleChange/model/entity/RoleChange.java b/src/main/java/com/ComNCheck/ComNCheck/domain/roleChange/model/entity/RoleChange.java index 11f175d..4f6eb2c 100644 --- a/src/main/java/com/ComNCheck/ComNCheck/domain/roleChange/model/entity/RoleChange.java +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/roleChange/model/entity/RoleChange.java @@ -39,6 +39,7 @@ public class RoleChange { private String requestPosition; @Column(name = "request_role", nullable = false) + @Enumerated(EnumType.STRING) private Role requestRole; @Builder @@ -53,5 +54,10 @@ public void approve() { this.status = RequestStatus.APPROVED; } + public void update(String requestPosition, Role requestRole) { + this.requestPosition = requestPosition; + this.requestRole = requestRole; + } + } diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/roleChange/repository/RoleChangeRequestRepository.java b/src/main/java/com/ComNCheck/ComNCheck/domain/roleChange/repository/RoleChangeRequestRepository.java index 37de7e4..e18d02f 100644 --- a/src/main/java/com/ComNCheck/ComNCheck/domain/roleChange/repository/RoleChangeRequestRepository.java +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/roleChange/repository/RoleChangeRequestRepository.java @@ -1,7 +1,12 @@ package com.ComNCheck.ComNCheck.domain.roleChange.repository; import com.ComNCheck.ComNCheck.domain.roleChange.model.entity.RoleChange; +import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; public interface RoleChangeRequestRepository extends JpaRepository { + + @Query("SELECT r FROM RoleChange r ORDER BY r.requestId DESC") + List findAllOrderByIdDesc(); } diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/roleChange/service/RoleChangeRequestService.java b/src/main/java/com/ComNCheck/ComNCheck/domain/roleChange/service/RoleChangeRequestService.java index f9da172..257e557 100644 --- a/src/main/java/com/ComNCheck/ComNCheck/domain/roleChange/service/RoleChangeRequestService.java +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/roleChange/service/RoleChangeRequestService.java @@ -1,6 +1,10 @@ package com.ComNCheck.ComNCheck.domain.roleChange.service; +import com.ComNCheck.ComNCheck.domain.global.exception.ApplyNotFoundException; +import com.ComNCheck.ComNCheck.domain.global.exception.ForbiddenException; +import com.ComNCheck.ComNCheck.domain.global.exception.MemberNotFoundException; +import com.ComNCheck.ComNCheck.domain.member.model.entity.Role; import com.ComNCheck.ComNCheck.domain.roleChange.model.dto.request.RoleChangeRequestDTO; import com.ComNCheck.ComNCheck.domain.roleChange.model.dto.response.ApprovedRoleListDTO; import com.ComNCheck.ComNCheck.domain.roleChange.model.dto.response.RoleChangeListDTO; @@ -24,9 +28,9 @@ public class RoleChangeRequestService { private final MemberRepository memberRepository; @Transactional - public RoleChangeResponseDTO createRoleChangeRequest(RoleChangeRequestDTO requestDTO) { - Member member = memberRepository.findByMemberId(requestDTO.getMemberId()) - .orElseThrow(() -> new IllegalArgumentException("등록된 회원이 없습니다.")); + public RoleChangeResponseDTO createRoleChangeRequest(Long memberId, RoleChangeRequestDTO requestDTO) { + Member member = memberRepository.findByMemberId(memberId) + .orElseThrow(() -> new MemberNotFoundException("등록된 회원이 없습니다.")); RoleChange roleChange = RoleChange.builder() .member(member) @@ -38,33 +42,49 @@ public RoleChangeResponseDTO createRoleChangeRequest(RoleChangeRequestDTO reques return RoleChangeResponseDTO.of(saveRoleChange); } - public List getAllRequests() { - List requests = roleChangeRequestRepository.findAll(); + public List getAllRequests(Long memberId) { + Member member = memberRepository.findByMemberId(memberId) + .orElseThrow(() -> new MemberNotFoundException("등록된 회원이 없습니다.")); + isCheckRole(member); + + List requests = roleChangeRequestRepository.findAllOrderByIdDesc(); return requests.stream() .map(RoleChangeListDTO::of) .collect(Collectors.toList()); } - public RoleChangeResponseDTO getRequestDetail(Long requestId) { + public RoleChangeResponseDTO getRequestDetail(Long requestId, Long memberId) { + Member member = memberRepository.findByMemberId(memberId) + .orElseThrow(() -> new MemberNotFoundException("등록된 회원이 없습니다.")); + isCheckRole(member); + RoleChange request = roleChangeRequestRepository.findById(requestId) - .orElseThrow(() -> new IllegalArgumentException("등록된 학생회 신청이 없습니다.")); + .orElseThrow(() -> new ApplyNotFoundException("등록된 학생회 신청이 없습니다.")); return RoleChangeResponseDTO.of(request); } @Transactional - public void approveRequest(Long requestId) { + public void approveRequest(Long requestId, Long memberId) { + Member member = memberRepository.findByMemberId(memberId) + .orElseThrow(() -> new MemberNotFoundException("등록된 회원이 없습니다.")); + isCheckRole(member); + RoleChange request = roleChangeRequestRepository.findById(requestId) - .orElseThrow(() -> new IllegalArgumentException("등록된 학생회 신청이 없습니다.")); + .orElseThrow(() -> new ApplyNotFoundException("등록된 학생회 신청이 없습니다.")); request.approve(); - Member member = request.getMember(); - member.updateRole(request.getRequestRole()); - member.updatePosition(request.getRequestPosition()); + Member updateMember = request.getMember(); + updateMember.updateRole(request.getRequestRole()); + updateMember.updatePosition(request.getRequestPosition()); } - public List getApproveRequests() { + public List getApproveRequests(Long memberId) { + Member member = memberRepository.findByMemberId(memberId) + .orElseThrow(() -> new MemberNotFoundException("등록된 회원이 없습니다.")); + isCheckRole(member); + List requests = roleChangeRequestRepository.findAll().stream() .filter(req -> req.getStatus() == RequestStatus.APPROVED) .collect(Collectors.toList()); @@ -74,24 +94,40 @@ public List getApproveRequests() { .collect(Collectors.toList()); } + @Transactional - public void changeMemberRole(Long requestId, RoleChangeRequestDTO requestDTO) { + public void deleteRequest(Long requestId, Long memberId) { + Member member = memberRepository.findByMemberId(memberId) + .orElseThrow(() -> new MemberNotFoundException("등록된 회원이 없습니다.")); + isCheckRole(member); + RoleChange request = roleChangeRequestRepository.findById(requestId) - .orElseThrow(() -> new IllegalArgumentException("요청이 없습니다.")); + .orElseThrow(() -> new ApplyNotFoundException("등록된 학생회 신청이 없습니다.")); + roleChangeRequestRepository.delete(request); + } - if(request.getStatus() != RequestStatus.APPROVED) { - throw new IllegalArgumentException("한번 변경된 요청만 수정 가능합니다."); - } + @Transactional + public RoleChangeResponseDTO updateRoleChange(Long requestId, Long memberId, String updatePosition, Role updateRole) { + Member member = memberRepository.findByMemberId(memberId) + .orElseThrow(() -> new MemberNotFoundException("등록된 회원이 없습니다.")); + isCheckRole(member); + + RoleChange role = roleChangeRequestRepository.findById(requestId) + .orElseThrow(() -> new ApplyNotFoundException("등록된 학생회 신청이 없습니다.")); - Member member = request.getMember(); - member.updateRole(requestDTO.getRequestRole()); - member.updatePosition(requestDTO.getRequestPosition()); + role.update(updatePosition, updateRole); + Member updateMember = role.getMember(); + updateMember.updateRole(role.getRequestRole()); + updateMember.updatePosition(role.getRequestPosition()); + + return RoleChangeResponseDTO.of(role); } - @Transactional - public void deleteRequest(Long requestId) { - RoleChange request = roleChangeRequestRepository.findById(requestId) - .orElseThrow(() -> new IllegalArgumentException("등록된 학생회 신청이 없습니다.")); - roleChangeRequestRepository.delete(request); + public void isCheckRole(Member member) { + Role checkRole = member.getRole(); + if (checkRole != Role.ROLE_MAJOR_PRESIDENT && checkRole != Role.ROLE_ADMIN) { + throw new ForbiddenException("접근 권한이 없습니다."); + } } + } diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/security/.DS_Store b/src/main/java/com/ComNCheck/ComNCheck/domain/security/.DS_Store new file mode 100644 index 0000000..dfe2219 Binary files /dev/null and b/src/main/java/com/ComNCheck/ComNCheck/domain/security/.DS_Store differ diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/security/config/CorsMvcConfig.java b/src/main/java/com/ComNCheck/ComNCheck/domain/security/config/CorsMvcConfig.java index 0a0d397..e720699 100644 --- a/src/main/java/com/ComNCheck/ComNCheck/domain/security/config/CorsMvcConfig.java +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/security/config/CorsMvcConfig.java @@ -11,6 +11,8 @@ public class CorsMvcConfig implements WebMvcConfigurer { public void addCorsMappings(CorsRegistry corsRegistry) { corsRegistry.addMapping("/**") .exposedHeaders("Set-Cookie") - .allowedOrigins("http://localhost:3000"); + .allowedOrigins("https://www.comncheck.com") + .allowedMethods("*") + .allowCredentials(true); } } diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/security/config/CustomAuthorizationRequestResolver.java b/src/main/java/com/ComNCheck/ComNCheck/domain/security/config/CustomAuthorizationRequestResolver.java new file mode 100644 index 0000000..bf6c7a9 --- /dev/null +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/security/config/CustomAuthorizationRequestResolver.java @@ -0,0 +1,42 @@ +package com.ComNCheck.ComNCheck.domain.security.config; + +import jakarta.servlet.http.HttpServletRequest; +import java.util.LinkedHashMap; +import java.util.Map; +import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizationRequestResolver; +import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver; +import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; + +public class CustomAuthorizationRequestResolver implements OAuth2AuthorizationRequestResolver { + + private final OAuth2AuthorizationRequestResolver defaultResolver; + + public CustomAuthorizationRequestResolver(OAuth2AuthorizationRequestResolver defaultResolver) { + this.defaultResolver = defaultResolver; + } + + @Override + public OAuth2AuthorizationRequest resolve(HttpServletRequest request) { + OAuth2AuthorizationRequest oAuth2AuthorizationRequest = this.defaultResolver.resolve(request); + return customizeRequest(oAuth2AuthorizationRequest); + } + + @Override + public OAuth2AuthorizationRequest resolve(HttpServletRequest request, String clientRegistrationId) { + OAuth2AuthorizationRequest oAuth2AuthorizationRequest = this.defaultResolver.resolve(request, clientRegistrationId); + return customizeRequest(oAuth2AuthorizationRequest); + } + + private OAuth2AuthorizationRequest customizeRequest(OAuth2AuthorizationRequest oAuth2AuthorizationRequest) { + if (oAuth2AuthorizationRequest == null) { + return null; + } + + Map extraParameters = new LinkedHashMap<>(oAuth2AuthorizationRequest.getAdditionalParameters()); + extraParameters.put("prompt", "select_account"); + + return OAuth2AuthorizationRequest.from(oAuth2AuthorizationRequest) + .additionalParameters(extraParameters) + .build(); + } +} diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/security/config/CustomFailureHandler.java b/src/main/java/com/ComNCheck/ComNCheck/domain/security/config/CustomFailureHandler.java new file mode 100644 index 0000000..ad73d1c --- /dev/null +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/security/config/CustomFailureHandler.java @@ -0,0 +1,25 @@ +package com.ComNCheck.ComNCheck.domain.security.config; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; + +public class CustomFailureHandler extends SimpleUrlAuthenticationFailureHandler { + + public CustomFailureHandler(String defaultFailureUrl) { + super(defaultFailureUrl); + } + + @Override + public void onAuthenticationFailure( + HttpServletRequest request, + HttpServletResponse response, + AuthenticationException exception + ) throws IOException, ServletException { + + super.onAuthenticationFailure(request, response, exception); + } +} diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/security/config/SecurityConfig.java b/src/main/java/com/ComNCheck/ComNCheck/domain/security/config/SecurityConfig.java index d8ceb76..eac5b56 100644 --- a/src/main/java/com/ComNCheck/ComNCheck/domain/security/config/SecurityConfig.java +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/security/config/SecurityConfig.java @@ -1,18 +1,23 @@ package com.ComNCheck.ComNCheck.domain.security.config; +import com.ComNCheck.ComNCheck.domain.member.service.CustomOAuthMemberService; import com.ComNCheck.ComNCheck.domain.security.filter.JWTFilter; import com.ComNCheck.ComNCheck.domain.security.handler.CustomSuccessHandler; -import com.ComNCheck.ComNCheck.domain.member.service.CustomOAuthMemberService; import com.ComNCheck.ComNCheck.domain.security.util.JWTUtil; import jakarta.servlet.http.HttpServletRequest; import java.util.Arrays; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; +import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizationRequestResolver; +import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver; +import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository; +import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; @@ -21,63 +26,74 @@ @RequiredArgsConstructor @EnableWebSecurity public class SecurityConfig { + private final CustomOAuthMemberService customOAuth2MemberService; private final CustomSuccessHandler customSuccessHandler; private final JWTUtil jwtUtil; + private final ClientRegistrationRepository clientRegistrationRepository; + private final OAuth2AuthorizedClientRepository authorizedClientRepository; @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - http - .cors(corsCustomizer -> corsCustomizer.configurationSource(new CorsConfigurationSource() { - @Override - public CorsConfiguration getCorsConfiguration(HttpServletRequest request) { - CorsConfiguration configuration = new CorsConfiguration(); - configuration.setAllowedOrigins(Arrays.asList("http://localhost:3000")); - configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS")); - configuration.setAllowCredentials(true); - configuration.setAllowedHeaders(Arrays.asList("*")); - configuration.setMaxAge(3600L); - configuration.setExposedHeaders(Arrays.asList("Set-Cookie", "Authorization")); - return configuration; - } - })) - .csrf(csrf -> csrf.disable()) - .formLogin(form -> form.disable()) - .httpBasic(httpBasic -> httpBasic.disable()) - .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) - .authorizeHttpRequests(auth -> auth - .requestMatchers( - "/login/**", - "/oauth2/**", - "/h2-console/**", - "/swagger-ui.html", - "/swagger-ui/**", - "/V3/api-docs", - "/v3/api-docs/**", - "/swagger-resources/**", - "/webjars/**" - //"/api/v1/**" - ).permitAll() - .anyRequest().authenticated() - ) - .oauth2Login(oauth2 -> oauth2 - .authorizationEndpoint(authorization -> authorization - .baseUri("/oauth2/authorize") - ) - .redirectionEndpoint(redirection -> redirection - .baseUri("/login/oauth2/code/*") - ) - .userInfoEndpoint(userInfoEndpointConfig -> userInfoEndpointConfig - .userService(customOAuth2MemberService)) - .successHandler(customSuccessHandler) - ); - System.out.println("시큐리티config"); - // H2 Console 관련 헤더 설정 -> 디비 변경 시 제거 - http.headers(headers -> headers.frameOptions(frameOptions -> frameOptions.sameOrigin())); + http.cors(corsCustomizer -> corsCustomizer.configurationSource(new CorsConfigurationSource() { + @Override + public CorsConfiguration getCorsConfiguration(HttpServletRequest request) { + CorsConfiguration configuration = new CorsConfiguration(); + configuration.setAllowedOrigins(Arrays.asList("https://www.comncheck.com")); + configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS")); + configuration.setAllowCredentials(true); + configuration.setAllowedHeaders(Arrays.asList("*")); + configuration.setMaxAge(3600L); + configuration.setExposedHeaders(Arrays.asList("Set-Cookie", "AccessToken")); + return configuration; + } + })); + + http.csrf(csrf -> csrf.disable()) + .formLogin(Customizer.withDefaults()).formLogin(form -> form.disable()) + .httpBasic(Customizer.withDefaults()).httpBasic(httpBasic -> httpBasic.disable()) + .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); + + http.authorizeHttpRequests(auth -> auth + .requestMatchers( + "/login/**", + "/oauth2/**", + "/swagger-ui.html", + "/swagger-ui/**", + "/V3/api-docs", + "/v3/api-docs/**", + "/swagger-resources/**", + "/webjars/**", + "/api/v1/**" + ).permitAll() + .anyRequest().authenticated() + ); + + http.oauth2Login(oauth2 -> oauth2 + .authorizationEndpoint(authorization -> authorization + .baseUri("/oauth2/authorize") + .authorizationRequestResolver(customAuthorizationRequestResolver()) + ) + .redirectionEndpoint(redirection -> redirection + .baseUri("/login/oauth2/code/*") + ) + .userInfoEndpoint(userInfoEndpointConfig -> + userInfoEndpointConfig.userService(customOAuth2MemberService) + ) + .successHandler(customSuccessHandler) + .failureHandler(new CustomFailureHandler("https://www.comncheck.com/login?error=invalid_domain")) + ); http.addFilterBefore(new JWTFilter(jwtUtil), UsernamePasswordAuthenticationFilter.class); return http.build(); } + + @Bean + public OAuth2AuthorizationRequestResolver customAuthorizationRequestResolver() { + DefaultOAuth2AuthorizationRequestResolver defaultResolver = + new DefaultOAuth2AuthorizationRequestResolver(clientRegistrationRepository, "/oauth2/authorize"); + return new CustomAuthorizationRequestResolver(defaultResolver); + } } diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/security/filter/JWTFilter.java b/src/main/java/com/ComNCheck/ComNCheck/domain/security/filter/JWTFilter.java index 10286a6..924e4d7 100644 --- a/src/main/java/com/ComNCheck/ComNCheck/domain/security/filter/JWTFilter.java +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/security/filter/JWTFilter.java @@ -2,9 +2,9 @@ import com.ComNCheck.ComNCheck.domain.member.model.dto.response.MemberDTO; import com.ComNCheck.ComNCheck.domain.member.model.entity.Role; -import com.ComNCheck.ComNCheck.domain.security.exception.TokenExpiredException; import com.ComNCheck.ComNCheck.domain.security.oauth.CustomOAuth2Member; import com.ComNCheck.ComNCheck.domain.security.util.JWTUtil; +import io.jsonwebtoken.ExpiredJwtException; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.Cookie; @@ -12,13 +12,13 @@ import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import lombok.RequiredArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.util.AntPathMatcher; import org.springframework.web.filter.OncePerRequestFilter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; @RequiredArgsConstructor public class JWTFilter extends OncePerRequestFilter { @@ -34,8 +34,7 @@ public class JWTFilter extends OncePerRequestFilter { "/webjars/**", "/login/**", "/oauth2/**", - "/h2-console/**" - //"api/v1/**" + "api/v1/**" }; private final AntPathMatcher pathMatcher = new AntPathMatcher(); @@ -61,45 +60,59 @@ protected void doFilterInternal(HttpServletRequest request, Cookie[] cookies = request.getCookies(); if (cookies != null) { for (Cookie cookie : cookies) { - if ("Authorization".equals(cookie.getName())) { // 쿠키 이름 확인 + if ("AccessToken".equals(cookie.getName())) { token = cookie.getValue(); - logger.debug("Found Authorization cookie: " + token); + logger.debug("AccessToken 쿠키를 찾았습니다: " + token); break; } } } try { - if (token != null) { - if (jwtUtil.isExpired(token)) { - throw new TokenExpiredException("만료된 토큰입니다."); - } + if (token == null || token.trim().isEmpty()) { + logger.error("AccessToken 쿠키가 존재하지 않습니다."); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.setContentType("application/json;charset=UTF-8"); + response.setCharacterEncoding("UTF-8"); + response.getWriter().write("{\"error\": \"MISSING_TOKEN\", \"message\": \"AccessToken 쿠키가 존재하지 않습니다.\"}"); + response.getWriter().flush(); + return; + } - String username = jwtUtil.getUsername(token); - Long id = jwtUtil.getId(token); - Role role = jwtUtil.getRole(token); + String username = jwtUtil.getUsername(token); + Long id = jwtUtil.getId(token); + Role role = jwtUtil.getRole(token); - MemberDTO memberDTO = new MemberDTO(); - memberDTO.setMemberId(id); - memberDTO.setName(username); - memberDTO.setRole(role); + MemberDTO memberDTO = new MemberDTO(); + memberDTO.setMemberId(id); + memberDTO.setName(username); + memberDTO.setRole(role); - CustomOAuth2Member customOAuth2Member = new CustomOAuth2Member(memberDTO); + CustomOAuth2Member customOAuth2Member = new CustomOAuth2Member(memberDTO); - Authentication authToken = - new UsernamePasswordAuthenticationToken( - customOAuth2Member, - null, - customOAuth2Member.getAuthorities() - ); - SecurityContextHolder.getContext().setAuthentication(authToken); - logger.debug("SecurityContext set with user: " + username); - } else { - logger.debug("No JWT token found in cookies."); - } + Authentication authToken = new UsernamePasswordAuthenticationToken( + customOAuth2Member, + null, + customOAuth2Member.getAuthorities() + ); + SecurityContextHolder.getContext().setAuthentication(authToken); + logger.debug("SecurityContext에 사용자 정보를 설정했습니다: " + username); + + } catch (ExpiredJwtException e) { + logger.error("토큰 만료: " + e.getMessage()); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.setContentType("application/json;charset=UTF-8"); + response.setCharacterEncoding("UTF-8"); + response.getWriter().write("{\"error\": \"TOKEN_EXPIRED\", \"message\": \"" + e.getMessage() + "\"}"); + response.getWriter().flush(); + return; } catch (Exception e) { - logger.error("Authentication error: " + e.getMessage()); - response.sendError(HttpServletResponse.SC_UNAUTHORIZED, e.getMessage()); + logger.error("인증 오류: " + e.getMessage()); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.setContentType("application/json;charset=UTF-8"); + response.setCharacterEncoding("UTF-8"); + response.getWriter().write("{\"error\": \"AUTHENTICATION_ERROR\", \"message\": \"" + e.getMessage() + "\"}"); + response.getWriter().flush(); return; } diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/security/handler/CustomSuccessHandler.java b/src/main/java/com/ComNCheck/ComNCheck/domain/security/handler/CustomSuccessHandler.java index c000911..0105c1e 100644 --- a/src/main/java/com/ComNCheck/ComNCheck/domain/security/handler/CustomSuccessHandler.java +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/security/handler/CustomSuccessHandler.java @@ -6,10 +6,12 @@ import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; import java.io.IOException; import java.util.Collection; import java.util.Iterator; import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseCookie; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; @@ -26,29 +28,69 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo Authentication authentication) throws IOException, ServletException { CustomOAuth2Member customMemberDetails = (CustomOAuth2Member) authentication.getPrincipal(); - Long userId = customMemberDetails.getMemberDTO().getMemberId(); + Long memberId = customMemberDetails.getMemberDTO().getMemberId(); String username = customMemberDetails.getName(); Collection authorities = authentication.getAuthorities(); Iterator iterator = authorities.iterator(); GrantedAuthority auth = iterator.next(); String role = auth.getAuthority().toString(); + boolean checkStudentCard = customMemberDetails.isCheckStudentCard(); + System.out.println(checkStudentCard); - String token = jwtUtil.createJwt(userId, username, role, 60*60*60L); + String token = jwtUtil.createJwt(memberId, username, role, 365L * 24 * 60 * 60 * 1000); // 60 * 60 * 1000L - response.addCookie(createCookie("Authorization", token)); - response.sendRedirect("http://localhost:3000/signup?id=" + userId); + ResponseCookie cookie = createCookie("AccessToken", token); + response.setHeader("Set-Cookie", cookie.toString()); + //response.addCookie(createCookie("AccessToken", token)); // Cookie로 했을때 + if(!checkStudentCard) { + response.sendRedirect("https://www.comncheck.com/login/first"); //https://com-n-check.vercel.app + } + else { + response.sendRedirect("https://www.comncheck.com/notice"); + } + //response.sendRedirect("http://localhost:3000/login/first"); } - private Cookie createCookie(String key, String value) { - Cookie cookie = new Cookie(key, value); - cookie.setMaxAge(60*60*60); - //cookie.setSecure(true); - cookie.setPath("/"); - //cookie.setHttpOnly(true); + public void clearAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response) { + HttpSession session = request.getSession(false); + if (session != null) { + session.invalidate(); + } - return cookie; + Cookie jsessionCookie = new Cookie("JSESSIONID", null); + jsessionCookie.setPath("/"); + jsessionCookie.setMaxAge(0); + response.addCookie(jsessionCookie); + + Cookie accessTokenCookie = new Cookie("AccessToken", null); + accessTokenCookie.setPath("/"); + accessTokenCookie.setHttpOnly(true); + accessTokenCookie.setMaxAge(0); + response.addCookie(accessTokenCookie); + } + + private ResponseCookie createCookie(String key, String value) { + return ResponseCookie.from(key, value) + .maxAge(60 * 60 * 60) + .secure(true) + .sameSite("None") + .path("/") + .domain("www.comncheck.com") + .httpOnly(true) + .build(); } +// private Cookie createCookie(String key, String value) { +// Cookie cookie = new Cookie(key, value); +// cookie.setMaxAge(60 * 60 * 60); +// cookie.setSecure(true); +// cookie.setHttpOnly(true); +// cookie.setPath("/"); +// cookie.setDomain("com-n-check.vercel.app"); +// cookie.setAttribute("SameSite", "None"); +// return cookie; +// } + } diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/security/oauth/CustomOAuth2Member.java b/src/main/java/com/ComNCheck/ComNCheck/domain/security/oauth/CustomOAuth2Member.java index 41e541e..b7440ed 100644 --- a/src/main/java/com/ComNCheck/ComNCheck/domain/security/oauth/CustomOAuth2Member.java +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/security/oauth/CustomOAuth2Member.java @@ -9,7 +9,8 @@ import org.springframework.security.oauth2.core.user.OAuth2User; @RequiredArgsConstructor -public class CustomOAuth2Member implements OAuth2User { +public class +CustomOAuth2Member implements OAuth2User { private final MemberDTO memberDTO; public MemberDTO getMemberDTO() { @@ -38,5 +39,8 @@ public String getName() { return memberDTO.getName(); } + public boolean isCheckStudentCard() { + return memberDTO.isCheckStudentCard(); + } } diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/security/util/JWTUtil.java b/src/main/java/com/ComNCheck/ComNCheck/domain/security/util/JWTUtil.java index 37ab0d4..d877de0 100644 --- a/src/main/java/com/ComNCheck/ComNCheck/domain/security/util/JWTUtil.java +++ b/src/main/java/com/ComNCheck/ComNCheck/domain/security/util/JWTUtil.java @@ -46,7 +46,7 @@ public String getUsername(String token) { .build() .parseSignedClaims(token) .getPayload() - .get("username", String.class); + .get("name", String.class); } public Role getRole(String token) { diff --git a/src/main/java/com/ComNCheck/ComNCheck/domain/testController/MyController.java b/src/main/java/com/ComNCheck/ComNCheck/domain/testController/MyController.java deleted file mode 100644 index 0eb39ce..0000000 --- a/src/main/java/com/ComNCheck/ComNCheck/domain/testController/MyController.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.ComNCheck.ComNCheck.domain.testController; - -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.ResponseBody; - -@Controller -public class MyController { - - @GetMapping("/my") - @ResponseBody - public String myAPI() { - return "my route"; - } - - -} diff --git a/src/main/resources/.DS_Store b/src/main/resources/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/src/main/resources/.DS_Store differ diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index bf9002c..76acdfa 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -2,21 +2,35 @@ server: port: 8080 spring: - jpa: - hibernate: - ddl-auto: create-drop - show-sql: true application: name: ComNCheck - datasource: - driver-class-name: org.h2.Driver - url: ${H2_DB_URL} #url: 'jdbc:h2:~/test' -> H2 DB 연결 주소 (Embedded Mode) - username: ${DB_USERNAME} - password: ${DB_PASSWORD} - h2: - console: - enabled: true - path: /h2-console + config: + import: + - classpath:/yaml/application-prod.yaml + - classpath:/yaml/application-log.yaml + profiles: + group: + setting: prod, log + active : setting + + data: + redis: + host: ${REDIS_HOST:localhost} + port: ${REDIS_PORT:6379} + password: ${REDIS_PASSWORD} + + cloud: + gcp: + storage: + credentials: + location: classpath:/gcp/ancient-pipe-447417-i4-755ce59fbf03.json + project-id: ${GCP_PROJECT_ID} + bucket: ${GCP_BUCKET_ID} + firestore: + project-id: ${FIREBASE_PROJECT_ID} + credentials: + location: classpath:/firebase/comncheck-firebase-adminsdk-fbsvc-1255c6f2ed.json + security: oauth2: client: @@ -30,7 +44,10 @@ spring: scope: - email - profile - jwt: secret: ${JWT_SECRET} - expirationMs: ${JWT_EXPIRATIONMS} \ No newline at end of file + expirationMs: ${JWT_EXPIRATIONMS} + +target: + server: + ip: ${PROD_FASTAPI_IP} diff --git a/src/main/resources/yaml/application-local.yaml b/src/main/resources/yaml/application-local.yaml new file mode 100644 index 0000000..089b3b7 --- /dev/null +++ b/src/main/resources/yaml/application-local.yaml @@ -0,0 +1,30 @@ +#spring: +# config: +# activate: +# on-profile: local +# jpa: +# hibernate: +# ddl-auto: update +# properties: +# hibernate: +# show_sql: true +# format_sql: true +# highlight_sql: true +# datasource: +# driver-class-name: org.h2.Driver +# url: ${H2_DB_URL:jdbc:h2:mem:test} #url: 'jdbc:h2:~/test' -> H2 DB 연결 주소 (Embedded Mode) +# username: ${H2_DB_USERNAME} +# password: ${H2_DB_PASSWORD} +# h2: +# console: +# enabled: true +# path: /h2-console +# settings: +# web-allow-others: true +# +#logging.level: +# org.hibernate: +# type: trace +# orm: +# jdbc: +# bind: trace diff --git a/src/main/resources/yaml/application-log.yaml b/src/main/resources/yaml/application-log.yaml new file mode 100644 index 0000000..d96612c --- /dev/null +++ b/src/main/resources/yaml/application-log.yaml @@ -0,0 +1,19 @@ +spring: + config: + activate: + on-profile: log + jpa: + hibernate: + ddl-auto: none + properties: + hibernate: + show_sql: true + format_sql: true + highlight_sql: true + +logging.level: + org.hibernate: + type: trace + orm: + jdbc: + bind: trace \ No newline at end of file diff --git a/src/main/resources/yaml/application-prod.yaml b/src/main/resources/yaml/application-prod.yaml new file mode 100644 index 0000000..9a40706 --- /dev/null +++ b/src/main/resources/yaml/application-prod.yaml @@ -0,0 +1,10 @@ +spring: + config: + activate: + on-profile: prod + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: ${MYSQL_URL} + username: ${MYSQL_USERNAME} + password: ${MYSQL_PASSWORD} +