diff --git a/.gitignore b/.gitignore index 1bd49ffd..6ba6c478 100644 --- a/.gitignore +++ b/.gitignore @@ -124,3 +124,4 @@ src/main/resources/config !application-test.yml src/main/generated dump.rdb +/src/main/resources/firebase-local.json diff --git a/README.md b/README.md index b9d04dd2..33687c98 100644 --- a/README.md +++ b/README.md @@ -1 +1,54 @@ -# moabam-BE +## ๐Ÿฅ MOABAM ์„œ๋น„์Šค + +![img.png](readme-image/img.png) + +![img_1.png](readme-image/img_1.png) + +

+ +## ๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘ง Backend Team ์†Œ๊ฐœ + +| ๊น€์˜๋ช… | ๊น€ํฌ๋นˆ(PO) | ๋ฐ•์„ธ์—ฐ | ์‹ ์žฌ์œค | ํ™ํ˜์ค€(SM) | +|:------------------------------------------------------------------------------:|:------------------------------------------------------------------------------:|:------------------------------------------------------------------------------:|:------------------------------------------------------------------------------:|:------------------------------------------------------------------------------:| +| DEVELOPER | DEVELOPER | DEVELOPER | DEVELOPER | DEVELOPER | +| | | | | | +| [ymkim97](https://github.com/ymkim97) | [kmebin](https://github.com/kmebin) | [parksey](https://github.com/parksey) | [DevUni](https://github.com/Shin-Jae-Yoon) | [HongDosan](https://github.com/HyuckJuneHong) | +| ๋ฐฉ ๋„๋ฉ”์ธ, ๋ฃจํ‹ด ์ธ์ฆ(๋ฉ”์ธ) | ์ƒํ’ˆ ๋„๋ฉ”์ธ, ๊ฒฐ์ œ, ์—๋Ÿฌ ์•Œ๋ฆผ, BE ํŒ€์žฅ | ํšŒ์› ๋„๋ฉ”์ธ, ๋žญํ‚น ์–ด๋“œ๋ฏผ ํŽ˜์ด์ง€ | ๋ฐฉ ๋„๋ฉ”์ธ, ๋ฃจํ‹ด ์ธ์ฆ(์„œ๋ธŒ), ์ธํ”„๋ผ (AWS, CI/CD) | ์ฟ ํฐ ๋„๋ฉ”์ธ, ์•Œ๋ฆผ, ์„ ์ฐฉ์ˆœ ์ด๋ฒคํŠธ, ์บ์‹ฑ | + +

+ +## ๊ณตํ†ต ํ˜‘์—… ๋ฐฉ์‹ + +![img.png](readme-image/ํ˜‘์—….png) + +## ์„œ๋น„์Šค ์•„ํ‚คํ…์ฒ˜ + +![img.png](readme-image/์„œ๋น„์Šค-์•„ํ‚คํ…์ฒ˜.png) + +## CI/CD ํŒŒ์ดํ”„๋ผ์ธ + +![img.png](readme-image/ํŒŒ์ดํ”„๋ผ์ธ.png) + +## ํ…Œ์ด๋ธ” ์•„ํ‚คํ…์ฒ˜ + +![img.png](readme-image/table.png) + +## API ๋ช…์„ธ + +![img.png](readme-image/api.png) + +## ์ปจ๋ฒค์…˜ + +![img_1.png](readme-image/์ปจ๋ฒค์…˜.png) + +## Config ๊ด€๋ฆฌ + +![img.png](readme-image/์ฝ˜ํ”ผ๊ทธ.png) + +## Test + +![img.png](readme-image/ํ…Œ์ŠคํŠธ.png) + +## ํ˜‘์—… ๊ทœ์น™ +![image](https://github.com/team-moabam/moabam-BE/assets/31675711/1c9ae816-9174-42fd-959a-bad9f375878d) + diff --git a/build.gradle b/build.gradle index c7f8c37e..82041f1c 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ plugins { id 'org.sonarqube' version '4.4.1.3373' id 'jacoco' id 'checkstyle' - id 'org.asciidoctor.jvm.convert' version '3.3.2' + id 'com.epages.restdocs-api-spec' version '0.18.4' } group = 'com.moabam' @@ -15,21 +15,27 @@ java { sourceCompatibility = '17' } -ext { - snippetsDir = file('build/generated-snippets') +compileJava { + options.compilerArgs << '-parameters' + options.encoding = 'UTF-8' +} + +compileTestJava { + options.compilerArgs << '-parameters' + options.encoding = 'UTF-8' } def querydslSrcDir = 'src/main/generated' + clean { delete file(querydslSrcDir) } + tasks.withType(JavaCompile) { options.generatedSourceOutputDirectory = file(querydslSrcDir) } configurations { - asciidoctorExtensions - compileOnly { extendsFrom annotationProcessor } @@ -58,7 +64,14 @@ dependencies { // Test testImplementation 'org.springframework.boot:spring-boot-starter-test' - testImplementation 'com.squareup.okhttp3:mockwebserver:4.11.0' + testImplementation 'com.squareup.okhttp3:mockwebserver' + + // RestDocs + testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc' + testImplementation 'com.epages:restdocs-api-spec-mockmvc:0.18.4' + + // Swagger + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0' // Querydsl implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' @@ -75,6 +88,9 @@ dependencies { // Apache Commons Lang 3 implementation 'org.apache.commons:commons-lang3:3.13.0' + // Cache + implementation 'org.springframework.boot:spring-boot-starter-cache' + // Redis implementation 'org.springframework.boot:spring-boot-starter-data-redis' @@ -92,12 +108,6 @@ dependencies { // JSON parser implementation 'org.json:json:20230618' - // Asciidoctor - asciidoctorExtensions 'org.springframework.restdocs:spring-restdocs-asciidoctor' - - // RestDocs - testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc' - // S3 implementation platform("io.awspring.cloud:spring-cloud-aws-dependencies:3.0.2") implementation 'io.awspring.cloud:spring-cloud-aws-starter-s3' @@ -110,9 +120,6 @@ dependencies { // Logback Slack Appender implementation 'com.github.maricn:logback-slack-appender:1.6.1' - - // Swagger - implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0' } tasks.named('test') { @@ -134,28 +141,25 @@ jacocoTestReport { afterEvaluate { classDirectories.setFrom( - files(classDirectories.files.collect { - fileTree(dir: it, excludes: [ - "**/*Application*", - "**/*Config*", - "**/*Request*", - "**/*Response*", - "**/*Exception*", - "**/*Mapper*", - "**/*ErrorMessage*", - "**/*DynamicQuery*", - "**/*BaseTimeEntity*", - "**/*HealthCheckController*", - "**/*S3Manager*", - ] + Qdomains) - }) + files(classDirectories.files.collect { + fileTree(dir: it, excludes: [ + "**/*Application*", + "**/*Config*", + "**/*Request*", + "**/*Response*", + "**/*Exception*", + "**/*Mapper*", + "**/*ErrorMessage*", + "**/*DynamicQuery*", + "**/*BaseTimeEntity*", + "**/*HealthCheckController*", + "**/*S3Manager*", + ] + Qdomains) + }) ) } } -compileJava.options.encoding = 'UTF-8' -compileTestJava.options.encoding = 'UTF-8' - tasks.withType(Checkstyle).configureEach { reports { xml.required = true @@ -177,37 +181,24 @@ sonar { property "sonar.host.url", "https://sonarcloud.io" property 'sonar.coverage.jacoco.xmlReportPaths', 'build/reports/jacoco/test/jacocoTestReport.xml' property 'sonar.coverage.exclusions', '**/test/**, **/Q*.java, **/*Doc*.java, **/resources/** ' + - ',**/*Application*.java , **/*Config*.java, **/*Request*.java, **/*Response*.java ,**/*Exception*.java ' + - ',**/*ErrorMessage*.java, **/*Mapper*.java, **/*DynamicQuery*, **/*BaseTimeEntity*, **/*HealthCheckController* ' + - ', **/*S3Manager*.java' + ',**/*Application*.java , **/*Config*.java, **/*Request*.java, **/*Response*.java ,**/*Exception*.java ' + + ',**/*ErrorMessage*.java, **/*Mapper*.java, **/*DynamicQuery*, **/*BaseTimeEntity*, **/*HealthCheckController* ' + + ', **/*S3Manager*.java' property 'sonar.java.checkstyle.reportPaths', 'build/reports/checkstyle/main.xml' } } -test { - outputs.dir snippetsDir -} +openapi3 { + servers = [ + { url = 'http://localhost:8080' }, + { url = 'http://dev.moabam.com' } + ] -asciidoctor { - configurations 'asciidoctorExtensions' - inputs.dir snippetsDir - dependsOn test -} + title = 'MOABAM API DOCS' + format = 'json' -asciidoctor.doFirst { - delete file('src/main/resources/static/docs') -} - -tasks.register('copyDocument', Copy) { - dependsOn asciidoctor - from file("build/docs/asciidoc") - into file("src/main/resources/static/docs") -} - -bootJar { - dependsOn copyDocument -} - -build { - dependsOn copyDocument + copy { + from 'build/api-spec' + into 'src/main/resources/static/docs' + } } diff --git a/infra/Dockerfile b/infra/Dockerfile index c7fb704b..4dadfa35 100644 --- a/infra/Dockerfile +++ b/infra/Dockerfile @@ -4,5 +4,6 @@ ARG SPRING_ACTIVE_PROFILES ENV SPRING_ACTIVE_PROFILES ${SPRING_ACTIVE_PROFILES} COPY build/libs/moabam-server-0.0.1-SNAPSHOT.jar moabam.jar +COPY src/main/resources/config/agent.java/ agent.java -ENTRYPOINT ["java", "-jar", "-Duser.timezone=Asia/Seoul", "-Dspring.profiles.active=${SPRING_ACTIVE_PROFILES}", "/moabam.jar"] +ENTRYPOINT ["java", "-javaagent:agent.java/scouter.agent.jar", "-Dsocuter.config=agent.java/conf/scouter.conf", "-jar", "-Duser.timezone=Asia/Seoul", "-Dspring.profiles.active=${SPRING_ACTIVE_PROFILES}", "/moabam.jar"] diff --git a/infra/docker-compose-dev.yml b/infra/docker-compose-dev.yml index 86ec3530..6419c55b 100644 --- a/infra/docker-compose-dev.yml +++ b/infra/docker-compose-dev.yml @@ -1,77 +1,77 @@ version: '3.7' services: - nginx: - image: nginx:latest - container_name: nginx - platform: linux/arm64/v8 - restart: always - ports: - - "80:80" - - "443:443" - volumes: - - ./nginx/nginx.conf:/etc/nginx/nginx.conf - - ./nginx/conf.d:/etc/nginx/conf.d - - ./nginx/certbot/conf:/etc/letsencrypt - - ./nginx/certbot/www:/var/www/certbot - - ../logs/nginx:/var/log/nginx - certbot: - image: certbot/certbot:latest - container_name: certbot - platform: linux/arm64 - restart: unless-stopped - volumes: - - ./nginx/certbot/conf:/etc/letsencrypt - - ./nginx/certbot/www:/var/www/certbot - entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'" - moabam-blue: - image: ${DOCKER_HUB_USERNAME}/${DOCKER_HUB_REPOSITORY}:${DOCKER_HUB_TAG} - container_name: ${BLUE_CONTAINER} - restart: unless-stopped - expose: - - ${SERVER_PORT} - depends_on: - - redis - - mysql - environment: - SPRING_ACTIVE_PROFILES: ${SPRING_ACTIVE_PROFILES} - moabam-green: - image: ${DOCKER_HUB_USERNAME}/${DOCKER_HUB_REPOSITORY}:${DOCKER_HUB_TAG} - container_name: ${GREEN_CONTAINER} - restart: unless-stopped - expose: - - ${SERVER_PORT} - depends_on: - - redis - - mysql - environment: - SPRING_ACTIVE_PROFILES: ${SPRING_ACTIVE_PROFILES} - redis: - image: redis:alpine - container_name: redis - platform: linux/arm64 - restart: always - command: redis-server - ports: - - "6379:6379" - volumes: - - ./data/redis:/data - mysql: - image: mysql:8.0.33 - container_name: mysql - platform: linux/arm64/v8 - restart: always - ports: - - "3306:3306" - environment: - MYSQL_DATABASE: ${DEV_MYSQL_DATABASE} - MYSQL_USERNAME: ${DEV_MYSQL_USERNAME} - MYSQL_ROOT_PASSWORD: ${DEV_MYSQL_PASSWORD} - TZ: Asia/Seoul - command: - - --character-set-server=utf8mb4 - - --collation-server=utf8mb4_unicode_ci - - --skip-character-set-client-handshake - volumes: - - ./data/mysql:/var/lib/mysql - - ./mysql/initdb.d:/docker-entrypoint-initdb.d + nginx: + image: nginx:latest + container_name: nginx + platform: linux/arm64/v8 + restart: always + ports: + - "80:80" + # - "443:443" + volumes: + - ./nginx/nginx.conf:/etc/nginx/nginx.conf + - ./nginx/conf.d:/etc/nginx/conf.d + # - ./nginx/certbot/conf:/etc/letsencrypt + # - ./nginx/certbot/www:/var/www/certbot + - ../logs/nginx:/var/log/nginx + certbot: + image: certbot/certbot:latest + container_name: certbot + platform: linux/arm64 + restart: unless-stopped + volumes: + - ./nginx/certbot/conf:/etc/letsencrypt + - ./nginx/certbot/www:/var/www/certbot + entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'" + moabam-blue: + image: ${DOCKER_HUB_USERNAME}/${DOCKER_HUB_REPOSITORY}:${DOCKER_HUB_TAG} + container_name: ${BLUE_CONTAINER} + restart: unless-stopped + expose: + - ${SERVER_PORT} + depends_on: + - redis + - mysql + environment: + SPRING_ACTIVE_PROFILES: ${SPRING_ACTIVE_PROFILES} + moabam-green: + image: ${DOCKER_HUB_USERNAME}/${DOCKER_HUB_REPOSITORY}:${DOCKER_HUB_TAG} + container_name: ${GREEN_CONTAINER} + restart: unless-stopped + expose: + - ${SERVER_PORT} + depends_on: + - redis + - mysql + environment: + SPRING_ACTIVE_PROFILES: ${SPRING_ACTIVE_PROFILES} + redis: + image: redis:alpine + container_name: redis + platform: linux/arm64 + restart: always + command: redis-server + ports: + - "6379:6379" + volumes: + - ./data/redis:/data + mysql: + image: mysql:8.0.33 + container_name: mysql + platform: linux/arm64/v8 + restart: always + ports: + - "3306:3306" + environment: + MYSQL_DATABASE: ${DEV_MYSQL_DATABASE} + MYSQL_USERNAME: ${DEV_MYSQL_USERNAME} + MYSQL_ROOT_PASSWORD: ${DEV_MYSQL_PASSWORD} + TZ: Asia/Seoul + command: + - --character-set-server=utf8mb4 + - --collation-server=utf8mb4_unicode_ci + - --skip-character-set-client-handshake + volumes: + - ./data/mysql:/var/lib/mysql + - ./mysql/initdb.d:/docker-entrypoint-initdb.d diff --git a/infra/docker-compose-prod.yml b/infra/docker-compose-prod.yml index 8cf816fa..527ec7bb 100644 --- a/infra/docker-compose-prod.yml +++ b/infra/docker-compose-prod.yml @@ -1,56 +1,56 @@ version: '3.7' services: - nginx: - image: nginx:latest - container_name: nginx - platform: linux/arm64/v8 - restart: always - ports: - - "80:80" - - "443:443" - volumes: - - ./nginx/nginx.conf:/etc/nginx/nginx.conf - - ./nginx/conf.d:/etc/nginx/conf.d - - ./nginx/certbot/conf:/etc/letsencrypt - - ./nginx/certbot/www:/var/www/certbot - - ../logs/nginx:/var/log/nginx - certbot: - image: certbot/certbot:latest - container_name: certbot - platform: linux/arm64 - restart: unless-stopped - volumes: - - ./nginx/certbot/conf:/etc/letsencrypt - - ./nginx/certbot/www:/var/www/certbot - entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'" - moabam-blue: - image: ${DOCKER_HUB_USERNAME}/${DOCKER_HUB_REPOSITORY}:${DOCKER_HUB_TAG} - container_name: ${BLUE_CONTAINER} - restart: unless-stopped - expose: - - ${SERVER_PORT} - depends_on: - - redis - environment: - SPRING_ACTIVE_PROFILES: ${SPRING_ACTIVE_PROFILES} - moabam-green: - image: ${DOCKER_HUB_USERNAME}/${DOCKER_HUB_REPOSITORY}:${DOCKER_HUB_TAG} - container_name: ${GREEN_CONTAINER} - restart: unless-stopped - expose: - - ${SERVER_PORT} - depends_on: - - redis - environment: - SPRING_ACTIVE_PROFILES: ${SPRING_ACTIVE_PROFILES} - redis: - image: redis:alpine - container_name: redis - platform: linux/arm64 - restart: always - command: redis-server - ports: - - "6379:6379" - volumes: - - ./data/redis:/data + nginx: + image: nginx:latest + container_name: nginx + platform: linux/arm64/v8 + restart: always + ports: + - "80:80" + # - "443:443" + volumes: + - ./nginx/nginx.conf:/etc/nginx/nginx.conf + - ./nginx/conf.d:/etc/nginx/conf.d + # - ./nginx/certbot/conf:/etc/letsencrypt + # - ./nginx/certbot/www:/var/www/certbot + - ../logs/nginx:/var/log/nginx + certbot: + image: certbot/certbot:latest + container_name: certbot + platform: linux/arm64 + restart: unless-stopped + volumes: + - ./nginx/certbot/conf:/etc/letsencrypt + - ./nginx/certbot/www:/var/www/certbot + entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'" + moabam-blue: + image: ${DOCKER_HUB_USERNAME}/${DOCKER_HUB_REPOSITORY}:${DOCKER_HUB_TAG} + container_name: ${BLUE_CONTAINER} + restart: unless-stopped + expose: + - ${SERVER_PORT} + depends_on: + - redis + environment: + SPRING_ACTIVE_PROFILES: ${SPRING_ACTIVE_PROFILES} + moabam-green: + image: ${DOCKER_HUB_USERNAME}/${DOCKER_HUB_REPOSITORY}:${DOCKER_HUB_TAG} + container_name: ${GREEN_CONTAINER} + restart: unless-stopped + expose: + - ${SERVER_PORT} + depends_on: + - redis + environment: + SPRING_ACTIVE_PROFILES: ${SPRING_ACTIVE_PROFILES} + redis: + image: redis:alpine + container_name: redis + platform: linux/arm64 + restart: always + command: redis-server + ports: + - "6379:6379" + volumes: + - ./data/redis:/data diff --git a/infra/mysql/initdb.d/init.sql b/infra/mysql/initdb.d/init.sql index a6edd500..455f5c60 100644 --- a/infra/mysql/initdb.d/init.sql +++ b/infra/mysql/initdb.d/init.sql @@ -13,10 +13,10 @@ create table admin create table badge ( - id bigint not null auto_increment, - member_id bigint not null, - type enum ('BIRTH','LEVEL10','LEVEL50') not null, - created_at datetime(6) not null, + id bigint not null auto_increment, + member_id bigint not null, + type enum ('BIRTH','LEVEL10','LEVEL50') not null, + created_at datetime(6) not null, primary key (id) ); @@ -121,7 +121,7 @@ create table member ( id bigint not null auto_increment, social_id varchar(255) not null unique, - nickname varchar(255) unique, + nickname varchar(255) unique, intro varchar(30), profile_image varchar(255) not null, morning_image varchar(255) not null, @@ -215,7 +215,9 @@ create table room deleted_at datetime(6), created_at datetime(6) not null, updated_at datetime(6), - primary key (id) + primary key (id), + FULLTEXT INDEX full_index_title (title) WITH PARSER ngram, + FULLTEXT INDEX full_index_manager_nickname (manager_nickname) WITH PARSER ngram ); create table routine @@ -225,7 +227,8 @@ create table routine content varchar(20) not null, created_at datetime(6) not null, updated_at datetime(6), - primary key (id) + primary key (id), + FULLTEXT INDEX full_index_content (content) WITH PARSER ngram ); alter table bug_history diff --git a/infra/nginx/nginx.conf b/infra/nginx/nginx.conf index 73220f14..da7c4d7c 100644 --- a/infra/nginx/nginx.conf +++ b/infra/nginx/nginx.conf @@ -27,6 +27,5 @@ http { '"$http_user_agent" '; include conf.d/upstream.conf; - include conf.d/http-server.conf; - include conf.d/ssl-server.conf; + include conf.d/http-server-notssl.conf; } diff --git a/infra/nginx/templates/http-server-notssl.template b/infra/nginx/templates/http-server-notssl.template new file mode 100644 index 00000000..f99be788 --- /dev/null +++ b/infra/nginx/templates/http-server-notssl.template @@ -0,0 +1,14 @@ +server { + listen 80; + server_name ${SERVER_DOMAIN}; + access_log /var/log/nginx/access.log main; + error_log /var/log/nginx/error.log error; + + location ^~ /actuator { + return 404; + } + + location / { + proxy_pass http://backend; + } +} diff --git a/infra/scripts/init-nginx-converter.sh b/infra/scripts/init-nginx-converter.sh index 861b2aeb..e5562b22 100644 --- a/infra/scripts/init-nginx-converter.sh +++ b/infra/scripts/init-nginx-converter.sh @@ -9,6 +9,7 @@ export SERVER_DOMAIN=${SERVER_DOMAIN} export SERVER_PORT=${SERVER_PORT} export BLUE_CONTAINER=${BLUE_CONTAINER} -envsubst '$SERVER_DOMAIN' < /home/ubuntu/moabam/infra/nginx/templates/http-server.template > /home/ubuntu/moabam/infra/nginx/conf.d/http-server.conf -envsubst '$SERVER_DOMAIN' < /home/ubuntu/moabam/infra/nginx/templates/ssl-server.template > /home/ubuntu/moabam/infra/nginx/conf.d/ssl-server.conf +#envsubst '$SERVER_DOMAIN' < /home/ubuntu/moabam/infra/nginx/templates/http-server.template > /home/ubuntu/moabam/infra/nginx/conf.d/http-server.conf +#envsubst '$SERVER_DOMAIN' < /home/ubuntu/moabam/infra/nginx/templates/ssl-server.template > /home/ubuntu/moabam/infra/nginx/conf.d/ssl-server.conf +envsubst '$SERVER_DOMAIN' < /home/ubuntu/moabam/infra/nginx/templates/http-server-notssl.template > /home/ubuntu/moabam/infra/nginx/conf.d/http-server-notssl.conf envsubst '$BLUE_CONTAINER $SERVER_PORT' < /home/ubuntu/moabam/infra/nginx/templates/upstream.template > /home/ubuntu/moabam/infra/nginx/conf.d/upstream.conf diff --git a/readme-image/api.png b/readme-image/api.png new file mode 100644 index 00000000..d58034ba Binary files /dev/null and b/readme-image/api.png differ diff --git a/readme-image/img.png b/readme-image/img.png new file mode 100644 index 00000000..42fda84d Binary files /dev/null and b/readme-image/img.png differ diff --git a/readme-image/img_1.png b/readme-image/img_1.png new file mode 100644 index 00000000..911a57d3 Binary files /dev/null and b/readme-image/img_1.png differ diff --git a/readme-image/table.png b/readme-image/table.png new file mode 100644 index 00000000..e371cd50 Binary files /dev/null and b/readme-image/table.png differ diff --git "a/readme-image/\354\204\234\353\271\204\354\212\244-\354\225\204\355\202\244\355\205\215\354\262\230.png" "b/readme-image/\354\204\234\353\271\204\354\212\244-\354\225\204\355\202\244\355\205\215\354\262\230.png" new file mode 100644 index 00000000..a8400b13 Binary files /dev/null and "b/readme-image/\354\204\234\353\271\204\354\212\244-\354\225\204\355\202\244\355\205\215\354\262\230.png" differ diff --git "a/readme-image/\354\273\250\353\262\244\354\205\230.png" "b/readme-image/\354\273\250\353\262\244\354\205\230.png" new file mode 100644 index 00000000..c2b6cb3f Binary files /dev/null and "b/readme-image/\354\273\250\353\262\244\354\205\230.png" differ diff --git "a/readme-image/\354\275\230\355\224\274\352\267\270.png" "b/readme-image/\354\275\230\355\224\274\352\267\270.png" new file mode 100644 index 00000000..c0cc64a9 Binary files /dev/null and "b/readme-image/\354\275\230\355\224\274\352\267\270.png" differ diff --git "a/readme-image/\355\205\214\354\212\244\355\212\270.png" "b/readme-image/\355\205\214\354\212\244\355\212\270.png" new file mode 100644 index 00000000..fb9929d1 Binary files /dev/null and "b/readme-image/\355\205\214\354\212\244\355\212\270.png" differ diff --git "a/readme-image/\355\214\214\354\235\264\355\224\204\353\235\274\354\235\270.png" "b/readme-image/\355\214\214\354\235\264\355\224\204\353\235\274\354\235\270.png" new file mode 100644 index 00000000..b18d2356 Binary files /dev/null and "b/readme-image/\355\214\214\354\235\264\355\224\204\353\235\274\354\235\270.png" differ diff --git "a/readme-image/\355\230\221\354\227\205.png" "b/readme-image/\355\230\221\354\227\205.png" new file mode 100644 index 00000000..37d8cc3c Binary files /dev/null and "b/readme-image/\355\230\221\354\227\205.png" differ diff --git a/src/main/java/com/moabam/api/application/coupon/CouponCacheService.java b/src/main/java/com/moabam/api/application/coupon/CouponCacheService.java new file mode 100644 index 00000000..0880a42e --- /dev/null +++ b/src/main/java/com/moabam/api/application/coupon/CouponCacheService.java @@ -0,0 +1,34 @@ +package com.moabam.api.application.coupon; + +import java.time.LocalDate; +import java.util.Optional; + +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; + +import com.moabam.api.domain.coupon.Coupon; +import com.moabam.api.domain.coupon.repository.CouponRepository; +import com.moabam.global.error.exception.NotFoundException; +import com.moabam.global.error.model.ErrorMessage; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +@CacheConfig(cacheNames = "coupons") +public class CouponCacheService { + + private final CouponRepository couponRepository; + + @Cacheable(key = "#couponName + #now") + public Coupon getByNameAndStartAt(String couponName, LocalDate now) { + return couponRepository.findByNameAndStartAt(couponName, now) + .orElseThrow(() -> new NotFoundException(ErrorMessage.INVALID_COUPON_PERIOD)); + } + + @Cacheable(key = "#now") + public Optional getByStartAt(LocalDate now) { + return couponRepository.findByStartAt(now); + } +} diff --git a/src/main/java/com/moabam/api/application/coupon/CouponManageService.java b/src/main/java/com/moabam/api/application/coupon/CouponManageService.java index 3cda6f9a..cadf613c 100644 --- a/src/main/java/com/moabam/api/application/coupon/CouponManageService.java +++ b/src/main/java/com/moabam/api/application/coupon/CouponManageService.java @@ -11,12 +11,10 @@ import com.moabam.api.domain.coupon.Coupon; import com.moabam.api.domain.coupon.CouponWallet; import com.moabam.api.domain.coupon.repository.CouponManageRepository; -import com.moabam.api.domain.coupon.repository.CouponRepository; import com.moabam.api.domain.coupon.repository.CouponWalletRepository; import com.moabam.global.common.util.ClockHolder; import com.moabam.global.error.exception.BadRequestException; import com.moabam.global.error.exception.ConflictException; -import com.moabam.global.error.exception.NotFoundException; import com.moabam.global.error.model.ErrorMessage; import lombok.RequiredArgsConstructor; @@ -34,14 +32,14 @@ public class CouponManageService { private final ClockHolder clockHolder; private final NotificationService notificationService; - private final CouponRepository couponRepository; + private final CouponCacheService couponCacheService; private final CouponManageRepository couponManageRepository; private final CouponWalletRepository couponWalletRepository; @Scheduled(fixedDelay = 1000) public void issue() { LocalDate now = clockHolder.date(); - Optional optionalCoupon = couponRepository.findByStartAt(now); + Optional optionalCoupon = couponCacheService.getByStartAt(now); if (optionalCoupon.isEmpty()) { return; @@ -70,21 +68,20 @@ public void issue() { couponManageRepository.increase(couponName, membersId.size()); } + public void delete(String couponName) { + couponManageRepository.deleteQueue(couponName); + couponManageRepository.deleteCount(couponName); + } + public void registerQueue(String couponName, Long memberId) { double registerTime = System.currentTimeMillis(); validateRegisterQueue(couponName, memberId); couponManageRepository.addIfAbsentQueue(couponName, memberId, registerTime); } - public void delete(String couponName) { - couponManageRepository.deleteQueue(couponName); - couponManageRepository.deleteCount(couponName); - } - private void validateRegisterQueue(String couponName, Long memberId) { LocalDate now = clockHolder.date(); - Coupon coupon = couponRepository.findByNameAndStartAt(couponName, now) - .orElseThrow(() -> new NotFoundException(ErrorMessage.INVALID_COUPON_PERIOD)); + Coupon coupon = couponCacheService.getByNameAndStartAt(couponName, now); if (couponManageRepository.hasValue(couponName, memberId)) { throw new ConflictException(ErrorMessage.CONFLICT_COUPON_ISSUE); diff --git a/src/main/java/com/moabam/api/application/member/MemberService.java b/src/main/java/com/moabam/api/application/member/MemberService.java index ef3a761c..7c900af5 100644 --- a/src/main/java/com/moabam/api/application/member/MemberService.java +++ b/src/main/java/com/moabam/api/application/member/MemberService.java @@ -89,7 +89,7 @@ public void delete(Member member) { throw new BadRequestException(NEED_TO_EXIT_ALL_ROOMS); } - member.delete(clockHolder.times()); + member.delete(clockHolder.dateTime()); memberRepository.flush(); memberRepository.delete(member); rankingService.removeRanking(MemberMapper.toRankingInfo(member)); diff --git a/src/main/java/com/moabam/api/application/notification/NotificationService.java b/src/main/java/com/moabam/api/application/notification/NotificationService.java index 3d8c859b..ab5ef937 100644 --- a/src/main/java/com/moabam/api/application/notification/NotificationService.java +++ b/src/main/java/com/moabam/api/application/notification/NotificationService.java @@ -64,7 +64,7 @@ public void sendCouponIssueResult(Long memberId, String couponName, String body) @Scheduled(cron = "0 55 * * * *") public void sendCertificationTime() { - int certificationTime = (clockHolder.times().getHour() + ONE_HOUR) % HOURS_IN_A_DAY; + int certificationTime = (clockHolder.dateTime().getHour() + ONE_HOUR) % HOURS_IN_A_DAY; List participants = participantSearchRepository.findAllByRoomCertifyTime(certificationTime); participants.parallelStream().forEach(participant -> { diff --git a/src/main/java/com/moabam/api/application/ranking/RankingMapper.java b/src/main/java/com/moabam/api/application/ranking/RankingMapper.java index 817ccdc8..5d17e5d1 100644 --- a/src/main/java/com/moabam/api/application/ranking/RankingMapper.java +++ b/src/main/java/com/moabam/api/application/ranking/RankingMapper.java @@ -25,7 +25,7 @@ public static TopRankingInfo topRankingResponse(int rank, long score, RankingInf public static TopRankingInfo topRankingResponse(int rank, UpdateRanking updateRanking) { return TopRankingInfo.builder() - .rank(rank + 1) + .rank(rank) .score(updateRanking.score()) .nickname(updateRanking.rankingInfo().nickname()) .image(updateRanking.rankingInfo().image()) diff --git a/src/main/java/com/moabam/api/application/ranking/RankingService.java b/src/main/java/com/moabam/api/application/ranking/RankingService.java index 651093f4..4b7b1e9e 100644 --- a/src/main/java/com/moabam/api/application/ranking/RankingService.java +++ b/src/main/java/com/moabam/api/application/ranking/RankingService.java @@ -51,17 +51,17 @@ public void removeRanking(RankingInfo rankingInfo) { public TopRankingResponse getMemberRanking(UpdateRanking myRankingInfo) { List topRankings = getTopRankings(); - Long myRanking = zSetRedisRepository.reverseRank(RANKING, myRankingInfo.rankingInfo()); + long myRanking = zSetRedisRepository.reverseRank(RANKING, myRankingInfo.rankingInfo()) + 1; Optional myTopRanking = topRankings.stream() .filter(topRankingInfo -> Objects.equals(topRankingInfo.memberId(), myRankingInfo.rankingInfo().memberId())) .findFirst(); if (myTopRanking.isPresent()) { - myRanking = (long)myTopRanking.get().rank(); + myRanking = myTopRanking.get().rank(); } - TopRankingInfo myRankingInfoResponse = RankingMapper.topRankingResponse(myRanking.intValue(), myRankingInfo); + TopRankingInfo myRankingInfoResponse = RankingMapper.topRankingResponse((int)myRanking, myRankingInfo); return RankingMapper.topRankingResponses(myRankingInfoResponse, topRankings); } diff --git a/src/main/java/com/moabam/api/application/room/CertificationService.java b/src/main/java/com/moabam/api/application/room/CertificationService.java index c4e86434..d65f0081 100644 --- a/src/main/java/com/moabam/api/application/room/CertificationService.java +++ b/src/main/java/com/moabam/api/application/room/CertificationService.java @@ -70,7 +70,7 @@ public CertifiedMemberInfo getCertifiedMemberInfo(Long memberId, Long roomId, Li case NIGHT -> BugType.NIGHT; }; - validateCertifyTime(clockHolder.times(), room.getCertifyTime()); + validateCertifyTime(clockHolder.dateTime(), room.getCertifyTime()); validateAlreadyCertified(memberId, roomId, today); certifyMember(memberId, roomId, participant, member, imageUrls); diff --git a/src/main/java/com/moabam/api/application/room/RoomService.java b/src/main/java/com/moabam/api/application/room/RoomService.java index 1f2ccdce..e274a560 100644 --- a/src/main/java/com/moabam/api/application/room/RoomService.java +++ b/src/main/java/com/moabam/api/application/room/RoomService.java @@ -216,7 +216,7 @@ private void validateEnteredRoomCount(Long memberId, RoomType roomType) { } private void validateCertifyTime(Room room) { - LocalDateTime now = clockHolder.times(); + LocalDateTime now = clockHolder.dateTime(); LocalTime targetTime = LocalTime.of(room.getCertifyTime(), 0); LocalDateTime targetDateTime = LocalDateTime.of(now.toLocalDate(), targetTime); diff --git a/src/main/java/com/moabam/api/application/room/SearchService.java b/src/main/java/com/moabam/api/application/room/SearchService.java index f489d966..1e60f0e8 100644 --- a/src/main/java/com/moabam/api/application/room/SearchService.java +++ b/src/main/java/com/moabam/api/application/room/SearchService.java @@ -162,25 +162,7 @@ public GetAllRoomsResponse getAllRooms(@Nullable RoomType roomType, @Nullable Lo public GetAllRoomsResponse searchRooms(String keyword, @Nullable RoomType roomType, @Nullable Long roomId) { List getAllRoomResponse = new ArrayList<>(); - List rooms = new ArrayList<>(); - - if (roomId == null && roomType == null) { - rooms = new ArrayList<>(roomRepository.searchByKeyword(keyword)); - } - - if (roomId == null && roomType != null) { - rooms = new ArrayList<>(roomRepository.searchByKeywordAndRoomType(keyword, roomType.name())); - } - - if (roomId != null && roomType == null) { - rooms = new ArrayList<>(roomRepository.searchByKeywordAndRoomId(keyword, roomId)); - } - - if (roomId != null && roomType != null) { - rooms = new ArrayList<>( - roomRepository.searchByKeywordAndRoomIdAndRoomType(keyword, roomType.name(), roomId)); - } - + List rooms = new ArrayList<>(roomSearchRepository.searchWithKeyword(keyword, roomType, roomId)); boolean hasNext = isHasNext(getAllRoomResponse, rooms); return RoomMapper.toSearchAllRoomsResponse(hasNext, getAllRoomResponse); @@ -402,7 +384,7 @@ private double calculateCompletePercentage(int certifiedMembersCount, Room room, return 0; } - LocalDateTime now = clockHolder.times(); + LocalDateTime now = clockHolder.dateTime(); LocalTime targetTime = LocalTime.of(room.getCertifyTime(), 0); LocalDateTime targetDateTime = LocalDateTime.of(now.toLocalDate(), targetTime); diff --git a/src/main/java/com/moabam/api/domain/coupon/repository/CouponRepository.java b/src/main/java/com/moabam/api/domain/coupon/repository/CouponRepository.java index f236858d..a4ed3c5f 100644 --- a/src/main/java/com/moabam/api/domain/coupon/repository/CouponRepository.java +++ b/src/main/java/com/moabam/api/domain/coupon/repository/CouponRepository.java @@ -16,6 +16,4 @@ public interface CouponRepository extends JpaRepository { Optional findByStartAt(LocalDate startAt); Optional findByNameAndStartAt(String couponName, LocalDate startAt); - - boolean existsByNameAndStartAt(String couponName, LocalDate startAt); } diff --git a/src/main/java/com/moabam/api/domain/room/Room.java b/src/main/java/com/moabam/api/domain/room/Room.java index 6817cb4d..d27135a8 100644 --- a/src/main/java/com/moabam/api/domain/room/Room.java +++ b/src/main/java/com/moabam/api/domain/room/Room.java @@ -55,7 +55,8 @@ public class Room extends BaseTimeEntity { @Column(name = "id") private Long id; - @Column(name = "title", nullable = false, length = 20) + @Column(name = "title", + columnDefinition = "VARCHAR(20) NOT NULL, FULLTEXT INDEX full_title (title) WITH PARSER ngram") private String title; @Column(name = "password", length = 8) @@ -89,7 +90,8 @@ public class Room extends BaseTimeEntity { @Column(name = "room_image", length = 500) private String roomImage; - @Column(name = "manager_nickname", length = 30) + @Column(name = "manager_nickname", + columnDefinition = "VARCHAR(30), FULLTEXT INDEX full_nickname (manager_nickname) WITH PARSER ngram") private String managerNickname; @Column(name = "deleted_at") diff --git a/src/main/java/com/moabam/api/domain/room/Routine.java b/src/main/java/com/moabam/api/domain/room/Routine.java index 6b3f0a86..0e43006b 100644 --- a/src/main/java/com/moabam/api/domain/room/Routine.java +++ b/src/main/java/com/moabam/api/domain/room/Routine.java @@ -37,7 +37,8 @@ public class Routine extends BaseTimeEntity { @JoinColumn(name = "room_id", updatable = false) private Room room; - @Column(name = "content", nullable = false, length = 20) + @Column(name = "content", + columnDefinition = "VARCHAR(20) NOT NULL, FULLTEXT INDEX full_content (content) WITH PARSER ngram") private String content; @Builder diff --git a/src/main/java/com/moabam/api/domain/room/repository/RoomSearchRepository.java b/src/main/java/com/moabam/api/domain/room/repository/RoomSearchRepository.java index a815760e..04f7893c 100644 --- a/src/main/java/com/moabam/api/domain/room/repository/RoomSearchRepository.java +++ b/src/main/java/com/moabam/api/domain/room/repository/RoomSearchRepository.java @@ -1,6 +1,7 @@ package com.moabam.api.domain.room.repository; import static com.moabam.api.domain.room.QRoom.*; +import static com.moabam.api.domain.room.QRoutine.*; import static com.moabam.global.common.util.GlobalConstant.*; import java.util.List; @@ -10,6 +11,8 @@ import com.moabam.api.domain.room.Room; import com.moabam.api.domain.room.RoomType; import com.moabam.global.common.util.DynamicQuery; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.core.types.dsl.Expressions; import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; @@ -18,6 +21,9 @@ @RequiredArgsConstructor public class RoomSearchRepository { + private static final double MATCH_THRESHOLD = 0.0; + private static final String MATCH_AGAINST_TEMPLATE = "function('match_against', {0}, {1})"; + private final JPAQueryFactory jpaQueryFactory; public List findAllWithNoOffset(RoomType roomType, Long roomId) { @@ -31,4 +37,30 @@ public List findAllWithNoOffset(RoomType roomType, Long roomId) { .limit(ROOM_FIXED_SEARCH_SIZE + 1L) .fetch(); } + + public List searchWithKeyword(String keyword, RoomType roomType, Long roomId) { + return jpaQueryFactory.selectFrom(room) + .distinct() + .leftJoin(routine).on(room.id.eq(routine.room.id)) + .where( + matchAgainst(keyword), + DynamicQuery.generateEq(roomType, room.roomType::eq), + DynamicQuery.generateEq(roomId, room.id::lt), + room.deletedAt.isNull() + ) + .orderBy(room.id.desc()) + .limit(11) + .fetch(); + } + + private BooleanExpression matchAgainst(String keyword) { + keyword = "\"" + keyword + "\""; + + return Expressions.numberTemplate(Double.class, MATCH_AGAINST_TEMPLATE, room.title, keyword) + .gt(MATCH_THRESHOLD) + .or(Expressions.numberTemplate(Double.class, MATCH_AGAINST_TEMPLATE, room.managerNickname, keyword) + .gt(MATCH_THRESHOLD)) + .or(Expressions.numberTemplate(Double.class, MATCH_AGAINST_TEMPLATE, routine.content, keyword) + .gt(MATCH_THRESHOLD)); + } } diff --git a/src/main/java/com/moabam/global/auth/filter/CorsFilter.java b/src/main/java/com/moabam/global/auth/filter/CorsFilter.java index bcd7e4d8..a74abb5d 100644 --- a/src/main/java/com/moabam/global/auth/filter/CorsFilter.java +++ b/src/main/java/com/moabam/global/auth/filter/CorsFilter.java @@ -3,6 +3,7 @@ import java.io.IOException; import java.util.Objects; +import org.springframework.context.annotation.Profile; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; @@ -22,6 +23,7 @@ @Slf4j @Order(0) +@Profile("!local") @Component @RequiredArgsConstructor public class CorsFilter extends OncePerRequestFilter { diff --git a/src/main/java/com/moabam/global/common/annotation/SlackNotification.java b/src/main/java/com/moabam/global/common/annotation/SlackNotification.java new file mode 100644 index 00000000..17863400 --- /dev/null +++ b/src/main/java/com/moabam/global/common/annotation/SlackNotification.java @@ -0,0 +1,12 @@ +package com.moabam.global.common.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface SlackNotification { + +} diff --git a/src/main/java/com/moabam/global/common/util/ClockHolder.java b/src/main/java/com/moabam/global/common/util/ClockHolder.java index 1ba7a0c5..4afc579b 100644 --- a/src/main/java/com/moabam/global/common/util/ClockHolder.java +++ b/src/main/java/com/moabam/global/common/util/ClockHolder.java @@ -2,10 +2,15 @@ import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.LocalTime; public interface ClockHolder { - LocalDateTime times(); + LocalDateTime dateTime(); LocalDate date(); + + LocalTime time(); + + LocalTime endOfDay(); } diff --git a/src/main/java/com/moabam/global/common/util/SlackNotificationAspect.java b/src/main/java/com/moabam/global/common/util/SlackNotificationAspect.java new file mode 100644 index 00000000..e50f41c7 --- /dev/null +++ b/src/main/java/com/moabam/global/common/util/SlackNotificationAspect.java @@ -0,0 +1,29 @@ +package com.moabam.global.common.util; + +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; + +import com.moabam.api.infrastructure.slack.SlackService; + +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; + +@Aspect +@Component +@Profile({"dev", "prod"}) +@RequiredArgsConstructor +public class SlackNotificationAspect { + + private final SlackService slackService; + + @Around("@annotation(com.moabam.global.common.annotation.SlackNotification) && args(request, exception)") + public Object sendSlack(ProceedingJoinPoint joinPoint, HttpServletRequest request, Exception exception) + throws Throwable { + slackService.send(request, exception); + + return joinPoint.proceed(); + } +} diff --git a/src/main/java/com/moabam/global/common/util/SystemClockHolder.java b/src/main/java/com/moabam/global/common/util/SystemClockHolder.java index 1d24cdc3..756457bc 100644 --- a/src/main/java/com/moabam/global/common/util/SystemClockHolder.java +++ b/src/main/java/com/moabam/global/common/util/SystemClockHolder.java @@ -2,16 +2,17 @@ import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.LocalTime; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Component; @Component -@Profile({"dev", "prod"}) +@Profile({"dev", "prod", "local"}) public class SystemClockHolder implements ClockHolder { @Override - public LocalDateTime times() { + public LocalDateTime dateTime() { return LocalDateTime.now(); } @@ -19,4 +20,14 @@ public LocalDateTime times() { public LocalDate date() { return LocalDate.now(); } + + @Override + public LocalTime time() { + return LocalTime.now(); + } + + @Override + public LocalTime endOfDay() { + return LocalTime.of(23, 59, 59, 999_999_999); + } } diff --git a/src/main/java/com/moabam/global/config/CacheConfig.java b/src/main/java/com/moabam/global/config/CacheConfig.java new file mode 100644 index 00000000..974f1bf3 --- /dev/null +++ b/src/main/java/com/moabam/global/config/CacheConfig.java @@ -0,0 +1,62 @@ +package com.moabam.global.config; + +import static org.springframework.data.redis.serializer.RedisSerializationContext.*; + +import java.time.Duration; +import java.time.LocalTime; + +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.cache.RedisCacheConfiguration; +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator; +import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.moabam.global.common.util.ClockHolder; + +import lombok.RequiredArgsConstructor; + +@EnableCaching +@Configuration +@RequiredArgsConstructor +public class CacheConfig { + + private final ClockHolder clockHolder; + + @Bean + public RedisCacheConfiguration redisCacheConfiguration() { + var strSerializePair = SerializationPair.fromSerializer(new StringRedisSerializer()); + var objSerializePair = SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer(objectMapper())); + + return RedisCacheConfiguration.defaultCacheConfig() + .entryTtl(getTtl()) + .serializeKeysWith(strSerializePair) + .serializeValuesWith(objSerializePair); + } + + private Duration getTtl() { + LocalTime now = clockHolder.time(); + LocalTime end = clockHolder.endOfDay(); + + return Duration.between(now, end); + } + + private ObjectMapper objectMapper() { + PolymorphicTypeValidator polymorphicTypeValidator = BasicPolymorphicTypeValidator.builder() + .allowIfSubType(Object.class) + .build(); + + return JsonMapper.builder() + .polymorphicTypeValidator(polymorphicTypeValidator) + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .addModule(new JavaTimeModule()) + .activateDefaultTyping(polymorphicTypeValidator, ObjectMapper.DefaultTyping.NON_FINAL) + .build(); + } +} diff --git a/src/main/java/com/moabam/global/config/SqlFunctionContributor.java b/src/main/java/com/moabam/global/config/SqlFunctionContributor.java new file mode 100644 index 00000000..e0d7bb69 --- /dev/null +++ b/src/main/java/com/moabam/global/config/SqlFunctionContributor.java @@ -0,0 +1,17 @@ +package com.moabam.global.config; + +import static org.hibernate.type.StandardBasicTypes.*; + +import org.hibernate.boot.model.FunctionContributions; +import org.hibernate.boot.model.FunctionContributor; + +public class SqlFunctionContributor implements FunctionContributor { + + @Override + public void contributeFunctions(FunctionContributions functionContributions) { + functionContributions.getFunctionRegistry() + .registerPattern( + "match_against", "MATCH (?1) AGAINST (?2 IN NATURAL LANGUAGE MODE)", + functionContributions.getTypeConfiguration().getBasicTypeRegistry().resolve(DOUBLE)); + } +} diff --git a/src/main/java/com/moabam/global/config/WebConfig.java b/src/main/java/com/moabam/global/config/WebConfig.java index e5971d20..fb6bb67e 100644 --- a/src/main/java/com/moabam/global/config/WebConfig.java +++ b/src/main/java/com/moabam/global/config/WebConfig.java @@ -43,8 +43,9 @@ public PathResolver pathResolver() { PathMapper.parsePath("/favicon/*"), PathMapper.parsePath("/*/icon-*"), PathMapper.parsePath("/favicon.ico"), - PathMapper.parsePath("/v3/api-docs"), - PathMapper.parsePath("/swagger*/**"), + PathMapper.parsePath("/swagger-ui/**"), + PathMapper.parsePath("/swagger-resources/**"), + PathMapper.parsePath("/v3/api-docs/**"), PathMapper.pathWithMethod("/serverTime", List.of(HttpMethod.GET)))) .build(); diff --git a/src/main/java/com/moabam/global/error/handler/GlobalExceptionHandler.java b/src/main/java/com/moabam/global/error/handler/GlobalExceptionHandler.java index 766a7e01..8fa4d5a3 100644 --- a/src/main/java/com/moabam/global/error/handler/GlobalExceptionHandler.java +++ b/src/main/java/com/moabam/global/error/handler/GlobalExceptionHandler.java @@ -10,12 +10,14 @@ import org.springframework.http.HttpStatus; import org.springframework.validation.FieldError; import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.MissingRequestValueException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; import org.springframework.web.multipart.MaxUploadSizeExceededException; +import com.moabam.global.common.annotation.SlackNotification; import com.moabam.global.error.exception.BadRequestException; import com.moabam.global.error.exception.ConflictException; import com.moabam.global.error.exception.FcmException; @@ -26,9 +28,18 @@ import com.moabam.global.error.exception.UnauthorizedException; import com.moabam.global.error.model.ErrorResponse; +import jakarta.servlet.http.HttpServletRequest; + @RestControllerAdvice public class GlobalExceptionHandler { + @SlackNotification + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + @ExceptionHandler(Exception.class) + protected ErrorResponse handleException(HttpServletRequest request, Exception exception) { + return new ErrorResponse(exception.getMessage(), null); + } + @ResponseStatus(HttpStatus.NOT_FOUND) @ExceptionHandler(NotFoundException.class) protected ErrorResponse handleNotFoundException(MoabamException exception) { @@ -104,6 +115,12 @@ protected ErrorResponse handleMethodArgumentTypeMismatchException(MethodArgument return new ErrorResponse(message, null); } + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ExceptionHandler(MissingRequestValueException.class) + protected ErrorResponse handleMethodArgumentTypeMismatchException(MissingRequestValueException exception) { + return new ErrorResponse(exception.getMessage(), null); + } + @ExceptionHandler(MaxUploadSizeExceededException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) protected ErrorResponse handleMaxSizeException(MaxUploadSizeExceededException exception) { diff --git a/src/main/java/com/moabam/global/error/handler/SlackExceptionHandler.java b/src/main/java/com/moabam/global/error/handler/SlackExceptionHandler.java deleted file mode 100644 index b144c62b..00000000 --- a/src/main/java/com/moabam/global/error/handler/SlackExceptionHandler.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.moabam.global.error.handler; - -import org.springframework.context.annotation.Profile; -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.ResponseStatus; -import org.springframework.web.bind.annotation.RestControllerAdvice; - -import com.moabam.api.infrastructure.slack.SlackService; -import com.moabam.global.error.model.ErrorResponse; - -import jakarta.servlet.http.HttpServletRequest; -import lombok.RequiredArgsConstructor; - -@RestControllerAdvice -@Profile({"dev", "prod"}) -@RequiredArgsConstructor -public class SlackExceptionHandler { - - private final SlackService slackService; - - @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) - @ExceptionHandler(Exception.class) - protected ErrorResponse handleException(HttpServletRequest request, Exception exception) throws Exception { - slackService.send(request, exception); - - return new ErrorResponse(exception.getMessage(), null); - } -} diff --git a/src/main/resources/META-INF/services/org.hibernate.boot.model.FunctionContributor b/src/main/resources/META-INF/services/org.hibernate.boot.model.FunctionContributor new file mode 100644 index 00000000..9d3615bf --- /dev/null +++ b/src/main/resources/META-INF/services/org.hibernate.boot.model.FunctionContributor @@ -0,0 +1 @@ +com.moabam.global.config.SqlFunctionContributor diff --git a/src/main/resources/config b/src/main/resources/config index 6c56ec63..9c4b72d9 160000 --- a/src/main/resources/config +++ b/src/main/resources/config @@ -1 +1 @@ -Subproject commit 6c56ec639c26b593ae2ea4ba98c4db1f4c0cf83c +Subproject commit 9c4b72d99a8c789257c85b5f3057d54266009785 diff --git a/src/main/resources/static/docs/coupon.html b/src/main/resources/static/docs/coupon.html deleted file mode 100644 index 8041f735..00000000 --- a/src/main/resources/static/docs/coupon.html +++ /dev/null @@ -1,723 +0,0 @@ - - - - - - - -์ฟ ํฐ(Coupon) - - - - - -
-
-

์ฟ ํฐ(Coupon)

-
-
-
-
์ฟ ํฐ์— ๋Œ€ํ•ด ์ƒ์„ฑ/์‚ญ์ œ/์กฐํšŒ/๋ฐœ๊ธ‰/์‚ฌ์šฉ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
-
-
-
-
-

์ฟ ํฐ ์ƒ์„ฑ

-
-
-
๊ด€๋ฆฌ์ž๊ฐ€ ์ฟ ํฐ์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
-
-
-

์š”์ฒญ

-
-
-
POST /admins/coupons HTTP/1.1
-Content-Type: application/json;charset=UTF-8
-Content-Length: 186
-Host: localhost:8080
-
-{
-  "name" : "couponName",
-  "description" : "coupon description",
-  "type" : "ํ™ฉ๊ธˆ",
-  "point" : 10,
-  "maxCount" : 10,
-  "startAt" : "2023-02-01",
-  "openAt" : "2023-01-01"
-}
-
-
-

์‘๋‹ต

-
-
-
HTTP/1.1 201 Created
-Access-Control-Allow-Origin: http://localhost:8080
-Access-Control-Allow-Methods: GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH
-Access-Control-Allow-Headers: Origin, Accept, Access-Control-Request-Method, Access-Control-Request-Headers, X-Requested-With,Content-Type, Referer
-Access-Control-Allow-Credentials: true
-Access-Control-Max-Age: 3600
-
-
-
-
-
-

์ฟ ํฐ ์‚ญ์ œ

-
-
-
๊ด€๋ฆฌ์ž๊ฐ€ ์ฟ ํฐ ID์™€ ์ผ์น˜ํ•˜๋Š” ์ฟ ํฐ์„ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค.
-
-
-

์š”์ฒญ

-
-
-
DELETE /admins/coupons/36 HTTP/1.1
-Host: localhost:8080
-
-
-

์‘๋‹ต

-
-
-
HTTP/1.1 200 OK
-Access-Control-Allow-Origin: http://localhost:8080
-Access-Control-Allow-Methods: GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH
-Access-Control-Allow-Headers: Origin, Accept, Access-Control-Request-Method, Access-Control-Request-Headers, X-Requested-With,Content-Type, Referer
-Access-Control-Allow-Credentials: true
-Access-Control-Max-Age: 3600
-
-
-
-
-
-

ํŠน์ • ์ฟ ํฐ ์กฐํšŒ

-
-
-
๊ด€๋ฆฌ์ž ํ˜น์€ ์‚ฌ์šฉ์ž๊ฐ€ ํŠน์ • ID์™€ ์ผ์น˜ํ•˜๋Š” ์ฟ ํฐ์„ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค.
-
-
-
-

์š”์ฒญ

-
-
-
GET /coupons/24 HTTP/1.1
-Host: localhost:8080
-
-
-

์‘๋‹ต

-
-
-
HTTP/1.1 200 OK
-Access-Control-Allow-Origin: http://localhost:8080
-Access-Control-Allow-Methods: GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH
-Access-Control-Allow-Headers: Origin, Accept, Access-Control-Request-Method, Access-Control-Request-Headers, X-Requested-With,Content-Type, Referer
-Access-Control-Allow-Credentials: true
-Access-Control-Max-Age: 3600
-Content-Type: application/json
-Content-Length: 202
-
-{
-  "id" : 24,
-  "adminId" : 1,
-  "name" : "couponName",
-  "description" : "",
-  "point" : 10,
-  "maxCount" : 100,
-  "type" : "MORNING",
-  "startAt" : "2023-02-01",
-  "openAt" : "2023-01-01"
-}
-
-
-
-
-
-
-

์ƒํƒœ์— ๋”ฐ๋ฅธ ์ฟ ํฐ๋“ค์„ ์กฐํšŒ

-
-
-
๊ด€๋ฆฌ์ž ํ˜น์€ ์‚ฌ์šฉ์ž๊ฐ€ ๋‚ ์งœ ์ƒํƒœ์— ๋”ฐ๋ผ ์ฟ ํฐ๋“ค์„ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค.
-
-
-
-

์š”์ฒญ

-
-
-
POST /coupons/search HTTP/1.1
-Content-Type: application/json;charset=UTF-8
-Content-Length: 44
-Host: localhost:8080
-
-{
-  "opened" : false,
-  "ended" : false
-}
-
-
-

์‘๋‹ต

-
-
-
HTTP/1.1 200 OK
-Access-Control-Allow-Origin: http://localhost:8080
-Access-Control-Allow-Methods: GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH
-Access-Control-Allow-Headers: Origin, Accept, Access-Control-Request-Method, Access-Control-Request-Headers, X-Requested-With,Content-Type, Referer
-Access-Control-Allow-Credentials: true
-Access-Control-Max-Age: 3600
-Content-Type: application/json
-Content-Length: 203
-
-[ {
-  "id" : 25,
-  "adminId" : 1,
-  "name" : "coupon1",
-  "description" : "",
-  "point" : 10,
-  "maxCount" : 100,
-  "type" : "MORNING",
-  "startAt" : "2023-03-01",
-  "openAt" : "2023-01-01"
-} ]
-
-
-
-
-
-
-

ํŠน์ • ์ฟ ํฐ์— ๋Œ€ํ•ด ๋ฐœ๊ธ‰

-
-
-
์‚ฌ์šฉ์ž๊ฐ€ ๋ฐœ๊ธ‰ ๊ฐ€๋Šฅํ•œ ์ฟ ํฐ์„ ์„ ์ฐฉ์ˆœ์œผ๋กœ ๋ฐœ๊ธ‰ ๋ฐ›์Šต๋‹ˆ๋‹ค.
-
-
-
-

์š”์ฒญ

-
-
-
POST /coupons HTTP/1.1
-Content-Type: application/x-www-form-urlencoded
-Host: localhost:8080
-Content-Length: 21
-
-couponName=couponName
-
-
-

์‘๋‹ต

-
-
-
HTTP/1.1 409 Conflict
-Access-Control-Allow-Origin: http://localhost:8080
-Access-Control-Allow-Methods: GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH
-Access-Control-Allow-Headers: Origin, Accept, Access-Control-Request-Method, Access-Control-Request-Headers, X-Requested-With,Content-Type, Referer
-Access-Control-Allow-Credentials: true
-Access-Control-Max-Age: 3600
-Content-Type: application/json
-Content-Length: 65
-
-{
-  "message" : "์ด๋ฏธ ์ฟ ํฐ ๋ฐœ๊ธ‰์— ์„ฑ๊ณตํ–ˆ์Šต๋‹ˆ๋‹ค!"
-}
-
-
-
-
-
-
-

ํŠน์ • ์‚ฌ์šฉ์ž์˜ ์ฟ ํฐ ๋ณด๊ด€ํ•จ์„ ์กฐํšŒ

-
-
-
์‚ฌ์šฉ์ž๊ฐ€ ์ž์‹ ์˜ ๋ณด๊ด€ํ•จ์— ์žˆ๋Š” ์ฟ ํฐ๋“ค์„ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค.
-
-
-
-

์š”์ฒญ

-
-
-
GET /my-coupons HTTP/1.1
-Host: localhost:8080
-
-
-

์‘๋‹ต

-
-
-
HTTP/1.1 200 OK
-Access-Control-Allow-Origin: http://localhost:8080
-Access-Control-Allow-Methods: GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH
-Access-Control-Allow-Headers: Origin, Accept, Access-Control-Request-Method, Access-Control-Request-Headers, X-Requested-With,Content-Type, Referer
-Access-Control-Allow-Credentials: true
-Access-Control-Max-Age: 3600
-Content-Type: application/json
-Content-Length: 3
-
-[ ]
-
-
-
-
-
-
-

์ฟ ํฐ์„ ์‚ฌ์šฉ

-
-
-
์‚ฌ์šฉ์ž๊ฐ€ ์ž์‹ ์˜ ๋ณด๊ด€ํ•จ์— ์žˆ๋Š” ์ฟ ํฐ๋“ค์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
-
-
-
-

์š”์ฒญ

-
-
-
POST /my-coupons/8 HTTP/1.1
-Host: localhost:8080
-Content-Type: application/x-www-form-urlencoded
-
-
-

์‘๋‹ต

-
-
-
HTTP/1.1 200 OK
-Access-Control-Allow-Origin: http://localhost:8080
-Access-Control-Allow-Methods: GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH
-Access-Control-Allow-Headers: Origin, Accept, Access-Control-Request-Method, Access-Control-Request-Headers, X-Requested-With,Content-Type, Referer
-Access-Control-Allow-Credentials: true
-Access-Control-Max-Age: 3600
-
-
-
-
-
-
-
-
- - - \ No newline at end of file diff --git a/src/main/resources/static/docs/index.html b/src/main/resources/static/docs/index.html deleted file mode 100644 index 215db37a..00000000 --- a/src/main/resources/static/docs/index.html +++ /dev/null @@ -1,630 +0,0 @@ - - - - - - - -MOABAM API ๋ฌธ์„œ - - - - - - - -
-
-

1. ๊ฐœ์š”

-
-
-

์ด API ๋ฌธ์„œ๋Š” 'MOABAM' ํ”„๋กœ์ ํŠธ์˜ ์‚ฐ์ถœ๋ฌผ์ž…๋‹ˆ๋‹ค.

-
-
-

1.1. API ์„œ๋ฒ„ ๊ฒฝ๋กœ

- ----- - - - - - - - - - - - - - - - - - -

ํ™˜๊ฒฝ

DNS

๋น„๊ณ 

๊ฐœ๋ฐœ(dev)

dev.moabam.com

์šด์˜(prod)

www.moabam.com

-
- - - - - -
- - -
-

ํ•ด๋‹น ํ”„๋กœ์ ํŠธ API ๋ฌธ์„œ๋Š” [ํŠน์ด์‚ฌํ•ญ]์ž…๋‹ˆ๋‹ค.

-
-
-
-
- - - - - -
- - -
-

ํ•ด๋‹น ํ”„๋กœ์ ํŠธ API ๋ฌธ์„œ๋Š” [์ฃผ์˜์‚ฌํ•ญ]์ž…๋‹ˆ๋‹ค.

-
-
-
-
-
-

1.2. ์‘๋‹ตํ˜•์‹

-
-

ํ”„๋กœ์ ํŠธ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์‘๋‹ตํ˜•์‹์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

-
-
-

1.2.1. ์ •์ƒ(2XX)

- ---- - - - - - - - - - - - - -
์‘๋‹ต๋ฐ์ดํ„ฐ๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ์‘๋‹ต๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ
-
-
{
-
-}
-
-
-
-
{
-  "name": "Hong-Dosan"
-}
-
-
-
-
-

1.2.2. ์ƒํƒœ์ฝ”๋“œ(HttpStatus)

-
-

์‘๋‹ต์‹œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์‘๋‹ต์ƒํƒœ ํ—ค๋”, ์‘๋‹ต์ฝ”๋“œ ๋ฐ ์‘๋‹ต๋ฉ”์‹œ์ง€๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

-
- ---- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
HttpStatus์„ค๋ช…

OK(200)

์ •์ƒ ์‘๋‹ต

CREATED(201)

์ƒˆ๋กœ์šด ๋ฆฌ์†Œ์Šค ์ƒ์„ฑ

BAD_REQUEST(400)

์š”์ฒญ๊ฐ’ ๋ˆ„๋ฝ, ์ž˜๋ชป๋œ ๊ธฐ์ž…

UNAUTHORIZED(401)

๋น„์ธ์ฆ ์š”์ฒญ

NOT_FOUND(404)

์š”์ฒญ๊ฐ’ ๋ˆ„๋ฝ, ์ž˜๋ชป๋œ ๊ธฐ์ž…, ๋น„์ธ๊ฐ€ ์ ‘์† ๋“ฑ

CONFLICT(409)

์š”์ฒญ๊ฐ’ ์ค‘๋ณต

INTERNAL_SERVER_ERROR(500)

์•Œ ์ˆ˜ ์—†๋Š” ์„œ๋ฒ„ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ด€๋ฆฌ์ž์—๊ฒŒ ๋ฌธ์˜ํ•˜์„ธ์š”.

-
-
-
-
-
- - - - - \ No newline at end of file diff --git a/src/main/resources/static/docs/notification.html b/src/main/resources/static/docs/notification.html deleted file mode 100644 index 4cd1aa89..00000000 --- a/src/main/resources/static/docs/notification.html +++ /dev/null @@ -1,520 +0,0 @@ - - - - - - - -์•Œ๋ฆผ(Notification) - - - - - -
-
-

์•Œ๋ฆผ(Notification)

-
-
-
-
์ฝ• ์ฐŒ๋ฅด๊ธฐ ์•Œ๋ฆผ, FCM Token ์ €์žฅ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
-
-
-
-

์ฝ• ์ฐŒ๋ฅด๊ธฐ ์•Œ๋ฆผ

-
-
-
1) ํŠน์ • ๋ฐฉ์˜ ์‚ฌ์šฉ์ž๊ฐ€ ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž๋ฅผ ์ฝ• ์ฐŒ๋ฆ…๋‹ˆ๋‹ค.
-2) ์„œ๋ฒ„์—์„œ ์ฝ• ์ฐŒ๋ฅผ ๋Œ€์ƒ์˜ FCM Token ์—ฌ๋ถ€๋ฅผ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค.
-3) Firebase ์„œ๋ฒ„์— FCM Push Messaing ์•Œ๋ฆผ์„ ๋น„๋™๊ธฐ๋กœ ์š”์ฒญํ•ฉ๋‹ˆ๋‹ค.
-4) Firebase ์„œ๋ฒ„์—์„œ FCM Token์œผ๋กœ ์‹๋ณ„๋œ ๊ธฐ๊ธฐ์— ์•Œ๋ฆผ์„ ๋ณด๋ƒ…๋‹ˆ๋‹ค.
-
-
-

์š”์ฒญ

-
-
-
GET /notifications/rooms/1/members/2 HTTP/1.1
-Host: localhost:8080
-
-
-

์‘๋‹ต

-
-
-
HTTP/1.1 200 OK
-Access-Control-Allow-Origin: http://localhost:8080
-Access-Control-Allow-Methods: GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH
-Access-Control-Allow-Headers: Origin, Accept, Access-Control-Request-Method, Access-Control-Request-Headers, X-Requested-With,Content-Type, Referer
-Access-Control-Allow-Credentials: true
-Access-Control-Max-Age: 3600
-
-
-
-
-

FCM TOKEN ์ €์žฅ

-
-
-
1) ํŠน์ • ์‚ฌ์šฉ์ž์˜ FCM-TOKEN์„ ๋ฐ›์•„์„œ REDIS DB์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
-
-
-

์š”์ฒญ

-
-
-
POST /notifications HTTP/1.1
-Content-Type: application/x-www-form-urlencoded
-Host: localhost:8080
-Content-Length: 18
-
-fcmToken=FCM-TOKEN
-
-
-

์‘๋‹ต

-
-
-
HTTP/1.1 200 OK
-Access-Control-Allow-Origin: http://localhost:8080
-Access-Control-Allow-Methods: GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH
-Access-Control-Allow-Headers: Origin, Accept, Access-Control-Request-Method, Access-Control-Request-Headers, X-Requested-With,Content-Type, Referer
-Access-Control-Allow-Credentials: true
-Access-Control-Max-Age: 3600
-
-
-
-
-
-
- - - \ No newline at end of file diff --git a/src/main/resources/static/docs/openapi3.json b/src/main/resources/static/docs/openapi3.json new file mode 100644 index 00000000..bf231c94 --- /dev/null +++ b/src/main/resources/static/docs/openapi3.json @@ -0,0 +1,327 @@ +{ + "openapi" : "3.0.1", + "info" : { + "title" : "MOABAM API DOCS", + "version" : "1.0.0" + }, + "servers" : [ { + "url" : "http://localhost:8080" + }, { + "url" : "http://www.moabam.com" + }, { + "url" : "http://dev.moabam.com" + } ], + "tags" : [ ], + "paths" : { + "/admins/coupons" : { + "post" : { + "tags" : [ "admins" ], + "operationId" : "admins/coupons", + "requestBody" : { + "content" : { + "application/json;charset=UTF-8" : { + "schema" : { + "$ref" : "#/components/schemas/admins-coupons399785149" + }, + "examples" : { + "admins/coupons" : { + "value" : "{\n \"name\" : \"couponName\",\n \"description\" : \"coupon description\",\n \"type\" : \"ํ™ฉ๊ธˆ\",\n \"point\" : 10,\n \"maxCount\" : 10,\n \"startAt\" : \"2023-02-01\",\n \"openAt\" : \"2023-01-01\"\n}" + } + } + } + } + }, + "responses" : { + "201" : { + "description" : "201" + } + } + } + }, + "/admins/coupons/36" : { + "delete" : { + "tags" : [ "admins" ], + "operationId" : "admins/coupons/couponId", + "responses" : { + "200" : { + "description" : "200" + } + } + } + }, + "/coupons" : { + "post" : { + "tags" : [ "coupons" ], + "operationId" : "coupons", + "requestBody" : { + "content" : { + "application/x-www-form-urlencoded" : { + "schema" : { + "$ref" : "#/components/schemas/my-coupons486549215" + }, + "examples" : { + "coupons" : { + "value" : "couponName=couponName" + } + } + } + } + }, + "responses" : { + "409" : { + "description" : "409", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/coupons475340455" + }, + "examples" : { + "coupons" : { + "value" : "{\n \"message\" : \"์ด๋ฏธ ์ฟ ํฐ ๋ฐœ๊ธ‰์— ์„ฑ๊ณตํ–ˆ์Šต๋‹ˆ๋‹ค!\"\n}" + } + } + } + } + } + } + } + }, + "/coupons/24" : { + "get" : { + "tags" : [ "coupons" ], + "operationId" : "coupons/couponId", + "responses" : { + "200" : { + "description" : "200", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/coupons-24975308875" + }, + "examples" : { + "coupons/couponId" : { + "value" : "{\n \"id\" : 24,\n \"adminId\" : 1,\n \"name\" : \"couponName\",\n \"description\" : \"\",\n \"point\" : 10,\n \"maxCount\" : 100,\n \"type\" : \"MORNING\",\n \"startAt\" : \"2023-02-01\",\n \"openAt\" : \"2023-01-01\"\n}" + } + } + } + } + } + } + } + }, + "/coupons/search" : { + "post" : { + "tags" : [ "coupons" ], + "operationId" : "coupons/search", + "requestBody" : { + "content" : { + "application/json;charset=UTF-8" : { + "schema" : { + "$ref" : "#/components/schemas/coupons-search1819561800" + }, + "examples" : { + "coupons/search" : { + "value" : "{\n \"opened\" : false,\n \"ended\" : false\n}" + } + } + } + } + }, + "responses" : { + "200" : { + "description" : "200", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/my-coupons486549215" + }, + "examples" : { + "coupons/search" : { + "value" : "[ {\n \"id\" : 25,\n \"adminId\" : 1,\n \"name\" : \"coupon1\",\n \"description\" : \"\",\n \"point\" : 10,\n \"maxCount\" : 100,\n \"type\" : \"MORNING\",\n \"startAt\" : \"2023-03-01\",\n \"openAt\" : \"2023-01-01\"\n} ]" + } + } + } + } + } + } + } + }, + "/my-coupons" : { + "get" : { + "tags" : [ "my-coupons" ], + "operationId" : "my-coupons/couponId", + "responses" : { + "200" : { + "description" : "200", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/my-coupons486549215" + }, + "examples" : { + "my-coupons/couponId" : { + "value" : "[ ]" + } + } + } + } + } + } + } + }, + "/my-coupons/8" : { + "post" : { + "tags" : [ "my-coupons" ], + "operationId" : "my-coupons/couponWalletId", + "responses" : { + "200" : { + "description" : "200" + } + } + } + }, + "/notifications" : { + "post" : { + "tags" : [ "notifications" ], + "operationId" : "notifications", + "requestBody" : { + "content" : { + "application/x-www-form-urlencoded" : { + "schema" : { + "$ref" : "#/components/schemas/my-coupons486549215" + }, + "examples" : { + "notifications" : { + "value" : "fcmToken=FCM-TOKEN" + } + } + } + } + }, + "responses" : { + "200" : { + "description" : "200" + } + } + } + }, + "/notifications/rooms/1/members/2" : { + "get" : { + "tags" : [ "notifications" ], + "operationId" : "notifications/rooms/roomId/members/memberId", + "responses" : { + "200" : { + "description" : "200" + } + } + } + } + }, + "components" : { + "schemas" : { + "coupons475340455" : { + "required" : [ "message" ], + "type" : "object", + "properties" : { + "message" : { + "type" : "string", + "description" : "์—๋Ÿฌ ๋ฉ”์‹œ์ง€" + } + } + }, + "admins-coupons399785149" : { + "required" : [ "description", "maxCount", "name", "openAt", "point", "startAt", "type" ], + "type" : "object", + "properties" : { + "name" : { + "type" : "string", + "description" : "์ฟ ํฐ๋ช…" + }, + "description" : { + "type" : "string", + "description" : "์ฟ ํฐ ๊ฐ„๋‹จ ์†Œ๊ฐœ (NULL ๊ฐ€๋Šฅ)" + }, + "type" : { + "type" : "string", + "description" : "์ฟ ํฐ ์ข…๋ฅ˜ (์•„์นจ, ์ €๋…, ํ™ฉ๊ธˆ, ํ• ์ธ)" + }, + "maxCount" : { + "type" : "number", + "description" : "์ฟ ํฐ์„ ๋ฐœ๊ธ‰ ์ตœ๋Œ€ ๊ฐฏ์ˆ˜" + }, + "openAt" : { + "type" : "string", + "description" : "์ฟ ํฐ ์ •๋ณด ์˜คํ”ˆ ๋‚ ์งœ (Ex: yyyy-MM-dd)" + }, + "startAt" : { + "type" : "string", + "description" : "์ฟ ํฐ ๋ฐœ๊ธ‰ ์‹œ์ž‘ ๋‚ ์งœ (Ex: yyyy-MM-dd)" + }, + "point" : { + "type" : "number", + "description" : "์ฟ ํฐ ์‚ฌ์šฉ ์‹œ, ์ œ๊ณตํ•˜๋Š” ํฌ์ธํŠธ๋Ÿ‰" + } + } + }, + "coupons-24975308875" : { + "required" : [ "adminId", "description", "id", "maxCount", "name", "openAt", "point", "startAt", "type" ], + "type" : "object", + "properties" : { + "name" : { + "type" : "string", + "description" : "์ฟ ํฐ๋ช…" + }, + "adminId" : { + "type" : "number", + "description" : "์ฟ ํฐ ๊ด€๋ฆฌ์ž ID" + }, + "description" : { + "type" : "string", + "description" : "์ฟ ํฐ์— ๋Œ€ํ•œ ๊ฐ„๋‹จ ์†Œ๊ฐœ (NULL ๊ฐ€๋Šฅ)" + }, + "id" : { + "type" : "number", + "description" : "์ฟ ํฐ ID" + }, + "type" : { + "type" : "string", + "description" : "์ฟ ํฐ ์ข…๋ฅ˜ (MORNING, NIGHT, GOLDEN, DISCOUNT)" + }, + "maxCount" : { + "type" : "number", + "description" : "์ฟ ํฐ์„ ๋ฐœ๊ธ‰ ์ตœ๋Œ€ ๊ฐฏ์ˆ˜" + }, + "openAt" : { + "type" : "string", + "description" : "์ฟ ํฐ ์ •๋ณด ์˜คํ”ˆ ๋‚ ์งœ (Ex: yyyy-MM-dd)" + }, + "startAt" : { + "type" : "string", + "description" : "์ฟ ํฐ ๋ฐœ๊ธ‰ ์‹œ์ž‘ ๋‚ ์งœ (Ex: yyyy-MM-dd)" + }, + "point" : { + "type" : "number", + "description" : "์ฟ ํฐ ์‚ฌ์šฉ ์‹œ, ์ œ๊ณตํ•˜๋Š” ํฌ์ธํŠธ๋Ÿ‰" + } + } + }, + "coupons-search1819561800" : { + "required" : [ "ended", "opened" ], + "type" : "object", + "properties" : { + "ended" : { + "type" : "boolean", + "description" : "์ข…๋ฃŒ๋œ ์ฟ ํฐ (true, false)" + }, + "opened" : { + "type" : "boolean", + "description" : "์ฟ ํฐ ์ •๋ณด๊ฐ€ ์˜คํ”ˆ๋œ ์ฟ ํฐ (true, false)" + } + } + }, + "my-coupons486549215" : { + "type" : "object" + } + } + } +} \ No newline at end of file diff --git a/src/test/java/com/moabam/api/application/coupon/CouponManageServiceTest.java b/src/test/java/com/moabam/api/application/coupon/CouponManageServiceTest.java index d209cdfe..881842b1 100644 --- a/src/test/java/com/moabam/api/application/coupon/CouponManageServiceTest.java +++ b/src/test/java/com/moabam/api/application/coupon/CouponManageServiceTest.java @@ -21,11 +21,9 @@ import com.moabam.api.domain.coupon.Coupon; import com.moabam.api.domain.coupon.CouponWallet; import com.moabam.api.domain.coupon.repository.CouponManageRepository; -import com.moabam.api.domain.coupon.repository.CouponRepository; import com.moabam.api.domain.coupon.repository.CouponWalletRepository; import com.moabam.global.common.util.ClockHolder; import com.moabam.global.error.exception.NotFoundException; -import com.moabam.global.error.model.ErrorMessage; import com.moabam.support.common.FilterProcessExtension; import com.moabam.support.fixture.CouponFixture; @@ -39,7 +37,7 @@ class CouponManageServiceTest { NotificationService notificationService; @Mock - CouponRepository couponRepository; + CouponCacheService couponCacheService; @Mock CouponManageRepository couponManageRepository; @@ -58,7 +56,7 @@ void issue_all_success(Set values) { Coupon coupon = CouponFixture.coupon(1000, 100); given(clockHolder.date()).willReturn(LocalDate.now()); - given(couponRepository.findByStartAt(any(LocalDate.class))).willReturn(Optional.of(coupon)); + given(couponCacheService.getByStartAt(any(LocalDate.class))).willReturn(Optional.of(coupon)); given(couponManageRepository.rangeQueue(any(String.class), any(long.class), any(long.class))) .willReturn(values); given(couponManageRepository.getCount(any(String.class))).willReturn(coupon.getMaxCount() - 1); @@ -77,7 +75,7 @@ void issue_all_success(Set values) { void issue_notStartAt() { // Given given(clockHolder.date()).willReturn(LocalDate.now()); - given(couponRepository.findByStartAt(any(LocalDate.class))).willReturn(Optional.empty()); + given(couponCacheService.getByStartAt(any(LocalDate.class))).willReturn(Optional.empty()); // When couponManageService.issue(); @@ -98,7 +96,7 @@ void issue_stockEnd() { Coupon coupon = CouponFixture.coupon(1000, 100); given(clockHolder.date()).willReturn(LocalDate.now()); - given(couponRepository.findByStartAt(any(LocalDate.class))).willReturn(Optional.of(coupon)); + given(couponCacheService.getByStartAt(any(LocalDate.class))).willReturn(Optional.of(coupon)); given(couponManageRepository.getCount(any(String.class))).willReturn(coupon.getMaxCount()); // When @@ -120,8 +118,7 @@ void registerQueue_success() { given(clockHolder.date()).willReturn(LocalDate.now()); given(couponManageRepository.sizeQueue(any(String.class))).willReturn(coupon.getMaxCount() - 1); - given(couponRepository.findByNameAndStartAt(any(String.class), any(LocalDate.class))) - .willReturn(Optional.of(coupon)); + given(couponCacheService.getByNameAndStartAt(any(String.class), any(LocalDate.class))).willReturn(coupon); // When couponManageService.registerQueue(coupon.getName(), 1L); @@ -135,13 +132,12 @@ void registerQueue_success() { void registerQueue_No_BadRequestException() { // Given given(clockHolder.date()).willReturn(LocalDate.now()); - given(couponRepository.findByNameAndStartAt(any(String.class), any(LocalDate.class))) - .willReturn(Optional.empty()); + given(couponCacheService.getByNameAndStartAt(any(String.class), any(LocalDate.class))) + .willThrow(NotFoundException.class); // When & Then assertThatThrownBy(() -> couponManageService.registerQueue("couponName", 1L)) - .isInstanceOf(NotFoundException.class) - .hasMessage(ErrorMessage.INVALID_COUPON_PERIOD.getMessage()); + .isInstanceOf(NotFoundException.class); } @DisplayName("์ฟ ํฐ ๋Œ€๊ธฐ์—ด๊ณผ ๋ฐœํ–‰๋œ ์žฌ๊ณ ๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ์‚ญ์ œ๋œ๋‹ค.") diff --git a/src/test/java/com/moabam/api/application/member/MemberServiceTest.java b/src/test/java/com/moabam/api/application/member/MemberServiceTest.java index 5bb68977..dc28bb72 100644 --- a/src/test/java/com/moabam/api/application/member/MemberServiceTest.java +++ b/src/test/java/com/moabam/api/application/member/MemberServiceTest.java @@ -129,7 +129,7 @@ void signUp_success() { void undo_delete_member(@WithMember AuthMember authMember) { // given Member member = MemberFixture.member(); - given(clockHolder.times()).willReturn(LocalDateTime.now()); + given(clockHolder.dateTime()).willReturn(LocalDateTime.now()); // When memberService.delete(member); diff --git a/src/test/java/com/moabam/api/application/notification/NotificationServiceTest.java b/src/test/java/com/moabam/api/application/notification/NotificationServiceTest.java index a08d2b06..2cb980ee 100644 --- a/src/test/java/com/moabam/api/application/notification/NotificationServiceTest.java +++ b/src/test/java/com/moabam/api/application/notification/NotificationServiceTest.java @@ -169,7 +169,7 @@ void sendCertificationTime_success(List participants) { // Given given(participantSearchRepository.findAllByRoomCertifyTime(any(Integer.class))).willReturn(participants); given(fcmService.findTokenByMemberId(any(Long.class))).willReturn(Optional.of("FCM-TOKEN")); - given(clockHolder.times()).willReturn(LocalDateTime.now()); + given(clockHolder.dateTime()).willReturn(LocalDateTime.now()); // When notificationService.sendCertificationTime(); @@ -186,7 +186,7 @@ void sendCertificationTime_NoFirebaseMessaging(List participants) { // Given given(participantSearchRepository.findAllByRoomCertifyTime(any(Integer.class))).willReturn(participants); given(fcmService.findTokenByMemberId(any(Long.class))).willReturn(Optional.empty()); - given(clockHolder.times()).willReturn(LocalDateTime.now()); + given(clockHolder.dateTime()).willReturn(LocalDateTime.now()); // When notificationService.sendCertificationTime(); diff --git a/src/test/java/com/moabam/api/application/room/CertificationServiceTest.java b/src/test/java/com/moabam/api/application/room/CertificationServiceTest.java index e02eac44..ad8b6579 100644 --- a/src/test/java/com/moabam/api/application/room/CertificationServiceTest.java +++ b/src/test/java/com/moabam/api/application/room/CertificationServiceTest.java @@ -131,7 +131,7 @@ void get_certified_member_info_success() { imageUrls.add("https://image.moabam.com/certifications/20231108/1_asdfsdfxcv-4815vcx-asfd"); imageUrls.add("https://image.moabam.com/certifications/20231108/2_asdfsdfxcv-4815vcx-asfd"); - given(clockHolder.times()).willReturn(LocalDateTime.now().withHour(10).withMinute(6)); + given(clockHolder.dateTime()).willReturn(LocalDateTime.now().withHour(10).withMinute(6)); given(clockHolder.date()).willReturn(today); given(participantSearchRepository.findOne(memberId, roomId)).willReturn(Optional.of(participant)); given(memberService.findMember(memberId)).willReturn(member1); diff --git a/src/test/java/com/moabam/api/application/room/SearchServiceTest.java b/src/test/java/com/moabam/api/application/room/SearchServiceTest.java index 6562caa2..7440bac6 100644 --- a/src/test/java/com/moabam/api/application/room/SearchServiceTest.java +++ b/src/test/java/com/moabam/api/application/room/SearchServiceTest.java @@ -404,7 +404,7 @@ void search_room_by_title_manager_nickname_routine_success() { routine16, routine17, routine18, routine19, routine20, routine21, routine22, routine23, routine24, routine25, routine26, routine27, routine28); - given(roomRepository.searchByKeyword("๋ฒˆ์งธ")).willReturn(rooms); + given(roomSearchRepository.searchWithKeyword("๋ฒˆ์งธ", null, null)).willReturn(rooms); given(routineRepository.findAllByRoomIdIn(anyList())).willReturn(routines); // when diff --git a/src/test/java/com/moabam/api/presentation/CouponControllerTest.java b/src/test/java/com/moabam/api/presentation/CouponControllerTest.java index 65f25723..f3e8d891 100644 --- a/src/test/java/com/moabam/api/presentation/CouponControllerTest.java +++ b/src/test/java/com/moabam/api/presentation/CouponControllerTest.java @@ -1,10 +1,10 @@ package com.moabam.api.presentation; +import static com.epages.restdocs.apispec.MockMvcRestDocumentationWrapper.*; import static org.hamcrest.Matchers.*; import static org.mockito.BDDMockito.*; -import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.*; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.*; import static org.springframework.restdocs.operation.preprocess.Preprocessors.*; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @@ -20,6 +20,7 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.boot.test.mock.mockito.SpyBean; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; import org.springframework.transaction.annotation.Transactional; @@ -68,7 +69,7 @@ class CouponControllerTest extends WithoutFilterSupporter { @Autowired CouponWalletRepository couponWalletRepository; - @MockBean + @SpyBean ClockHolder clockHolder; @MockBean diff --git a/src/test/java/com/moabam/api/presentation/MemberAuthorizeControllerTest.java b/src/test/java/com/moabam/api/presentation/MemberAuthorizeControllerTest.java index be66cc08..3e3083ec 100644 --- a/src/test/java/com/moabam/api/presentation/MemberAuthorizeControllerTest.java +++ b/src/test/java/com/moabam/api/presentation/MemberAuthorizeControllerTest.java @@ -213,7 +213,8 @@ void token_info_response_fail(int code) throws Exception { .andRespond(withStatus(HttpStatusCode.valueOf(code))); mockMvc.perform(post("/members/login/kakao/oauth") - .flashAttr("authorizationCodeResponse", authorizationCodeResponse)) + .content(objectMapper.writeValueAsString(authorizationCodeResponse)) + .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isBadRequest()); } } diff --git a/src/test/java/com/moabam/api/presentation/NotificationControllerTest.java b/src/test/java/com/moabam/api/presentation/NotificationControllerTest.java index b623bcb9..e7457db1 100644 --- a/src/test/java/com/moabam/api/presentation/NotificationControllerTest.java +++ b/src/test/java/com/moabam/api/presentation/NotificationControllerTest.java @@ -1,7 +1,7 @@ package com.moabam.api.presentation; +import static com.epages.restdocs.apispec.MockMvcRestDocumentationWrapper.*; import static org.mockito.BDDMockito.*; -import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.*; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.*; import static org.springframework.restdocs.operation.preprocess.Preprocessors.*; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*; diff --git a/src/test/java/com/moabam/api/presentation/RoomControllerTest.java b/src/test/java/com/moabam/api/presentation/RoomControllerTest.java index f3de23a6..c7cbc332 100644 --- a/src/test/java/com/moabam/api/presentation/RoomControllerTest.java +++ b/src/test/java/com/moabam/api/presentation/RoomControllerTest.java @@ -602,7 +602,7 @@ void enter_room_wrong_password_fail() throws Exception { Member member = Member.builder() .id(1L) - .socialId("1") + .socialId("321313") .bug(BugFixture.bug()) .build(); @@ -1329,7 +1329,7 @@ void search_last_page_all_morning_rooms_success() throws Exception { @Test void search_first_page_all_rooms_by_keyword_success() throws Exception { // given - Room room1 = RoomFixture.room("์•„์นจ - ์ฒซ ๋ฒˆ์งธ ๋ฐฉ", RoomType.MORNING, 10, "1234"); + Room room1 = RoomFixture.room("ํ…Œ์ŠคํŠธํ•˜๋Š” ๋ฐฉ์ž…๋‹ˆ๋‹ค", RoomType.MORNING, 10, "1234"); Room room2 = RoomFixture.room("์•„์นจ - ๋‘ ๋ฒˆ์งธ ๋ฐฉ", RoomType.MORNING, 9); Room room3 = RoomFixture.room("๋ฐค - ์„ธ ๋ฒˆ์งธ ๋ฐฉ", RoomType.NIGHT, 22); Room room4 = RoomFixture.room("์•„์นจ - ๋„ค ๋ฒˆ์งธ ๋ฐฉ", RoomType.MORNING, 7); @@ -1344,6 +1344,8 @@ void search_first_page_all_rooms_by_keyword_success() throws Exception { Room room13 = RoomFixture.room("๋ฐค - ์—ด์…‹ ๋ฒˆ์งธ ๋ฐฉ", RoomType.NIGHT, 2); Room room14 = RoomFixture.room("๋ฐค - ์—ด๋„ท ๋ฒˆ์งธ ๋ฐฉ", RoomType.NIGHT, 21); + room1.changeManagerNickname("์•„์นจ"); + Routine routine1 = RoomFixture.routine(room1, "๋ฐฉ1์˜ ๋ฃจํ‹ด1"); Routine routine2 = RoomFixture.routine(room1, "๋ฐฉ1์˜ ๋ฃจํ‹ด2"); @@ -1396,17 +1398,17 @@ void search_first_page_all_rooms_by_keyword_success() throws Exception { routine20, routine21, routine22, routine23, routine24, routine25, routine26, routine27, routine28)); // expected - mockMvc.perform(get("/rooms/search?keyword=์•„์นจ")) + mockMvc.perform(get("/rooms/search?keyword=ํ…Œ์ŠคํŠธ")) .andExpect(status().isOk()) .andDo(print()); - mockMvc.perform(get("/rooms/search?keyword=๋ฐฉ12")) - .andExpect(status().isOk()) - .andDo(print()); - - mockMvc.perform(get("/rooms/search?keyword=๋ฐฉ")) - .andExpect(status().isOk()) - .andDo(print()); + // mockMvc.perform(get("/rooms/search?keyword=๋ฐฉ12")) + // .andExpect(status().isOk()) + // .andDo(print()); + // + // mockMvc.perform(get("/rooms/search?keyword=๋ฐฉ")) + // .andExpect(status().isOk()) + // .andDo(print()); } @DisplayName("๋ฐฉ ๊ฒ€์ƒ‰ ์กฐํšŒ ์„ฑ๊ณต - ํ‚ค์›Œ๋“œ + ๋ฐฉ ํƒ€์ž… ์กด์žฌ") diff --git a/src/test/java/com/moabam/support/common/TestClockHolder.java b/src/test/java/com/moabam/support/common/TestClockHolder.java index aa75977d..3b21a0cd 100644 --- a/src/test/java/com/moabam/support/common/TestClockHolder.java +++ b/src/test/java/com/moabam/support/common/TestClockHolder.java @@ -2,6 +2,7 @@ import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.LocalTime; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Component; @@ -13,7 +14,7 @@ public class TestClockHolder implements ClockHolder { @Override - public LocalDateTime times() { + public LocalDateTime dateTime() { return LocalDateTime.of(2023, 12, 3, 14, 30, 0); } @@ -21,4 +22,14 @@ public LocalDateTime times() { public LocalDate date() { return LocalDateTime.now().toLocalDate(); } + + @Override + public LocalTime time() { + return LocalTime.of(23, 50, 50); + } + + @Override + public LocalTime endOfDay() { + return LocalTime.of(23, 59, 59, 999_999_999); + } } diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index 7083d3d7..3e48ed0d 100644 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -1,93 +1,94 @@ logging: - level: - org.hibernate.SQL: debug - org.springframework: DEBUG + level: + org.hibernate.SQL: debug + org.springframework: DEBUG + org.hibernate.orm.jdbc.bind: trace spring: - # Profile - profiles: - active: test + # Profile + profiles: + active: test - datasource: - driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://localhost:3305/moabam?serverTimezone=UTC&characterEncoding=UTF-8 - username: root - password: 1234 - - jpa: - hibernate: - ddl-auto: create - properties: - hibernate: - format_sql: true - highlight_sql: true - database: mysql + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:3305/moabam?serverTimezone=UTC&characterEncoding=UTF-8 + username: root + password: 1234 - # Redis - data: - redis: - host: 127.0.0.1 - port: 6379 + jpa: + hibernate: + ddl-auto: create + properties: + hibernate: + format_sql: true + highlight_sql: true + database: mysql - # AWS - cloud: - aws: - region: - static: ap-test-test - s3: - bucket: test - url: test - cloud-front: - url: test - credentials: - access-key: test - secret-key: test - max-request-size: 10MB # ์š”์ฒญ ๋‹น ์ตœ๋Œ€ ์‚ฌ์ด์ฆˆ + # Redis + data: + redis: + host: 127.0.0.1 + port: 6379 + + # AWS + cloud: + aws: + region: + static: ap-test-test + s3: + bucket: test + url: test + cloud-front: + url: test + credentials: + access-key: test + secret-key: test + max-request-size: 10MB # ์š”์ฒญ ๋‹น ์ตœ๋Œ€ ์‚ฌ์ด์ฆˆ oauth2: - client: - provider: test - client-id: testtestetsttest - client-secret: testtestetsttest - authorization-grant-type: authorization_code - admin-key: testtesttesttesttesttesttest - scope: - - profile_nickname - - profile_image + client: + provider: test + client-id: testtestetsttest + client-secret: testtestetsttest + authorization-grant-type: authorization_code + admin-key: testtesttesttesttesttesttest + scope: + - profile_nickname + - profile_image - provider: - authorization_uri: https://authorization.com/test/test - redirect_uri: http://redirect:8080/test - token-uri: https://kauth.kakao.com/oauth/token - token-info: https://kapi.kakao.com/v1/user/access_token_info - unlink: https://kapi.kakao.com/v1/user/unlink - admin-redirect-uri: https://dev-admin.moabam.com/login/kakao/oauth + provider: + authorization_uri: https://authorization.com/test/test + redirect_uri: http://redirect:8080/test + token-uri: https://kauth.kakao.com/oauth/token + token-info: https://kapi.kakao.com/v1/user/access_token_info + unlink: https://kapi.kakao.com/v1/user/unlink + admin-redirect-uri: https://dev-admin.moabam.com/login/kakao/oauth token: - iss: "PARK" - access-expire: 100000 - refresh-expire: 150000 - secret-key: testestestestestestestestestesttestestestestestestestestestest - admin-secret: testestestestestestestestestesttestestestestestestestestestest + iss: "PARK" + access-expire: 100000 + refresh-expire: 150000 + secret-key: testestestestestestestestestesttestestestestestestestestestest + admin-secret: testestestestestestestestestesttestestestestestestestestestest allows: - admin-domain: "localhost" - domain: "localhost" - origin: - - "https://test.com" - - "https://test.com" + admin-domain: "localhost" + domain: "localhost" + origin: + - "https://test.com" + - "https://test.com" admin: moamoamoabam # Payment payment: - toss: - base-url: "https://api.tosspayments.com" - secret-key: "test_sk_4yKeq5bgrpWk4XYdDoBxVGX0lzW6:" + toss: + base-url: "https://api.tosspayments.com" + secret-key: "test_sk_4yKeq5bgrpWk4XYdDoBxVGX0lzW6:" # Webhook webhook: - slack: - url: test + slack: + url: test