diff --git a/.github/ISSUE_TEMPLATE/report_bug.md b/.github/ISSUE_TEMPLATE/report_bug.md
new file mode 100644
index 0000000..2675453
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/report_bug.md
@@ -0,0 +1,50 @@
+---
+name: Report bug
+about: 오류가 발생한 영역에 대해 보고합니다 [Dev]
+title: "[BUG] "
+labels: bug
+assignees: ''
+
+---
+
+## 설명
+
+짧고 명확하게 오류에 대해 설명합니다
+
+
+## 오류 발생 재현
+
+이렇게 했더니 다음과 같은 오류가 발생했습니다
+주의. log는 절대!!! 복붙하지 않고 인용, gist등을 이용합니다
+
+1. Go to '...'
+2. Click on '....'
+3. Scroll down to '....'
+4. See error
+
+
+## 기대 결과
+
+이런 결과가 나왔으면 좋겠습니다
+
+
+## 첨부 자료
+
+해당 템플릿에 자료(사진, 동영상 등) 첨부합니다
+
+
+## 추가 정보
+
+추가로 하고 싶은 말이나 위에서 설명하지 못했던 것 등을 간략히 작성합니다
+긴 논의는 GitHub Discussion에서 진행해주세요
+
+
+## 나의 환경
+
+외부적 환경으로 인해 문제가 발생한 것 같다면 기재해줍니다
+
+**Desktop**
+
+- OS: windows 11
+- IDE: Intellij
+- Browser: Chrome
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 0000000..703317f
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,19 @@
+## 변경사항
+
+변경사항에 대해 간략하게 설명해주세요.
+SemVer 규칙을 따라야 합니다
+
+### Issue Link - #1
+
+
+## 체크리스트
+
+리뷰 요청 전에 확인해야 할 사항들을 나열해주세요.
+
+- [ ] 내 코드를 스스로 검토했나요?
+- [ ] 핵심 기능에 대해 충분한 테스트를 수행했나요?
+- [ ] 분석이 필요한가요?
+- [ ] 이것이 제품 업데이트의 일부인가요? 그렇다면, 이 업데이트에 대해 한 문장으로 작성해주세요.
+
+
+## 참고
diff --git a/.github/workflows/FUNDING.yml b/.github/workflows/FUNDING.yml
new file mode 100644
index 0000000..9e12df3
--- /dev/null
+++ b/.github/workflows/FUNDING.yml
@@ -0,0 +1,2 @@
+github: laigasus
+custom: https://toss.me/laigasus
\ No newline at end of file
diff --git a/.github/workflows/auto-release.yml b/.github/workflows/auto-release.yml
new file mode 100644
index 0000000..8ccf6e8
--- /dev/null
+++ b/.github/workflows/auto-release.yml
@@ -0,0 +1,29 @@
+name: github action tag release
+
+on:
+ push:
+ branches:
+ - main
+
+jobs:
+ setup-build-deploy:
+ name: Setup, Build, and Deploy
+ runs-on: ubuntu-latest
+ permissions:
+ packages: write
+ contents: write
+ id-token: write
+
+ steps:
+ - name: Bump version and push tag
+ id: tag_version
+ uses: mathieudutour/github-tag-action@v6.1
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Create a GitHub release
+ uses: ncipollo/release-action@v1
+ with:
+ tag: ${{ steps.tag_version.outputs.new_tag }}
+ name: Release ${{ steps.tag_version.outputs.new_tag }}
+ body: ${{ steps.tag_version.outputs.changelog }}
\ No newline at end of file
diff --git a/.github/workflows/ci-test.yml b/.github/workflows/ci-test.yml
new file mode 100644
index 0000000..5c65734
--- /dev/null
+++ b/.github/workflows/ci-test.yml
@@ -0,0 +1,50 @@
+name: CI - test
+
+on:
+ push:
+ branches: [ "main", "develop" ]
+ pull_request:
+ branches: [ "main", "develop" ]
+
+permissions:
+ contents: read
+ checks: write
+ pull-requests: write
+
+jobs:
+ build:
+ name: Build and test project
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout the code
+ uses: actions/checkout@v3
+ with:
+ token: ${{ secrets.ACTION_TOKEN }}
+ submodules: true
+
+ - name: Set up JDK 21
+ uses: actions/setup-java@v3
+ with:
+ java-version: '21'
+ distribution: 'corretto'
+
+ - name: Grant execute permission for gradlew
+ run: |
+ chmod +x ./gradlew
+
+ - name: Test with Gradle
+ run: |
+ ./gradlew test
+
+ - name: Publish result of unit test
+ uses: EnricoMi/publish-unit-test-result-action@v2
+ if: always()
+ with:
+ files: "**/build/test-results/test/TEST-*.xml"
+
+ - name: Publish failure of unit test
+ uses: mikepenz/action-junit-report@v3
+ if: always()
+ with:
+ report_paths: '**/build/test-results/test/TEST-*.xml'
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..4ff88f9
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,38 @@
+HELP.md
+.gradle
+build/
+!gradle/wrapper/gradle-wrapper.jar
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+bin/
+!**/src/main/**/bin/
+!**/src/test/**/bin/
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+out/
+!**/src/main/**/out/
+!**/src/test/**/out/
+.env
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+
+### VS Code ###
+.vscode/
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..3dc8a52
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,27 @@
+FROM gradle:8.2.1-jdk17-alpine AS builder
+WORKDIR /build
+
+# 그래들 파일이 변경되었을 때만 새롭게 의존패키지 다운로드 받게함.
+COPY build.gradle settings.gradle /build/
+RUN gradle build -x test --parallel --continue > /dev/null 2>&1 || true
+
+ARG EnvironmentVariable
+ARG RAILWAY_ENVIRONMENT
+ENV RAILWAY_ENVIRONMENT=$RAILWAY_ENVIRONMENT
+
+# 빌더 이미지에서 애플리케이션 빌드
+COPY . /build
+RUN gradle build -x test --parallel
+
+# APP
+FROM openjdk:21-slim
+WORKDIR /app
+
+# 빌더 이미지에서 jar 파일만 복사
+COPY --from=builder /build/build/libs/blisgo.jar .
+
+EXPOSE 8080
+
+# root 권한으로 실행
+USER root
+ENTRYPOINT ["java","-jar","application.jar"]
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..f20050c
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,9 @@
+bootJar.mainClass = 'blisgo.app.BlisgoApplication'
+bootJar.enabled = true
+
+dependencies {
+ implementation project(':usecase')
+ implementation project(':infrastructure:internal')
+ implementation project(':infrastructure:external')
+ implementation project(':domain')
+}
\ No newline at end of file
diff --git a/app/src/main/java/blisgo/app/BlisgoApplication.java b/app/src/main/java/blisgo/app/BlisgoApplication.java
new file mode 100644
index 0000000..391a893
--- /dev/null
+++ b/app/src/main/java/blisgo/app/BlisgoApplication.java
@@ -0,0 +1,23 @@
+package blisgo.app;
+
+import blisgo.domain.DomainRoot;
+import blisgo.infrastructure.external.ExternalRoot;
+import blisgo.infrastructure.internal.InternalRoot;
+import blisgo.usecase.UseCaseRoot;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+import java.util.TimeZone;
+
+@SpringBootApplication(scanBasePackageClasses = {
+ InternalRoot.class,
+ DomainRoot.class,
+ ExternalRoot.class,
+ UseCaseRoot.class
+})
+public class BlisgoApplication {
+ public static void main(String[] args) {
+ TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
+ SpringApplication.run(BlisgoApplication.class, args);
+ }
+}
diff --git a/app/src/main/resources/application-dev.yml b/app/src/main/resources/application-dev.yml
new file mode 100644
index 0000000..840f51a
--- /dev/null
+++ b/app/src/main/resources/application-dev.yml
@@ -0,0 +1,48 @@
+spring:
+ config:
+ activate:
+ on-profile: dev
+
+ devtools:
+ livereload:
+ enabled: true
+
+ datasource:
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ url: "jdbc:mysql://localhost:3306/blisgo"
+ username: root
+ password: root
+
+ jpa:
+ show-sql: true
+ hibernate:
+ ddl-auto: create
+ properties:
+ hibernate:
+ format_sql: true
+ highlight_sql: true
+ use_sql_comments: true
+ ejb:
+ naming_strategy: org.hibernate.cfg.ImprovedNamingStrategy
+ defer-datasource-initialization: true
+
+ sql:
+ init:
+ mode: always
+
+ thymeleaf:
+ cache: false
+
+ data:
+ redis:
+ host: localhost
+ port: 6379
+
+ flyway:
+ enabled: false
+
+server:
+ servlet:
+ session:
+ cookie:
+ domain: localhost
\ No newline at end of file
diff --git a/app/src/main/resources/application-prod.yml b/app/src/main/resources/application-prod.yml
new file mode 100644
index 0000000..cb75928
--- /dev/null
+++ b/app/src/main/resources/application-prod.yml
@@ -0,0 +1,36 @@
+spring:
+ data:
+ redis:
+ host: ${REDISHOST}
+ username: ${REDISUSER}
+ password: ${REDISPASSWORD}
+ port: ${REDISPORT}
+
+ datasource:
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ url: "jdbc:${MYSQL_URL}"
+ username: ${MYSQLUSER}
+ password: ${MYSQLPASSWORD}
+
+ thymeleaf:
+ cache: true
+
+ jpa:
+ show-sql: true
+ hibernate:
+ ddl-auto: validate
+
+ flyway:
+ enabled: true
+ baseline-on-migrate: on
+ locations: classpath:db/migration
+ url: "jdbc:${MYSQL_URL}"
+ user: ${MYSQLUSER}
+ password: ${MYSQLPASSWORD}
+ driver-class-name: com.mysql.cj.jdbc.Driver
+
+server:
+ servlet:
+ session:
+ cookie:
+ domain: blisgo.up.railway.app
\ No newline at end of file
diff --git a/app/src/main/resources/application.yml b/app/src/main/resources/application.yml
new file mode 100644
index 0000000..9468e7a
--- /dev/null
+++ b/app/src/main/resources/application.yml
@@ -0,0 +1,5 @@
+spring:
+ profiles:
+ include:
+ common, thymeleaf, oauth, redis
+ active: ${APP_PROFILE}
diff --git a/app/src/main/resources/banner.txt b/app/src/main/resources/banner.txt
new file mode 100644
index 0000000..c97469c
--- /dev/null
+++ b/app/src/main/resources/banner.txt
@@ -0,0 +1,8 @@
+ ____ _ ___ ____ ____ ___
+| __ )| | |_ _/ ___| / ___|/ _ \
+| _ \| | | |\___ \| | _| | | |
+| |_) | |___ | | ___) | |_| | |_| |
+|____/|_____|___|____/ \____|\___/
+
+Until the Earth becomes HEALTHY!!!
+Powered by Spring Boot ${spring-boot.version}
\ No newline at end of file
diff --git a/app/src/main/resources/data.sql b/app/src/main/resources/data.sql
new file mode 100644
index 0000000..4c4c222
--- /dev/null
+++ b/app/src/main/resources/data.sql
@@ -0,0 +1,948 @@
+INSERT INTO reply (post_id, content)
+VALUES (1, 'This is a test comment 1.'),
+ (1, 'This is a test comment 2.'),
+ (1, 'This is a test comment 3.'),
+ (1, 'This is a test comment 4.'),
+ (1, 'This is a test comment 5.'),
+ (1, 'This is a test comment 6.'),
+ (1, 'This is a test comment 7.'),
+ (1, 'This is a test comment 8.'),
+ (1, 'This is a test comment 9.'),
+ (1, 'This is a test comment 10.'),
+ (1, 'This is a test comment 11.'),
+ (1, 'This is a test comment 12.'),
+ (1, 'This is a test comment 13.'),
+ (1, 'This is a test comment 14.'),
+ (1, 'This is a test comment 15.'),
+ (1, 'This is a test comment 16.'),
+ (1, 'This is a test comment 17.'),
+ (1, 'This is a test comment 18.'),
+ (1, 'This is a test comment 19.'),
+ (1, 'This is a test comment 20.'),
+ (1, 'This is a test comment 21.'),
+ (1, 'This is a test comment 22.'),
+ (1, 'This is a test comment 23.'),
+ (1, 'This is a test comment 24.'),
+ (1, 'This is a test comment 25.'),
+ (1, 'This is a test comment 26.'),
+ (1, 'This is a test comment 27.'),
+ (1, 'This is a test comment 28.'),
+ (1, 'This is a test comment 29.'),
+ (1, 'This is a test comment 30.'),
+ (1, 'This is a test comment 31.'),
+ (1, 'This is a test comment 32.'),
+ (1, 'This is a test comment 33.'),
+ (1, 'This is a test comment 34.'),
+ (1, 'This is a test comment 35.'),
+ (1, 'This is a test comment 36.'),
+ (1, 'This is a test comment 37.'),
+ (1, 'This is a test comment 38.'),
+ (1, 'This is a test comment 39.'),
+ (1, 'This is a test comment 40.');
+
+
+INSERT INTO post (title, content, author_email, author_name, author_picture)
+VALUES ('Post 1', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 2', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 3', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 4', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 5', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 6', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 7', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 8', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 9', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 10', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 11', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 12', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 13', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 14', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 15', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 16', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 17', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 18', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 19', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 20', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 21', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 22', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 23', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 24', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 25', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 26', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 27', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 28', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 29', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 30', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 31', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 32', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 33', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 34', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 35', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 36', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 37', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 38', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 39', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 40', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 41', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 42', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 43', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 44', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 45', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 46', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 47', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 48', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 49', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 50', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 51', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 52', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 53', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 54', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 55', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 56', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 57', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 58', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 59', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 60', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 61', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 62', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 63', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 64', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 65', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 66', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 67', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 68', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 69', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 70', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 71', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 72', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 73', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 74', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 75', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 76', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 77', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 78', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 79', null, 'a@gmail.com', 'A', 'https://a.jpg'),
+ ('Post 80', null, 'a@gmail.com', 'A', 'https://a.jpg');
+
+INSERT INTO guide (category, content, picture)
+VALUES ('IRON', '고철 로 배출', 'https://ik.imagekit.io/blisgo/guide/rhcjffb.webp'),
+ ('METAL', '금속캔 으로 배출', 'https://ik.imagekit.io/blisgo/guide/rmathrzos.webp'),
+ ('BULKY', '대형폐기물 로 배출', 'https://ik.imagekit.io/blisgo/guide/eogudvPrlanf.webp'),
+ ('STYROFOAM', '스티로폼 으로 배출', 'https://ik.imagekit.io/blisgo/guide/qkfvhgkqtjdtnwl.webp'),
+ ('NON_FLAMMABLE', '불연성폐기물 로 배출', 'https://ik.imagekit.io/blisgo/guide/qnfdustjd-whdfidwp.webp'),
+ ('VINYL', '비닐 로 배출', 'https://ik.imagekit.io/blisgo/guide/qlslffb.webp'),
+ ('GLASS', '유리병류 로 배출', 'https://ik.imagekit.io/blisgo/guide/dbflqud.webp'),
+ ('PROG', '음식물류 폐기물 로 배출', 'https://ik.imagekit.io/blisgo/guide/dmatlranf.webp'),
+ ('CLOTHES', '의류 및 원단류 로 배출', 'https://ik.imagekit.io/blisgo/guide/dmlfb.webp'),
+ ('ONLY_BOX', '전용수거함 으로 배출', 'https://ik.imagekit.io/blisgo/guide/wjsdydgka.webp'),
+ ('PAPER', '종이류 로 배출', 'https://ik.imagekit.io/blisgo/guide/whddl.webp'),
+ ('CARTON', '종이팩 으로 배출', 'https://ik.imagekit.io/blisgo/guide/whddlvor-whddlzjq.webp'),
+ ('HOME_APPLIANCES', '폐가전제품 으로 배출', 'https://ik.imagekit.io/blisgo/guide/vPrkwjswpvna.webp'),
+ ('PLASTIC', '플라스틱 으로 배출', 'https://ik.imagekit.io/blisgo/guide/vmffktmxlr.webp'),
+ ('PAY_AS_YOU_GO_BAG', '종량제봉투 로 배출', NULL),
+ ('SEPARATION_BY_MATERIAL', '재질에 맞게 배출', NULL),
+ ('CAUTION', '위험! 폐기시 주의가 필요함', NULL),
+ ('PRO_FACILITY', '전문처리시설 로 배출', NULL);
+
+
+
+INSERT INTO waste (waste_id, name, type, picture, treatment)
+VALUES (1001, '가격표', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1001.webp', NULL),
+ (1002, '가구류', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1002.webp', NULL),
+ (1003, '가발', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1003.webp', NULL),
+ (1004, '가습기', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1004.webp', NULL),
+ (1005, '가위', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1005.webp',
+ '
다른 재질이 많이 섞인 제품은 분리해서 배출하며, 분리가 어렵다면 종량제봉투 로 배출
'),
+ (1006, '개수대', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1006.webp', NULL),
+ (1007, '거울', '생활폐기물/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1007.webp',
+ '크기에 따라 해당 폐기물로 배출
'),
+ (1008, '걸레', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1008.webp', NULL),
+ (1009, '계란껍질', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1009.webp', NULL),
+ (1010, '고무장갑', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1010.webp', NULL),
+ (1011, '골판지', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1011.webp',
+ '종이 < 일반 골판지플라스틱 < PP 골판지
'),
+ (1012, '골프 클럽 백', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1012.webp', NULL),
+ (1013, '골프공', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1013.webp', NULL),
+ (1014, '공구류(철)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1014.webp',
+ '다른 재질이 많이 섞인 제품은 분리해서 배출하며, 분리가 어렵다면 종량제봉투 로 배출
'),
+ (1015, '공기청정기', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1015.webp', NULL),
+ (1016, '광고전단지', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1016.webp',
+ '비닐코팅된 종이는 종량제봉투 배출
'),
+ (1017, '구두, 샌들, 슬리퍼', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1017.webp',
+ '의류 및 원단류 배출 방법을 참고하여 배출하거나 종량제봉투 로 배출
'),
+ (1018, '국자', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1018.webp',
+ '고철 < 금속, 비금속 국자플라스틱 < 플라스틱 국자종량제봉투 < 나무 국자
'),
+ (1019, '그릇', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1019.webp',
+ '불연성-종량제 < 도자기·유리그릇고철 < 금속, 비금속그릇플라스틱 < 플라스틱 그릇
'),
+ (1020, '기름(기계)', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1020.webp', NULL),
+ (1021, '기름(식용)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1021.webp',
+ '전용수거함 으로 배출
'),
+ (1022, '기타(악기)', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1022.webp', NULL),
+ (1023, '깨진유리', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1023.webp',
+ '불연성폐기물 배출방법을 참조하여 배출
'),
+ (1024, '나무젓가락', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1024.webp', NULL),
+ (1025, '나무조각, 나뭇가지, 나무줄기', '생활폐기물/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1025.webp',
+ '종량제봉투 에 담을 수 없다면 대형폐기물 로 처리
'),
+ (1026, '나사(못)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1026.webp', NULL),
+ (1027, '나침반', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1027.webp', NULL),
+ (1028, '낙엽', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1028.webp', NULL),
+ (1029, '낚싯대', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1029.webp', NULL),
+ (1030, '난로(전기난로)', '폐가전제품/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1030.webp',
+ '대형폐기물 또는 가전제품 으로 배출
'),
+ (1031, '낫', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1031.webp',
+ '고철 로 배출하되, 가능하다면 손잡이 부분(나무재질 등)을 분리하여 배출
'),
+ (1032, '내열식기류', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1032.webp', NULL),
+ (1033, '냄비뚜껑(강화유리)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1033.webp', NULL),
+ (1034, '냉장고(냉동고)', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1034.webp', NULL),
+ (1035, '노트북', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1035.webp', NULL),
+ (1036, '농약용기', '기타폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1036.webp',
+ '내용물을 다 사용한 후 따로 봉투에 담아 배출
'),
+ (1037, '다리미', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1037.webp', NULL),
+ (1038, '도끼', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1038.webp',
+ '고철 로 배출하되, 가능하다면 손잡이 부분(나무재질 등)을 분리하여 배출
'),
+ (1039, '도마', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1039.webp',
+ '종량제봉투 < 나무도마플라스틱 < 플라스틱 도마
'),
+ (1040, '도자기류', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1040.webp', NULL),
+ (1041, '돋보기', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1041.webp', NULL),
+ (1042, '디지털카메라', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1042.webp', NULL),
+ (1043, '뚝배기', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1043.webp', NULL),
+ (1044, '라디오', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1044.webp', NULL),
+ (1045, '라이터(일회용)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1045.webp',
+ '모두 사용한 후 종량제봉투 로 배출
'),
+ (1046, '라켓(배드민턴, 테니스 등)', '생활폐기물/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1046.webp',
+ '종량제봉투 에 담을 수 없다면 대형폐기물 로 처리
'),
+ (1047, '랩(사용 후)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1047.webp',
+ '사용한 랩은 종량제봉투 로 배출
'),
+ (1048, '랩의 심', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1048.webp', NULL),
+ (1049, '런닝머신', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1049.webp', NULL),
+ (1050, '리코더(플라스틱)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1050.webp', NULL),
+ (1051, '마스크', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1051.webp', NULL),
+ (1052, '마우스패드', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1052.webp', NULL),
+ (1053, '마커펜, 만년필', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1053.webp', NULL),
+ (1054, '매트, 매트리스', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1054.webp', NULL),
+ (1055, '맥주병뚜껑(철, 알루미늄)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1055.webp', NULL),
+ (1056, '머그컵(도자기류)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1056.webp', NULL),
+ (1057, '머플러(목도리)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1057.webp',
+ '의류 및 원단류 배출 방법을 참고하여 배출
'),
+ (1058, '메가폰(플라스틱)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1058.webp', NULL),
+ (1059, '면도기(일회용)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1059.webp', NULL),
+ (1060, '면도칼', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1060.webp',
+ '수거원이 다치지 않도록 종이 등으로 감싸서 종량제봉투 로 배출
'),
+ (1061, '면봉', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1061.webp', NULL),
+ (1062, '명함', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1062.webp',
+ '종이류 로 배출하며, 플라스틱 합성지 등 다른 재질 포함시 종량제봉투 에 배출
'),
+ (1063, '명함지갑', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1063.webp', NULL),
+ (1064, '모니터(컴퓨터 TV)', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1064.webp', NULL),
+ (1065, '모자(의류)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1065.webp',
+ '의류 및 원단류 배출 방법을 참고하여 배출하거나 종량제봉투 로 배출
'),
+ (1066, '목발', '생활폐기물/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1066.webp',
+ '대형폐기물 또는 고철 등 재질에 맞게 배출
'),
+ (1067, '목재', '생활폐기물/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1067.webp',
+ '종량제봉투 에 담을 수 없다면 대형폐기물 로 처리
'),
+ (1068, '문갑, 문짝', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1068.webp', NULL),
+ (1069, '물티슈', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1069.webp', NULL),
+ (1070, '밀짚모자', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1070.webp', NULL),
+ (1071, '바나나껍질', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1071.webp', NULL),
+ (1072, '바둑판', '생활폐기물/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1072.webp',
+ '종량제봉투 에 담을 수 없는 경우 대형폐기물 로 처리
'),
+ (1073, '바베큐그릴', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1073.webp', NULL),
+ (1074, '밥상', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1074.webp', NULL),
+ (1075, '방석', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1075.webp', NULL),
+ (1076, '백과사전, 사전', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1076.webp', NULL),
+ (1077, '백열전구', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1077.webp', NULL),
+ (1078, '벼루', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1078.webp', NULL),
+ (1079, '벽돌', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1079.webp', NULL),
+ (1080, '벽시계', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1080.webp', NULL),
+ (1081, '보온병', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1081.webp', NULL),
+ (1082, '복사기', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1082.webp', NULL),
+ (1083, '볼펜', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1083.webp', NULL),
+ (1084, '볼풀공', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1084.webp', NULL),
+ (1085, '분무기(플라스틱)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1085.webp', NULL),
+ (1086, '분유 깡통', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1086.webp', NULL),
+ (1087, '붓', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1087.webp', NULL),
+ (1088, '블라인드', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1088.webp', NULL),
+ (1089, '비닐봉지(일회용)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1089.webp', NULL),
+ (1090, '비닐장판', '생활폐기물/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1090.webp',
+ '종량제봉투 에 담을 수 없는 경우 대형폐기물 로 처리
'),
+ (1091, '비닐코팅종이', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1091.webp', NULL),
+ (1092, '비디오카메라(캠코더)', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1092.webp', NULL),
+ (1093, '비디오테이프', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1093.webp',
+ '내부 필름은 분리하여 종량제봉투 로 배출
'),
+ (1094, '빗, 헤어브러시', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1094.webp',
+ '재질에 맞게 배출하되 나무 빗 등은 종량제봉투 로 배출
'),
+ (1095, '빨대', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1095.webp', NULL),
+ (1096, '사다리', '생활폐기물/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1096.webp',
+ '대형폐기물 또는 고철 로 배출
'),
+ (1097, '사인펜', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1097.webp', NULL),
+ (1098, '사진', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1098.webp', NULL),
+ (1099, '사진인화지', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1099.webp', NULL),
+ (1100, '삽', '생활폐기물/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1100.webp',
+ '대형폐기물 또는 재질에 맞게 배출
'),
+ (1101, '상한 음식', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1101.webp', NULL),
+ (1102, '생선(먹고 남은)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1102.webp',
+ '생선뼈는 종량제봉투 에 버리고, 나머지는 음식물 로 배출
'),
+ (1103, '샤프펜슬', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1103.webp', NULL),
+ (1104, '샴푸용기', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1104.webp',
+ '내부를 헹구고 플라스틱 으로 배출
'),
+ (1105, '서랍장', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1105.webp', NULL),
+ (1106, '서류봉투(갈색 종이)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1106.webp', NULL),
+ (1107, '선풍기', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1107.webp', NULL),
+ (1108, '성냥', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1108.webp',
+ '물에 적신 후 종량제봉투 로 배출
'),
+ (1109, '세면대', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1109.webp', NULL),
+ (1110, '세탁기', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1110.webp', NULL),
+ (1111, '셔틀콕(배드민턴공, 깃털공)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1111.webp', NULL),
+ (1112, '소스용기', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1112.webp',
+ '내부를 헹구고 플라스틱 또는 재질에 맞게 배출
'),
+ (1113, '손목 시계', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1113.webp',
+ '종량제종투 에 담을 수 없는 경우 대형폐기물 로 처리, 건전지는 분리하여 전용수거함 으로 배출
'),
+ (1114, '솜', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1114.webp', NULL),
+ (1115, '솜이불', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1115.webp', NULL),
+ (1116, '송곳', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1116.webp', NULL),
+ (1117, '수세미', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1117.webp', NULL),
+ (1118, '수조, 수족관', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1118.webp', NULL),
+ (1119, '수첩', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1119.webp',
+ '종이류 또는 재질에 맞게 배출
'),
+ (1120, '숯', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1120.webp', NULL),
+ (1121, '스노우보드', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1121.webp', NULL),
+ (1122, '스캐너', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1122.webp', NULL),
+ (1123, '스케이트보드', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1123.webp', NULL),
+ (1124, '스키용구류', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1124.webp', NULL),
+ (1125, '스탠드', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1125.webp', NULL),
+ (1126, '스폰지', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1126.webp', NULL),
+ (1127, '스프레이, 부탄가스', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1127.webp', '금속캔(부탄가스통) 배출방법 참고
'),
+ (1128, '스피커', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1128.webp', NULL),
+ (1129, '식기세척기(식기건조기)', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1129.webp', NULL),
+ (1130, '식물, 나무', '생활폐기물/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1130.webp',
+ '종량제봉투 에 담을 수 없는 경우 대형폐기물 로 처리
'),
+ (1131, '식용유용기(플라스틱)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1131.webp',
+ '모두 사용 후 플라스틱 으로 배출
'),
+ (1132, '신문지', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1132.webp', NULL),
+ (1133, '신발', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1133.webp', NULL),
+ (1134, '신발장', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1134.webp', NULL),
+ (1135, '싱크대', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1135.webp', NULL),
+ (1136, '쌀통', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1136.webp', NULL),
+ (1137, '쌀포대', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1137.webp',
+ '종이류 또는 재질에 맞게 배출
'),
+ (1138, '쓰레받기', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1138.webp',
+ '플라스틱 < 가정용 플라스틱 쓰레받기고철 < 고철 쓰레받기
'),
+ (1139, '아기욕조, 아기침대', '생활폐기물/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1139.webp', NULL),
+ (1140, '아령', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1140.webp',
+ '고철 또는 재질에 맞게 배출
'),
+ (1141, '아이스팩', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1141.webp', NULL),
+ (1142, '악기', '생활폐기물/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1142.webp',
+ '종량제봉투 에 담을 수 없는 경우 대형페기물 로 배출 ※악기는 폐가전 제품 무상방문 수거 대상품목이 아님
'),
+ (1143, '압력솓', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1143.webp', NULL),
+ (1144, '애완동물 용변 시트', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1144.webp', NULL),
+ (1145, '애완동물 음식캔', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1145.webp',
+ '금속캔 으로 배출
'),
+ (1146, '애완동물집, 운반케이스', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1146.webp', NULL),
+ (1147, '액자', '생활폐기물/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1147.webp',
+ '종량제봉투 에 담을 수 없는 경우 대형폐기물 로 처리
'),
+ (1148, '앨범', '생활폐기물/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1148.webp',
+ '종량제봉투 에 담을 수 없는 경우 대형폐기물 로 처리
'),
+ (1149, '야구공', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1149.webp', NULL),
+ (1150, '야구글러브', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1150.webp', NULL),
+ (1151, '야구배트', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1151.webp',
+ '재질에 맞게 배출 또는 종량제봉투 로 배출
'),
+ (1152, '약 종류', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1152.webp',
+ '약국, 보건소 등의 전용수거함 으로 배출
'),
+ (1153, '양초', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1153.webp', NULL),
+ (1154, '에어매트', '생활폐기물/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1154.webp',
+ '종량제봉투 에 담을 수 없는 경우 대형폐기물 로 처리
'),
+ (1155, '에어컨', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1155.webp', NULL),
+ (1156, '엔진오일', '기타폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1156.webp',
+ '전문처리시설 (카센터 등)로 배출
'),
+ (1157, '여행가방(트렁크)', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1157.webp', NULL),
+ (1158, '역기', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1158.webp', NULL),
+ (1159, '연필, 색연필', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1159.webp', NULL),
+ (1160, '연필깎이', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1160.webp', NULL),
+ (1161, '오디오세트', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1161.webp', NULL),
+ (1162, '오렌지껍질', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1162.webp', NULL),
+ (1163, '온풍기', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1163.webp', NULL),
+ (1164, '옷걸이(세탁소 흰색 철사)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1164.webp', NULL),
+ (1165, '와이퍼', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1165.webp', NULL),
+ (1166, '와인셀러', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1166.webp', NULL),
+ (1167, '완충재(뽁뽁이)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1167.webp', NULL),
+ (1168, '요가매트', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1168.webp', NULL),
+ (1169, '우산', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1169.webp',
+ '뼈대와 비닐을 분리하여, 각각의 분리수거함으로 배출, 분리가 어렵다면 종량제봉투 로 배출
'),
+ (1170, '유리병', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1170.webp', '유리병류 로 배출
'),
+ (1171, '유리병뚜껑(철, 알루미늄)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1171.webp', NULL),
+ (1172, '유리판, 유리제품', '생활폐기물/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1172.webp',
+ '불연성폐기물 또는 대형폐기물 로 배출
'),
+ (1173, '유모차', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1173.webp', NULL),
+ (1174, '윤활유', '기타폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1174.webp', '구입처와 상담 후 배출
'),
+ (1175, '응접세트', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1175.webp', NULL),
+ (1176, '의류', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1176.webp',
+ '의류 및 원단류 로 배출
'),
+ (1177, '의류건조대', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1177.webp',
+ '고철 , 플라스틱 등 재질에 맞게 배출
'),
+ (1178, '의자', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1178.webp', NULL),
+ (1179, '이불', '생활폐기물/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1179.webp',
+ '종량제종투 에 담을 수 없는 경우 대형폐기물 로 배출
'),
+ (1180, '인형류', '생활폐기물/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1180.webp',
+ '종량제종투 에 담을 수 없는 경우 대형폐기물 로 배출
'),
+ (1181, '자동차 부품', '기타폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1181.webp', '중고센터, 판매처 등과 상담하여 처리
'),
+ (1182, '자루걸레', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1182.webp', NULL),
+ (1183, '자석', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1183.webp', NULL),
+ (1184, '자전거', '기타폐기물/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1184.webp',
+ '대형폐기물 로 처리하거나, 중고센터 등과 상담하여 처리
'),
+ (1185, '잡지', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1185.webp', NULL),
+ (1186, '장난감류', '생활폐기물/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1186.webp',
+ '크기에 따라 대형폐기물 또는 재질에 맞게 배출 (여러재질이 섞인 경우 종량제봉투 로 배출)
'),
+ (1187, '장롱', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1187.webp', NULL),
+ (1188, '장식장', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1188.webp', NULL),
+ (1189, '장판', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1189.webp', NULL),
+ (1190, '재떨이', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1190.webp',
+ '불연성-종량제 < 도자기·유리재떨이고철 < 금속류 재떨이
'),
+ (1191, '전기밥솔', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1191.webp', NULL),
+ (1192, '전기비데', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1192.webp', NULL),
+ (1193, '전기오븐레인지', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1193.webp', NULL),
+ (1194, '전기코드류', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1194.webp', NULL),
+ (1195, '전기포트', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1195.webp', NULL),
+ (1196, '전단지', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1196.webp', NULL),
+ (1197, '전동칫솔', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1197.webp', NULL),
+ (1198, '전자레인지', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1198.webp', NULL),
+ (1199, '전자사전(전자수첩)', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1199.webp', NULL),
+ (1200, '전자피아노', '생활폐기물/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1200.webp',
+ '종량제봉투에 담을 수 없는 경우 대형폐기물로 처리 ※악기는 폐가전 제품 무상방문 수거 대상품목이 아님
'),
+ (1201, '전지', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1201.webp',
+ '전용수거함 으로 배출
'),
+ (1202, '전축', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1202.webp', NULL),
+ (1203, '전화기(팩스포함)', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1203.webp', NULL),
+ (1204, '전화번호부', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1204.webp', NULL),
+ (1205, '접착제(본드 등)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1205.webp', NULL),
+ (1206, '정기장판', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1206.webp',
+ '대형폐기물 로 배출 (전기이불담요 포함)
'),
+ (1207, '정수기', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1207.webp',
+ '대형가전 으로 배출
'),
+ (1208, '젖꼭지(아기용품)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1208.webp', NULL),
+ (1209, '젖병(아기용품)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1209.webp',
+ '젖병의 몸체와 윗부분의 젖꼭지는 분리하여 재질에 맞게 배출
'),
+ (1210, '조각칼', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1210.webp',
+ '수거원이 다치지 않도록 종이 등으로 감싸서 종량제봉투 로 배출
'),
+ (1211, '종이기저귀', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1211.webp', NULL),
+ (1212, '종이상자', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1212.webp', NULL),
+ (1213, '종이심', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1213.webp', NULL),
+ (1214, '종이조각', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1214.webp', NULL),
+ (1215, '종이팩(우유팩 등)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1215.webp',
+ '내부에 알루미늄박이 붙어 있다면 종량제봉투 로 배출
'),
+ (1216, '주전자(철, 알루미늄)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1216.webp',
+ '플라스틱 뚜껑 등은 돌려서 제거한 후 고철 로 배출
'),
+ (1217, '줄자', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1217.webp',
+ '재질에 맞게 배출 또는 종량제봉투 로 배출
'),
+ (1218, '지우개', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1218.webp', NULL),
+ (1219, '진열대', '생활폐기물/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1219.webp', NULL),
+ (1220, '차 찌꺼기', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1220.webp', NULL),
+ (1221, '찬장', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1221.webp', NULL),
+ (1222, '찻잔(도자기류)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1222.webp', NULL),
+ (1223, '책', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1223.webp', NULL),
+ (1224, '책상, 책장', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1224.webp', NULL),
+ (1225, '천체망원경', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1225.webp', NULL),
+ (1226, '철사', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1226.webp', NULL),
+ (1227, '철판(가정요리용)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1227.webp', NULL),
+ (1228, '청소기', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1228.webp', NULL),
+ (1229, '체온계(건전지, 디지털)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1229.webp',
+ '건전지는 분리하여 전용수거함 으로 배출
'),
+ (1230, '체중계', '생활폐기물/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1230.webp',
+ '종량제종투 에 담을 수 없는 경우 대형폐기물 로 배출
'),
+ (1231, '축구공', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1231.webp', NULL),
+ (1232, '충전식전지', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1232.webp',
+ '전용수거함 으로 배출
'),
+ (1233, '치약용기', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1233.webp',
+ '플라스틱 또는 재질에 맞게 배출
'),
+ (1234, '치킨박스', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1234.webp',
+ '기름에 오염된 내부 종이는 종량제봉투 로 배출
'),
+ (1235, '침구류(이불, 베개 등)', '생활폐기물/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1235.webp',
+ '종량제종투 에 담을 수 없는 경우 대형폐기물 로 배출
'),
+ (1236, '침대', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1236.webp', NULL),
+ (1237, '칫솔', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1237.webp', NULL),
+ (1238, '카펫, 융단', '생활폐기물/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1238.webp',
+ '종량제종투 에 담을 수 없는 경우 대형폐기물 로 배출
'),
+ (1239, '캐비넷', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1239.webp', NULL),
+ (1240, '캔 따개', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1240.webp', NULL),
+ (1241, '캡(플라스틱 뚜껑)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1241.webp', NULL),
+ (1242, '커튼, 커튼 레일', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1242.webp',
+ '종량제종투 에 담을 수 없는 경우 대형폐기물 로 배출
'),
+ (1243, '커피메이커', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1243.webp', NULL),
+ (1244, '커피원두, 찌꺼기', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1244.webp', NULL),
+ (1245, '컴퓨터', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1245.webp', NULL),
+ (1246, '컵', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1246.webp',
+ '불연성-종량제 < 도자기·유리 컵고철 < 금속·비금속 컵플라스틱 < 플라스틱컵
'),
+ (1247, '코르크따개(와인따개)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1247.webp',
+ '수거원이 다치지 않도록 종이 등으로 감싸서 종량제봉투 로 배출재질에 맞게 해당 분리수거함으로 배출
'),
+ (1248, '코르크마개', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1248.webp', NULL),
+ (1249, '코팅된 종이', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1249.webp', NULL),
+ (1250, '콘센트', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1250.webp', NULL),
+ (1251, '콘텍트렌즈', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1251.webp', NULL),
+ (1252, '쿠션', '생활폐기물/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1252.webp',
+ '종량제종투 에 담을 수 없는 경우 대형폐기물 로 배출
'),
+ (1253, '크레용', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1253.webp', NULL),
+ (1254, '키보드(컴퓨터용)', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1254.webp', NULL),
+ (1255, '타이어', '기타폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1255.webp', '구입처와 상담 후 배출
'),
+ (1256, '탁상달력', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1256.webp',
+ '종이류 배출방법을 참고하여 배출
'),
+ (1257, '탈수기', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1257.webp', NULL),
+ (1258, '텐트', '생활폐기물/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1258.webp',
+ '종량제종투 에 담을 수 없는 경우 대형폐기물 로 배출
'),
+ (1259, '텔레비전(TV)', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1259.webp', NULL),
+ (1260, '토스터기', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1260.webp', NULL),
+ (1261, '톱', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1261.webp', NULL),
+ (1262, '튀김기름', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1262.webp',
+ '폐식용유 전용수거함 배출
'),
+ (1263, '틀니', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1263.webp', NULL),
+ (1264, '티백(녹차)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1264.webp', NULL),
+ (1265, '파인애플껍질', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1265.webp', NULL),
+ (1266, '팩스(복합기)', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1266.webp', NULL),
+ (1267, '페트병', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1267.webp', NULL),
+ (1268, '포스터, 포장지', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1268.webp',
+ '종이류 배출방법을 참고하여 배출
'),
+ (1269, '프린터', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1269.webp', NULL),
+ (1270, '피아노', '기타폐기물/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1270.webp',
+ '대형폐기물 로 처리하거나, 중고센터등과 상담하여 처리
'),
+ (1271, '피자 세이버(피자 삼발이)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1271.webp', NULL),
+ (1272, '피자박스', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1272.webp',
+ '기름에 오염된 내부 종이는 종량제봉투 로 배출
'),
+ (1273, '필름(사진용)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1273.webp', NULL),
+ (1274, '핫팩', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1274.webp', NULL),
+ (1275, '항아리', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1275.webp',
+ '대형폐기물 로 처리
'),
+ (1276, '헝겁류', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1276.webp', NULL),
+ (1277, '헤드폰(헤드셋)', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1277.webp', NULL),
+ (1278, '헬멧', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1278.webp',
+ '종량제봉투 로 배출하되, 분리하여 재질에 맞게 배출
'),
+ (1279, '형광등', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1279.webp',
+ '전용수거함 으로 배출
'),
+ (1280, '호일(사용 후)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1280.webp', NULL),
+ (1281, '화로', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1281.webp', NULL),
+ (1282, '화분, 화병', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1282.webp',
+ '불연성폐기물 로 배출 등 재질에 맞게 배출
'),
+ (1283, '화장대', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1283.webp', NULL),
+ (1284, '화장품냉장고', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1284.webp', NULL),
+ (1285, '후라이팬', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1285.webp', NULL),
+ (1286, '휴대용 플레이어(MP3등)', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1286.webp', NULL),
+ (1287, '휴대전화', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1287.webp',
+ '폐가전제품 으로 배출 ※우체국 보상판매 이용 가능
');
+
+INSERT INTO waste_categories (categories, waste_id)
+VALUES ('PAPER', 1001),
+ ('BULKY', 1002),
+ ('PAY_AS_YOU_GO_BAG', 1003),
+ ('HOME_APPLIANCES', 1004),
+ ('PAY_AS_YOU_GO_BAG', 1005),
+ ('SEPARATION_BY_MATERIAL', 1005),
+ ('BULKY', 1006),
+ ('NON_FLAMMABLE', 1007),
+ ('BULKY', 1007),
+ ('PAY_AS_YOU_GO_BAG', 1008),
+ ('PAY_AS_YOU_GO_BAG', 1009),
+ ('PAY_AS_YOU_GO_BAG', 1010),
+ ('PAPER', 1011),
+ ('PLASTIC', 1011),
+ ('BULKY', 1012),
+ ('PAY_AS_YOU_GO_BAG', 1013),
+ ('IRON', 1014),
+ ('HOME_APPLIANCES', 1015),
+ ('PAPER', 1016),
+ ('CLOTHES', 1017),
+ ('PAY_AS_YOU_GO_BAG', 1017),
+ ('IRON', 1018),
+ ('PLASTIC', 1018),
+ ('PAY_AS_YOU_GO_BAG', 1018),
+ ('NON_FLAMMABLE', 1019),
+ ('IRON', 1019),
+ ('PLASTIC', 1019),
+ ('PRO_FACILITY', 1020),
+ ('ONLY_BOX', 1021),
+ ('BULKY', 1022),
+ ('NON_FLAMMABLE', 1023),
+ ('PAY_AS_YOU_GO_BAG', 1024),
+ ('PAY_AS_YOU_GO_BAG', 1025),
+ ('BULKY', 1025),
+ ('IRON', 1026),
+ ('PAY_AS_YOU_GO_BAG', 1027),
+ ('PAY_AS_YOU_GO_BAG', 1028),
+ ('BULKY', 1029),
+ ('HOME_APPLIANCES', 1030),
+ ('BULKY', 1030),
+ ('IRON', 1031),
+ ('PAY_AS_YOU_GO_BAG', 1031),
+ ('NON_FLAMMABLE', 1032),
+ ('PAY_AS_YOU_GO_BAG', 1033),
+ ('HOME_APPLIANCES', 1034),
+ ('HOME_APPLIANCES', 1035),
+ ('CAUTION', 1036),
+ ('HOME_APPLIANCES', 1037),
+ ('IRON', 1038),
+ ('PAY_AS_YOU_GO_BAG', 1038),
+ ('PAY_AS_YOU_GO_BAG', 1039),
+ ('PLASTIC', 1039),
+ ('NON_FLAMMABLE', 1040),
+ ('PAY_AS_YOU_GO_BAG', 1041),
+ ('HOME_APPLIANCES', 1042),
+ ('NON_FLAMMABLE', 1043),
+ ('HOME_APPLIANCES', 1044),
+ ('PAY_AS_YOU_GO_BAG', 1045),
+ ('CAUTION', 1045),
+ ('PAY_AS_YOU_GO_BAG', 1046),
+ ('BULKY', 1046),
+ ('PAY_AS_YOU_GO_BAG', 1047),
+ ('PAPER', 1048),
+ ('HOME_APPLIANCES', 1049),
+ ('PLASTIC', 1050),
+ ('PAY_AS_YOU_GO_BAG', 1051),
+ ('PAY_AS_YOU_GO_BAG', 1052),
+ ('PAY_AS_YOU_GO_BAG', 1053),
+ ('BULKY', 1054),
+ ('IRON', 1055),
+ ('NON_FLAMMABLE', 1056),
+ ('CLOTHES', 1057),
+ ('PLASTIC', 1058),
+ ('PAY_AS_YOU_GO_BAG', 1059),
+ ('PAY_AS_YOU_GO_BAG', 1060),
+ ('CAUTION', 1060),
+ ('PAY_AS_YOU_GO_BAG', 1061),
+ ('PAPER', 1062),
+ ('PAY_AS_YOU_GO_BAG', 1062),
+ ('PAY_AS_YOU_GO_BAG', 1063),
+ ('HOME_APPLIANCES', 1064),
+ ('CLOTHES', 1065),
+ ('PAY_AS_YOU_GO_BAG', 1065),
+ ('IRON', 1066),
+ ('SEPARATION_BY_MATERIAL', 1066),
+ ('BULKY', 1066),
+ ('PAY_AS_YOU_GO_BAG', 1067),
+ ('BULKY', 1067),
+ ('BULKY', 1068),
+ ('PAY_AS_YOU_GO_BAG', 1069),
+ ('PAY_AS_YOU_GO_BAG', 1070),
+ ('PROG', 1071),
+ ('PAY_AS_YOU_GO_BAG', 1072),
+ ('BULKY', 1072),
+ ('BULKY', 1073),
+ ('BULKY', 1074),
+ ('PAY_AS_YOU_GO_BAG', 1075),
+ ('PAPER', 1076),
+ ('NON_FLAMMABLE', 1077),
+ ('PAY_AS_YOU_GO_BAG', 1078),
+ ('NON_FLAMMABLE', 1079),
+ ('BULKY', 1080),
+ ('PAY_AS_YOU_GO_BAG', 1081),
+ ('HOME_APPLIANCES', 1082),
+ ('PAY_AS_YOU_GO_BAG', 1083),
+ ('PLASTIC', 1084),
+ ('PLASTIC', 1085),
+ ('IRON', 1086),
+ ('PAY_AS_YOU_GO_BAG', 1087),
+ ('BULKY', 1088),
+ ('VINYL', 1089),
+ ('PAY_AS_YOU_GO_BAG', 1090),
+ ('BULKY', 1090),
+ ('PAY_AS_YOU_GO_BAG', 1091),
+ ('HOME_APPLIANCES', 1092),
+ ('PLASTIC', 1093),
+ ('SEPARATION_BY_MATERIAL', 1094),
+ ('PAY_AS_YOU_GO_BAG', 1094),
+ ('PLASTIC', 1095),
+ ('IRON', 1096),
+ ('BULKY', 1096),
+ ('PAY_AS_YOU_GO_BAG', 1097),
+ ('PAY_AS_YOU_GO_BAG', 1098),
+ ('PAY_AS_YOU_GO_BAG', 1099),
+ ('SEPARATION_BY_MATERIAL', 1100),
+ ('BULKY', 1100),
+ ('PROG', 1101),
+ ('PROG', 1102),
+ ('PAY_AS_YOU_GO_BAG', 1102),
+ ('PAY_AS_YOU_GO_BAG', 1103),
+ ('PLASTIC', 1104),
+ ('BULKY', 1105),
+ ('PAPER', 1106),
+ ('HOME_APPLIANCES', 1107),
+ ('PAY_AS_YOU_GO_BAG', 1108),
+ ('BULKY', 1109),
+ ('HOME_APPLIANCES', 1110),
+ ('PAY_AS_YOU_GO_BAG', 1111),
+ ('PLASTIC', 1112),
+ ('SEPARATION_BY_MATERIAL', 1112),
+ ('PAY_AS_YOU_GO_BAG', 1113),
+ ('PAY_AS_YOU_GO_BAG', 1114),
+ ('BULKY', 1115),
+ ('PAY_AS_YOU_GO_BAG', 1116),
+ ('PAY_AS_YOU_GO_BAG', 1117),
+ ('BULKY', 1118),
+ ('PAPER', 1119),
+ ('SEPARATION_BY_MATERIAL', 1119),
+ ('PAY_AS_YOU_GO_BAG', 1120),
+ ('BULKY', 1121),
+ ('HOME_APPLIANCES', 1122),
+ ('BULKY', 1123),
+ ('BULKY', 1124),
+ ('HOME_APPLIANCES', 1125),
+ ('PAY_AS_YOU_GO_BAG', 1126),
+ ('METAL', 1127),
+ ('CAUTION', 1127),
+ ('HOME_APPLIANCES', 1128),
+ ('HOME_APPLIANCES', 1129),
+ ('PAY_AS_YOU_GO_BAG', 1130),
+ ('BULKY', 1130),
+ ('PLASTIC', 1131),
+ ('PAPER', 1132),
+ ('PAY_AS_YOU_GO_BAG', 1133),
+ ('BULKY', 1134),
+ ('BULKY', 1135),
+ ('BULKY', 1136),
+ ('PAPER', 1137),
+ ('SEPARATION_BY_MATERIAL', 1137),
+ ('PLASTIC', 1138),
+ ('IRON', 1138),
+ ('SEPARATION_BY_MATERIAL', 1139),
+ ('BULKY', 1139),
+ ('IRON', 1140),
+ ('SEPARATION_BY_MATERIAL', 1140),
+ ('PAY_AS_YOU_GO_BAG', 1141),
+ ('PAY_AS_YOU_GO_BAG', 1142),
+ ('BULKY', 1142),
+ ('IRON', 1143),
+ ('PAY_AS_YOU_GO_BAG', 1144),
+ ('METAL', 1145),
+ ('CAUTION', 1145),
+ ('SEPARATION_BY_MATERIAL', 1146),
+ ('PAY_AS_YOU_GO_BAG', 1147),
+ ('BULKY', 1147),
+ ('PAY_AS_YOU_GO_BAG', 1148),
+ ('BULKY', 1148),
+ ('PAY_AS_YOU_GO_BAG', 1149),
+ ('PAY_AS_YOU_GO_BAG', 1150),
+ ('SEPARATION_BY_MATERIAL', 1151),
+ ('PAY_AS_YOU_GO_BAG', 1151),
+ ('ONLY_BOX', 1152),
+ ('PAY_AS_YOU_GO_BAG', 1153),
+ ('PAY_AS_YOU_GO_BAG', 1154),
+ ('BULKY', 1154),
+ ('HOME_APPLIANCES', 1155),
+ ('PRO_FACILITY', 1156),
+ ('BULKY', 1157),
+ ('IRON', 1158),
+ ('PAY_AS_YOU_GO_BAG', 1159),
+ ('PAY_AS_YOU_GO_BAG', 1160),
+ ('HOME_APPLIANCES', 1161),
+ ('PROG', 1162),
+ ('HOME_APPLIANCES', 1163),
+ ('IRON', 1164),
+ ('SEPARATION_BY_MATERIAL', 1165),
+ ('HOME_APPLIANCES', 1166),
+ ('VINYL', 1167),
+ ('PAY_AS_YOU_GO_BAG', 1168),
+ ('PAY_AS_YOU_GO_BAG', 1169),
+ ('GLASS', 1170),
+ ('CAUTION', 1170),
+ ('IRON', 1171),
+ ('NON_FLAMMABLE', 1172),
+ ('BULKY', 1172),
+ ('BULKY', 1173),
+ ('PRO_FACILITY', 1174),
+ ('BULKY', 1175),
+ ('CLOTHES', 1176),
+ ('IRON', 1177),
+ ('PLASTIC', 1177),
+ ('SEPARATION_BY_MATERIAL', 1177),
+ ('BULKY', 1178),
+ ('PAY_AS_YOU_GO_BAG', 1179),
+ ('BULKY', 1179),
+ ('PAY_AS_YOU_GO_BAG', 1180),
+ ('BULKY', 1180),
+ ('PRO_FACILITY', 1181),
+ ('BULKY', 1182),
+ ('PAY_AS_YOU_GO_BAG', 1183),
+ ('PRO_FACILITY', 1184),
+ ('BULKY', 1184),
+ ('PAPER', 1185),
+ ('SEPARATION_BY_MATERIAL', 1186),
+ ('PAY_AS_YOU_GO_BAG', 1186),
+ ('BULKY', 1186),
+ ('BULKY', 1187),
+ ('BULKY', 1188),
+ ('BULKY', 1189),
+ ('NON_FLAMMABLE', 1190),
+ ('IRON', 1190),
+ ('HOME_APPLIANCES', 1191),
+ ('HOME_APPLIANCES', 1192),
+ ('HOME_APPLIANCES', 1193),
+ ('PAY_AS_YOU_GO_BAG', 1194),
+ ('HOME_APPLIANCES', 1195),
+ ('PAPER', 1196),
+ ('PAY_AS_YOU_GO_BAG', 1197),
+ ('HOME_APPLIANCES', 1198),
+ ('HOME_APPLIANCES', 1199),
+ ('PAY_AS_YOU_GO_BAG', 1200),
+ ('BULKY', 1200),
+ ('ONLY_BOX', 1201),
+ ('HOME_APPLIANCES', 1202),
+ ('HOME_APPLIANCES', 1203),
+ ('PAPER', 1204),
+ ('PAY_AS_YOU_GO_BAG', 1205),
+ ('BULKY', 1206),
+ ('HOME_APPLIANCES', 1207),
+ ('PAY_AS_YOU_GO_BAG', 1208),
+ ('PLASTIC', 1209),
+ ('SEPARATION_BY_MATERIAL', 1209),
+ ('PAY_AS_YOU_GO_BAG', 1210),
+ ('PAY_AS_YOU_GO_BAG', 1211),
+ ('PAPER', 1212),
+ ('PAPER', 1213),
+ ('PAPER', 1214),
+ ('CARTON', 1215),
+ ('IRON', 1216),
+ ('SEPARATION_BY_MATERIAL', 1217),
+ ('PAY_AS_YOU_GO_BAG', 1217),
+ ('PAY_AS_YOU_GO_BAG', 1218),
+ ('SEPARATION_BY_MATERIAL', 1219),
+ ('BULKY', 1219),
+ ('PAY_AS_YOU_GO_BAG', 1220),
+ ('BULKY', 1221),
+ ('NON_FLAMMABLE', 1222),
+ ('PAPER', 1223),
+ ('BULKY', 1224),
+ ('BULKY', 1225),
+ ('IRON', 1226),
+ ('IRON', 1227),
+ ('HOME_APPLIANCES', 1228),
+ ('PAY_AS_YOU_GO_BAG', 1229),
+ ('PAY_AS_YOU_GO_BAG', 1230),
+ ('BULKY', 1230),
+ ('PAY_AS_YOU_GO_BAG', 1231),
+ ('ONLY_BOX', 1232),
+ ('PLASTIC', 1233),
+ ('SEPARATION_BY_MATERIAL', 1233),
+ ('PAPER', 1234),
+ ('PAY_AS_YOU_GO_BAG', 1235),
+ ('BULKY', 1235),
+ ('BULKY', 1236),
+ ('PAY_AS_YOU_GO_BAG', 1237),
+ ('PAY_AS_YOU_GO_BAG', 1238),
+ ('BULKY', 1238),
+ ('BULKY', 1239),
+ ('IRON', 1240),
+ ('PLASTIC', 1241),
+ ('PAY_AS_YOU_GO_BAG', 1242),
+ ('BULKY', 1242),
+ ('HOME_APPLIANCES', 1243),
+ ('PAY_AS_YOU_GO_BAG', 1244),
+ ('HOME_APPLIANCES', 1245),
+ ('NON_FLAMMABLE', 1246),
+ ('IRON', 1246),
+ ('PLASTIC', 1246),
+ ('PAY_AS_YOU_GO_BAG', 1247),
+ ('SEPARATION_BY_MATERIAL', 1247),
+ ('PAY_AS_YOU_GO_BAG', 1248),
+ ('PAY_AS_YOU_GO_BAG', 1249),
+ ('PAY_AS_YOU_GO_BAG', 1250),
+ ('PAY_AS_YOU_GO_BAG', 1251),
+ ('PAY_AS_YOU_GO_BAG', 1252),
+ ('BULKY', 1252),
+ ('PAY_AS_YOU_GO_BAG', 1253),
+ ('HOME_APPLIANCES', 1254),
+ ('PRO_FACILITY', 1255),
+ ('PAPER', 1256),
+ ('HOME_APPLIANCES', 1257),
+ ('PAY_AS_YOU_GO_BAG', 1258),
+ ('BULKY', 1258),
+ ('HOME_APPLIANCES', 1259),
+ ('HOME_APPLIANCES', 1260),
+ ('IRON', 1261),
+ ('ONLY_BOX', 1262),
+ ('PAY_AS_YOU_GO_BAG', 1263),
+ ('PAY_AS_YOU_GO_BAG', 1264),
+ ('PAY_AS_YOU_GO_BAG', 1265),
+ ('HOME_APPLIANCES', 1266),
+ ('PLASTIC', 1267),
+ ('PAPER', 1268),
+ ('HOME_APPLIANCES', 1269),
+ ('BULKY', 1270),
+ ('PRO_FACILITY', 1270),
+ ('PLASTIC', 1271),
+ ('PAPER', 1272),
+ ('PAY_AS_YOU_GO_BAG', 1273),
+ ('PAY_AS_YOU_GO_BAG', 1274),
+ ('BULKY', 1275),
+ ('PAY_AS_YOU_GO_BAG', 1276),
+ ('HOME_APPLIANCES', 1277),
+ ('PAY_AS_YOU_GO_BAG', 1278),
+ ('SEPARATION_BY_MATERIAL', 1278),
+ ('ONLY_BOX', 1279),
+ ('PAY_AS_YOU_GO_BAG', 1280),
+ ('BULKY', 1281),
+ ('NON_FLAMMABLE', 1282),
+ ('SEPARATION_BY_MATERIAL', 1282),
+ ('BULKY', 1283),
+ ('HOME_APPLIANCES', 1284),
+ ('IRON', 1285),
+ ('HOME_APPLIANCES', 1286),
+ ('HOME_APPLIANCES', 1287);
+
+/*INSERT INTO dogam (member_id, waste_id)
+VALUES ('okjaeook98@gmail.com', 1001),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1002),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1003),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1004),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1005),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1006),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1007),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1008),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1009),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1010),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1011),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1012),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1013),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1014),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1015),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1016),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1017),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1018),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1019),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1020),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1021),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1022),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1023),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1024),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1025),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1026),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1027),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1028),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1029),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1030),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1031),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1032),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1033),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1034),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1035),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1036),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1037),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1038),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1039),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1040),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1041),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1042),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1043),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1044),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1045),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1046),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1047),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1048),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1049),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1050),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1051),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1052),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1053),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1054),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1055),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1056),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1057),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1058),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1059),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1060),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1061),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1062),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1063),
+ ('4c29cb26-d2f3-38d8-9d02-a9da30c7ad99', 1064);*/
\ No newline at end of file
diff --git a/app/src/main/resources/db/migration/V2__insert_guide.sql b/app/src/main/resources/db/migration/V2__insert_guide.sql
new file mode 100644
index 0000000..d6f67fc
--- /dev/null
+++ b/app/src/main/resources/db/migration/V2__insert_guide.sql
@@ -0,0 +1,22 @@
+INSERT INTO guide (category, content, picture)
+VALUES ('IRON', '고철 로 배출', 'https://ik.imagekit.io/blisgo/guide/rhcjffb.webp'),
+ ('METAL', '금속캔 으로 배출', 'https://ik.imagekit.io/blisgo/guide/rmathrzos.webp'),
+ ('BULKY', '대형폐기물 로 배출', 'https://ik.imagekit.io/blisgo/guide/eogudvPrlanf.webp'),
+ ('STYROFOAM', '스티로폼 으로 배출', 'https://ik.imagekit.io/blisgo/guide/qkfvhgkqtjdtnwl.webp'),
+ ('NON_FLAMMABLE', '불연성폐기물 로 배출', 'https://ik.imagekit.io/blisgo/guide/qnfdustjd-whdfidwp.webp'),
+ ('VINYL', '비닐 로 배출', 'https://ik.imagekit.io/blisgo/guide/qlslffb.webp'),
+ ('GLASS', '유리병류 로 배출', 'https://ik.imagekit.io/blisgo/guide/dbflqud.webp'),
+ ('PROG', '음식물류 폐기물 로 배출', 'https://ik.imagekit.io/blisgo/guide/dmatlranf.webp'),
+ ('CLOTHES', '의류 및 원단류 로 배출', 'https://ik.imagekit.io/blisgo/guide/dmlfb.webp'),
+ ('ONLY_BOX', '전용수거함 으로 배출', 'https://ik.imagekit.io/blisgo/guide/wjsdydgka.webp'),
+ ('PAPER', '종이류 로 배출', 'https://ik.imagekit.io/blisgo/guide/whddl.webp'),
+ ('CARTON', '종이팩 으로 배출', 'https://ik.imagekit.io/blisgo/guide/whddlvor-whddlzjq.webp'),
+ ('HOME_APPLIANCES', '폐가전제품 으로 배출', 'https://ik.imagekit.io/blisgo/guide/vPrkwjswpvna.webp'),
+ ('PLASTIC', '플라스틱 으로 배출', 'https://ik.imagekit.io/blisgo/guide/vmffktmxlr.webp'),
+ ('PAY_AS_YOU_GO_BAG', '종량제봉투 로 배출', NULL),
+ ('SEPARATION_BY_MATERIAL', '재질에 맞게 배출', NULL),
+ ('CAUTION', '위험! 폐기시 주의가 필요함', NULL),
+ ('PRO_FACILITY', '전문처리시설 로 배출', NULL);
+
+
+
diff --git a/app/src/main/resources/db/migration/V3__insert_waste.sql b/app/src/main/resources/db/migration/V3__insert_waste.sql
new file mode 100644
index 0000000..0793b07
--- /dev/null
+++ b/app/src/main/resources/db/migration/V3__insert_waste.sql
@@ -0,0 +1,378 @@
+INSERT INTO waste (waste_id, name, type, picture, treatment)
+VALUES (1001, '가격표', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1001.webp', NULL),
+ (1002, '가구류', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1002.webp', NULL),
+ (1003, '가발', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1003.webp', NULL),
+ (1004, '가습기', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1004.webp', NULL),
+ (1005, '가위', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1005.webp',
+ '다른 재질이 많이 섞인 제품은 분리해서 배출하며, 분리가 어렵다면 종량제봉투 로 배출
'),
+ (1006, '개수대', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1006.webp', NULL),
+ (1007, '거울', '생활폐기물/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1007.webp',
+ '크기에 따라 해당 폐기물로 배출
'),
+ (1008, '걸레', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1008.webp', NULL),
+ (1009, '계란껍질', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1009.webp', NULL),
+ (1010, '고무장갑', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1010.webp', NULL),
+ (1011, '골판지', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1011.webp',
+ '종이 < 일반 골판지플라스틱 < PP 골판지
'),
+ (1012, '골프 클럽 백', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1012.webp', NULL),
+ (1013, '골프공', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1013.webp', NULL),
+ (1014, '공구류(철)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1014.webp',
+ '다른 재질이 많이 섞인 제품은 분리해서 배출하며, 분리가 어렵다면 종량제봉투 로 배출
'),
+ (1015, '공기청정기', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1015.webp', NULL),
+ (1016, '광고전단지', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1016.webp',
+ '비닐코팅된 종이는 종량제봉투 배출
'),
+ (1017, '구두, 샌들, 슬리퍼', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1017.webp',
+ '의류 및 원단류 배출 방법을 참고하여 배출하거나 종량제봉투 로 배출
'),
+ (1018, '국자', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1018.webp',
+ '고철 < 금속, 비금속 국자플라스틱 < 플라스틱 국자종량제봉투 < 나무 국자
'),
+ (1019, '그릇', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1019.webp',
+ '불연성-종량제 < 도자기·유리그릇고철 < 금속, 비금속그릇플라스틱 < 플라스틱 그릇
'),
+ (1020, '기름(기계)', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1020.webp', NULL),
+ (1021, '기름(식용)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1021.webp',
+ '전용수거함 으로 배출
'),
+ (1022, '기타(악기)', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1022.webp', NULL),
+ (1023, '깨진유리', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1023.webp',
+ '불연성폐기물 배출방법을 참조하여 배출
'),
+ (1024, '나무젓가락', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1024.webp', NULL),
+ (1025, '나무조각, 나뭇가지, 나무줄기', '생활폐기물/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1025.webp',
+ '종량제봉투 에 담을 수 없다면 대형폐기물 로 처리
'),
+ (1026, '나사(못)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1026.webp', NULL),
+ (1027, '나침반', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1027.webp', NULL),
+ (1028, '낙엽', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1028.webp', NULL),
+ (1029, '낚싯대', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1029.webp', NULL),
+ (1030, '난로(전기난로)', '폐가전제품/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1030.webp',
+ '대형폐기물 또는 가전제품 으로 배출
'),
+ (1031, '낫', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1031.webp',
+ '고철 로 배출하되, 가능하다면 손잡이 부분(나무재질 등)을 분리하여 배출
'),
+ (1032, '내열식기류', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1032.webp', NULL),
+ (1033, '냄비뚜껑(강화유리)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1033.webp', NULL),
+ (1034, '냉장고(냉동고)', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1034.webp', NULL),
+ (1035, '노트북', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1035.webp', NULL),
+ (1036, '농약용기', '기타폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1036.webp',
+ '내용물을 다 사용한 후 따로 봉투에 담아 배출
'),
+ (1037, '다리미', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1037.webp', NULL),
+ (1038, '도끼', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1038.webp',
+ '고철 로 배출하되, 가능하다면 손잡이 부분(나무재질 등)을 분리하여 배출
'),
+ (1039, '도마', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1039.webp',
+ '종량제봉투 < 나무도마플라스틱 < 플라스틱 도마
'),
+ (1040, '도자기류', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1040.webp', NULL),
+ (1041, '돋보기', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1041.webp', NULL),
+ (1042, '디지털카메라', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1042.webp', NULL),
+ (1043, '뚝배기', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1043.webp', NULL),
+ (1044, '라디오', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1044.webp', NULL),
+ (1045, '라이터(일회용)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1045.webp',
+ '모두 사용한 후 종량제봉투 로 배출
'),
+ (1046, '라켓(배드민턴, 테니스 등)', '생활폐기물/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1046.webp',
+ '종량제봉투 에 담을 수 없다면 대형폐기물 로 처리
'),
+ (1047, '랩(사용 후)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1047.webp',
+ '사용한 랩은 종량제봉투 로 배출
'),
+ (1048, '랩의 심', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1048.webp', NULL),
+ (1049, '런닝머신', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1049.webp', NULL),
+ (1050, '리코더(플라스틱)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1050.webp', NULL),
+ (1051, '마스크', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1051.webp', NULL),
+ (1052, '마우스패드', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1052.webp', NULL),
+ (1053, '마커펜, 만년필', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1053.webp', NULL),
+ (1054, '매트, 매트리스', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1054.webp', NULL),
+ (1055, '맥주병뚜껑(철, 알루미늄)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1055.webp', NULL),
+ (1056, '머그컵(도자기류)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1056.webp', NULL),
+ (1057, '머플러(목도리)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1057.webp',
+ '의류 및 원단류 배출 방법을 참고하여 배출
'),
+ (1058, '메가폰(플라스틱)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1058.webp', NULL),
+ (1059, '면도기(일회용)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1059.webp', NULL),
+ (1060, '면도칼', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1060.webp',
+ '수거원이 다치지 않도록 종이 등으로 감싸서 종량제봉투 로 배출
'),
+ (1061, '면봉', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1061.webp', NULL),
+ (1062, '명함', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1062.webp',
+ '종이류 로 배출하며, 플라스틱 합성지 등 다른 재질 포함시 종량제봉투 에 배출
'),
+ (1063, '명함지갑', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1063.webp', NULL),
+ (1064, '모니터(컴퓨터 TV)', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1064.webp', NULL),
+ (1065, '모자(의류)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1065.webp',
+ '의류 및 원단류 배출 방법을 참고하여 배출하거나 종량제봉투 로 배출
'),
+ (1066, '목발', '생활폐기물/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1066.webp',
+ '대형폐기물 또는 고철 등 재질에 맞게 배출
'),
+ (1067, '목재', '생활폐기물/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1067.webp',
+ '종량제봉투 에 담을 수 없다면 대형폐기물 로 처리
'),
+ (1068, '문갑, 문짝', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1068.webp', NULL),
+ (1069, '물티슈', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1069.webp', NULL),
+ (1070, '밀짚모자', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1070.webp', NULL),
+ (1071, '바나나껍질', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1071.webp', NULL),
+ (1072, '바둑판', '생활폐기물/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1072.webp',
+ '종량제봉투 에 담을 수 없는 경우 대형폐기물 로 처리
'),
+ (1073, '바베큐그릴', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1073.webp', NULL),
+ (1074, '밥상', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1074.webp', NULL),
+ (1075, '방석', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1075.webp', NULL),
+ (1076, '백과사전, 사전', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1076.webp', NULL),
+ (1077, '백열전구', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1077.webp', NULL),
+ (1078, '벼루', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1078.webp', NULL),
+ (1079, '벽돌', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1079.webp', NULL),
+ (1080, '벽시계', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1080.webp', NULL),
+ (1081, '보온병', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1081.webp', NULL),
+ (1082, '복사기', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1082.webp', NULL),
+ (1083, '볼펜', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1083.webp', NULL),
+ (1084, '볼풀공', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1084.webp', NULL),
+ (1085, '분무기(플라스틱)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1085.webp', NULL),
+ (1086, '분유 깡통', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1086.webp', NULL),
+ (1087, '붓', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1087.webp', NULL),
+ (1088, '블라인드', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1088.webp', NULL),
+ (1089, '비닐봉지(일회용)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1089.webp', NULL),
+ (1090, '비닐장판', '생활폐기물/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1090.webp',
+ '종량제봉투 에 담을 수 없는 경우 대형폐기물 로 처리
'),
+ (1091, '비닐코팅종이', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1091.webp', NULL),
+ (1092, '비디오카메라(캠코더)', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1092.webp', NULL),
+ (1093, '비디오테이프', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1093.webp',
+ '내부 필름은 분리하여 종량제봉투 로 배출
'),
+ (1094, '빗, 헤어브러시', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1094.webp',
+ '재질에 맞게 배출하되 나무 빗 등은 종량제봉투 로 배출
'),
+ (1095, '빨대', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1095.webp', NULL),
+ (1096, '사다리', '생활폐기물/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1096.webp',
+ '대형폐기물 또는 고철 로 배출
'),
+ (1097, '사인펜', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1097.webp', NULL),
+ (1098, '사진', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1098.webp', NULL),
+ (1099, '사진인화지', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1099.webp', NULL),
+ (1100, '삽', '생활폐기물/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1100.webp',
+ '대형폐기물 또는 재질에 맞게 배출
'),
+ (1101, '상한 음식', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1101.webp', NULL),
+ (1102, '생선(먹고 남은)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1102.webp',
+ '생선뼈는 종량제봉투 에 버리고, 나머지는 음식물 로 배출
'),
+ (1103, '샤프펜슬', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1103.webp', NULL),
+ (1104, '샴푸용기', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1104.webp',
+ '내부를 헹구고 플라스틱 으로 배출
'),
+ (1105, '서랍장', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1105.webp', NULL),
+ (1106, '서류봉투(갈색 종이)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1106.webp', NULL),
+ (1107, '선풍기', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1107.webp', NULL),
+ (1108, '성냥', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1108.webp',
+ '물에 적신 후 종량제봉투 로 배출
'),
+ (1109, '세면대', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1109.webp', NULL),
+ (1110, '세탁기', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1110.webp', NULL),
+ (1111, '셔틀콕(배드민턴공, 깃털공)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1111.webp', NULL),
+ (1112, '소스용기', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1112.webp',
+ '내부를 헹구고 플라스틱 또는 재질에 맞게 배출
'),
+ (1113, '손목 시계', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1113.webp',
+ '종량제종투 에 담을 수 없는 경우 대형폐기물 로 처리, 건전지는 분리하여 전용수거함 으로 배출
'),
+ (1114, '솜', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1114.webp', NULL),
+ (1115, '솜이불', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1115.webp', NULL),
+ (1116, '송곳', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1116.webp', NULL),
+ (1117, '수세미', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1117.webp', NULL),
+ (1118, '수조, 수족관', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1118.webp', NULL),
+ (1119, '수첩', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1119.webp',
+ '종이류 또는 재질에 맞게 배출
'),
+ (1120, '숯', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1120.webp', NULL),
+ (1121, '스노우보드', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1121.webp', NULL),
+ (1122, '스캐너', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1122.webp', NULL),
+ (1123, '스케이트보드', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1123.webp', NULL),
+ (1124, '스키용구류', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1124.webp', NULL),
+ (1125, '스탠드', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1125.webp', NULL),
+ (1126, '스폰지', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1126.webp', NULL),
+ (1127, '스프레이, 부탄가스', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1127.webp', '금속캔(부탄가스통) 배출방법 참고
'),
+ (1128, '스피커', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1128.webp', NULL),
+ (1129, '식기세척기(식기건조기)', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1129.webp', NULL),
+ (1130, '식물, 나무', '생활폐기물/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1130.webp',
+ '종량제봉투 에 담을 수 없는 경우 대형폐기물 로 처리
'),
+ (1131, '식용유용기(플라스틱)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1131.webp',
+ '모두 사용 후 플라스틱 으로 배출
'),
+ (1132, '신문지', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1132.webp', NULL),
+ (1133, '신발', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1133.webp', NULL),
+ (1134, '신발장', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1134.webp', NULL),
+ (1135, '싱크대', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1135.webp', NULL),
+ (1136, '쌀통', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1136.webp', NULL),
+ (1137, '쌀포대', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1137.webp',
+ '종이류 또는 재질에 맞게 배출
'),
+ (1138, '쓰레받기', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1138.webp',
+ '플라스틱 < 가정용 플라스틱 쓰레받기고철 < 고철 쓰레받기
'),
+ (1139, '아기욕조, 아기침대', '생활폐기물/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1139.webp', NULL),
+ (1140, '아령', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1140.webp',
+ '고철 또는 재질에 맞게 배출
'),
+ (1141, '아이스팩', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1141.webp', NULL),
+ (1142, '악기', '생활폐기물/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1142.webp',
+ '종량제봉투 에 담을 수 없는 경우 대형페기물 로 배출 ※악기는 폐가전 제품 무상방문 수거 대상품목이 아님
'),
+ (1143, '압력솓', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1143.webp', NULL),
+ (1144, '애완동물 용변 시트', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1144.webp', NULL),
+ (1145, '애완동물 음식캔', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1145.webp',
+ '금속캔 으로 배출
'),
+ (1146, '애완동물집, 운반케이스', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1146.webp', NULL),
+ (1147, '액자', '생활폐기물/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1147.webp',
+ '종량제봉투 에 담을 수 없는 경우 대형폐기물 로 처리
'),
+ (1148, '앨범', '생활폐기물/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1148.webp',
+ '종량제봉투 에 담을 수 없는 경우 대형폐기물 로 처리
'),
+ (1149, '야구공', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1149.webp', NULL),
+ (1150, '야구글러브', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1150.webp', NULL),
+ (1151, '야구배트', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1151.webp',
+ '재질에 맞게 배출 또는 종량제봉투 로 배출
'),
+ (1152, '약 종류', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1152.webp',
+ '약국, 보건소 등의 전용수거함 으로 배출
'),
+ (1153, '양초', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1153.webp', NULL),
+ (1154, '에어매트', '생활폐기물/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1154.webp',
+ '종량제봉투 에 담을 수 없는 경우 대형폐기물 로 처리
'),
+ (1155, '에어컨', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1155.webp', NULL),
+ (1156, '엔진오일', '기타폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1156.webp',
+ '전문처리시설 (카센터 등)로 배출
'),
+ (1157, '여행가방(트렁크)', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1157.webp', NULL),
+ (1158, '역기', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1158.webp', NULL),
+ (1159, '연필, 색연필', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1159.webp', NULL),
+ (1160, '연필깎이', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1160.webp', NULL),
+ (1161, '오디오세트', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1161.webp', NULL),
+ (1162, '오렌지껍질', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1162.webp', NULL),
+ (1163, '온풍기', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1163.webp', NULL),
+ (1164, '옷걸이(세탁소 흰색 철사)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1164.webp', NULL),
+ (1165, '와이퍼', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1165.webp', NULL),
+ (1166, '와인셀러', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1166.webp', NULL),
+ (1167, '완충재(뽁뽁이)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1167.webp', NULL),
+ (1168, '요가매트', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1168.webp', NULL),
+ (1169, '우산', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1169.webp',
+ '뼈대와 비닐을 분리하여, 각각의 분리수거함으로 배출, 분리가 어렵다면 종량제봉투 로 배출
'),
+ (1170, '유리병', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1170.webp', '유리병류 로 배출
'),
+ (1171, '유리병뚜껑(철, 알루미늄)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1171.webp', NULL),
+ (1172, '유리판, 유리제품', '생활폐기물/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1172.webp',
+ '불연성폐기물 또는 대형폐기물 로 배출
'),
+ (1173, '유모차', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1173.webp', NULL),
+ (1174, '윤활유', '기타폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1174.webp', '구입처와 상담 후 배출
'),
+ (1175, '응접세트', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1175.webp', NULL),
+ (1176, '의류', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1176.webp',
+ '의류 및 원단류 로 배출
'),
+ (1177, '의류건조대', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1177.webp',
+ '고철 , 플라스틱 등 재질에 맞게 배출
'),
+ (1178, '의자', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1178.webp', NULL),
+ (1179, '이불', '생활폐기물/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1179.webp',
+ '종량제종투 에 담을 수 없는 경우 대형폐기물 로 배출
'),
+ (1180, '인형류', '생활폐기물/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1180.webp',
+ '종량제종투 에 담을 수 없는 경우 대형폐기물 로 배출
'),
+ (1181, '자동차 부품', '기타폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1181.webp', '중고센터, 판매처 등과 상담하여 처리
'),
+ (1182, '자루걸레', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1182.webp', NULL),
+ (1183, '자석', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1183.webp', NULL),
+ (1184, '자전거', '기타폐기물/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1184.webp',
+ '대형폐기물 로 처리하거나, 중고센터 등과 상담하여 처리
'),
+ (1185, '잡지', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1185.webp', NULL),
+ (1186, '장난감류', '생활폐기물/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1186.webp',
+ '크기에 따라 대형폐기물 또는 재질에 맞게 배출 (여러재질이 섞인 경우 종량제봉투 로 배출)
'),
+ (1187, '장롱', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1187.webp', NULL),
+ (1188, '장식장', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1188.webp', NULL),
+ (1189, '장판', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1189.webp', NULL),
+ (1190, '재떨이', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1190.webp',
+ '불연성-종량제 < 도자기·유리재떨이고철 < 금속류 재떨이
'),
+ (1191, '전기밥솔', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1191.webp', NULL),
+ (1192, '전기비데', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1192.webp', NULL),
+ (1193, '전기오븐레인지', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1193.webp', NULL),
+ (1194, '전기코드류', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1194.webp', NULL),
+ (1195, '전기포트', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1195.webp', NULL),
+ (1196, '전단지', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1196.webp', NULL),
+ (1197, '전동칫솔', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1197.webp', NULL),
+ (1198, '전자레인지', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1198.webp', NULL),
+ (1199, '전자사전(전자수첩)', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1199.webp', NULL),
+ (1200, '전자피아노', '생활폐기물/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1200.webp',
+ '종량제봉투에 담을 수 없는 경우 대형폐기물로 처리 ※악기는 폐가전 제품 무상방문 수거 대상품목이 아님
'),
+ (1201, '전지', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1201.webp',
+ '전용수거함 으로 배출
'),
+ (1202, '전축', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1202.webp', NULL),
+ (1203, '전화기(팩스포함)', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1203.webp', NULL),
+ (1204, '전화번호부', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1204.webp', NULL),
+ (1205, '접착제(본드 등)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1205.webp', NULL),
+ (1206, '정기장판', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1206.webp',
+ '대형폐기물 로 배출 (전기이불담요 포함)
'),
+ (1207, '정수기', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1207.webp',
+ '대형가전 으로 배출
'),
+ (1208, '젖꼭지(아기용품)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1208.webp', NULL),
+ (1209, '젖병(아기용품)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1209.webp',
+ '젖병의 몸체와 윗부분의 젖꼭지는 분리하여 재질에 맞게 배출
'),
+ (1210, '조각칼', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1210.webp',
+ '수거원이 다치지 않도록 종이 등으로 감싸서 종량제봉투 로 배출
'),
+ (1211, '종이기저귀', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1211.webp', NULL),
+ (1212, '종이상자', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1212.webp', NULL),
+ (1213, '종이심', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1213.webp', NULL),
+ (1214, '종이조각', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1214.webp', NULL),
+ (1215, '종이팩(우유팩 등)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1215.webp',
+ '내부에 알루미늄박이 붙어 있다면 종량제봉투 로 배출
'),
+ (1216, '주전자(철, 알루미늄)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1216.webp',
+ '플라스틱 뚜껑 등은 돌려서 제거한 후 고철 로 배출
'),
+ (1217, '줄자', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1217.webp',
+ '재질에 맞게 배출 또는 종량제봉투 로 배출
'),
+ (1218, '지우개', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1218.webp', NULL),
+ (1219, '진열대', '생활폐기물/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1219.webp', NULL),
+ (1220, '차 찌꺼기', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1220.webp', NULL),
+ (1221, '찬장', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1221.webp', NULL),
+ (1222, '찻잔(도자기류)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1222.webp', NULL),
+ (1223, '책', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1223.webp', NULL),
+ (1224, '책상, 책장', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1224.webp', NULL),
+ (1225, '천체망원경', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1225.webp', NULL),
+ (1226, '철사', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1226.webp', NULL),
+ (1227, '철판(가정요리용)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1227.webp', NULL),
+ (1228, '청소기', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1228.webp', NULL),
+ (1229, '체온계(건전지, 디지털)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1229.webp',
+ '건전지는 분리하여 전용수거함 으로 배출
'),
+ (1230, '체중계', '생활폐기물/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1230.webp',
+ '종량제종투 에 담을 수 없는 경우 대형폐기물 로 배출
'),
+ (1231, '축구공', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1231.webp', NULL),
+ (1232, '충전식전지', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1232.webp',
+ '전용수거함 으로 배출
'),
+ (1233, '치약용기', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1233.webp',
+ '플라스틱 또는 재질에 맞게 배출
'),
+ (1234, '치킨박스', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1234.webp',
+ '기름에 오염된 내부 종이는 종량제봉투 로 배출
'),
+ (1235, '침구류(이불, 베개 등)', '생활폐기물/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1235.webp',
+ '종량제종투 에 담을 수 없는 경우 대형폐기물 로 배출
'),
+ (1236, '침대', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1236.webp', NULL),
+ (1237, '칫솔', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1237.webp', NULL),
+ (1238, '카펫, 융단', '생활폐기물/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1238.webp',
+ '종량제종투 에 담을 수 없는 경우 대형폐기물 로 배출
'),
+ (1239, '캐비넷', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1239.webp', NULL),
+ (1240, '캔 따개', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1240.webp', NULL),
+ (1241, '캡(플라스틱 뚜껑)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1241.webp', NULL),
+ (1242, '커튼, 커튼 레일', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1242.webp',
+ '종량제종투 에 담을 수 없는 경우 대형폐기물 로 배출
'),
+ (1243, '커피메이커', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1243.webp', NULL),
+ (1244, '커피원두, 찌꺼기', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1244.webp', NULL),
+ (1245, '컴퓨터', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1245.webp', NULL),
+ (1246, '컵', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1246.webp',
+ '불연성-종량제 < 도자기·유리 컵고철 < 금속·비금속 컵플라스틱 < 플라스틱컵
'),
+ (1247, '코르크따개(와인따개)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1247.webp',
+ '수거원이 다치지 않도록 종이 등으로 감싸서 종량제봉투 로 배출재질에 맞게 해당 분리수거함으로 배출
'),
+ (1248, '코르크마개', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1248.webp', NULL),
+ (1249, '코팅된 종이', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1249.webp', NULL),
+ (1250, '콘센트', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1250.webp', NULL),
+ (1251, '콘텍트렌즈', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1251.webp', NULL),
+ (1252, '쿠션', '생활폐기물/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1252.webp',
+ '종량제종투 에 담을 수 없는 경우 대형폐기물 로 배출
'),
+ (1253, '크레용', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1253.webp', NULL),
+ (1254, '키보드(컴퓨터용)', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1254.webp', NULL),
+ (1255, '타이어', '기타폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1255.webp', '구입처와 상담 후 배출
'),
+ (1256, '탁상달력', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1256.webp',
+ '종이류 배출방법을 참고하여 배출
'),
+ (1257, '탈수기', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1257.webp', NULL),
+ (1258, '텐트', '생활폐기물/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1258.webp',
+ '종량제종투 에 담을 수 없는 경우 대형폐기물 로 배출
'),
+ (1259, '텔레비전(TV)', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1259.webp', NULL),
+ (1260, '토스터기', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1260.webp', NULL),
+ (1261, '톱', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1261.webp', NULL),
+ (1262, '튀김기름', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1262.webp',
+ '폐식용유 전용수거함 배출
'),
+ (1263, '틀니', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1263.webp', NULL),
+ (1264, '티백(녹차)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1264.webp', NULL),
+ (1265, '파인애플껍질', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1265.webp', NULL),
+ (1266, '팩스(복합기)', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1266.webp', NULL),
+ (1267, '페트병', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1267.webp', NULL),
+ (1268, '포스터, 포장지', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1268.webp',
+ '종이류 배출방법을 참고하여 배출
'),
+ (1269, '프린터', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1269.webp', NULL),
+ (1270, '피아노', '기타폐기물/대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1270.webp',
+ '대형폐기물 로 처리하거나, 중고센터등과 상담하여 처리
'),
+ (1271, '피자 세이버(피자 삼발이)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1271.webp', NULL),
+ (1272, '피자박스', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1272.webp',
+ '기름에 오염된 내부 종이는 종량제봉투 로 배출
'),
+ (1273, '필름(사진용)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1273.webp', NULL),
+ (1274, '핫팩', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1274.webp', NULL),
+ (1275, '항아리', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1275.webp',
+ '대형폐기물 로 처리
'),
+ (1276, '헝겁류', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1276.webp', NULL),
+ (1277, '헤드폰(헤드셋)', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1277.webp', NULL),
+ (1278, '헬멧', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1278.webp',
+ '종량제봉투 로 배출하되, 분리하여 재질에 맞게 배출
'),
+ (1279, '형광등', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1279.webp',
+ '전용수거함 으로 배출
'),
+ (1280, '호일(사용 후)', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1280.webp', NULL),
+ (1281, '화로', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1281.webp', NULL),
+ (1282, '화분, 화병', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1282.webp',
+ '불연성폐기물 로 배출 등 재질에 맞게 배출
'),
+ (1283, '화장대', '대형폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1283.webp', NULL),
+ (1284, '화장품냉장고', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1284.webp', NULL),
+ (1285, '후라이팬', '생활폐기물', 'https://ik.imagekit.io/blisgo/dictionary/1285.webp', NULL),
+ (1286, '휴대용 플레이어(MP3등)', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1286.webp', NULL),
+ (1287, '휴대전화', '폐가전제품', 'https://ik.imagekit.io/blisgo/dictionary/1287.webp',
+ '폐가전제품 으로 배출 ※우체국 보상판매 이용 가능
');
\ No newline at end of file
diff --git a/app/src/main/resources/db/migration/V4__insert_waste_categories.sql b/app/src/main/resources/db/migration/V4__insert_waste_categories.sql
new file mode 100644
index 0000000..9c3ea18
--- /dev/null
+++ b/app/src/main/resources/db/migration/V4__insert_waste_categories.sql
@@ -0,0 +1,356 @@
+INSERT INTO waste_categories (categories, waste_id)
+VALUES ('PAPER', 1001),
+ ('BULKY', 1002),
+ ('PAY_AS_YOU_GO_BAG', 1003),
+ ('HOME_APPLIANCES', 1004),
+ ('PAY_AS_YOU_GO_BAG', 1005),
+ ('SEPARATION_BY_MATERIAL', 1005),
+ ('BULKY', 1006),
+ ('NON_FLAMMABLE', 1007),
+ ('BULKY', 1007),
+ ('PAY_AS_YOU_GO_BAG', 1008),
+ ('PAY_AS_YOU_GO_BAG', 1009),
+ ('PAY_AS_YOU_GO_BAG', 1010),
+ ('PAPER', 1011),
+ ('PLASTIC', 1011),
+ ('BULKY', 1012),
+ ('PAY_AS_YOU_GO_BAG', 1013),
+ ('IRON', 1014),
+ ('HOME_APPLIANCES', 1015),
+ ('PAPER', 1016),
+ ('CLOTHES', 1017),
+ ('PAY_AS_YOU_GO_BAG', 1017),
+ ('IRON', 1018),
+ ('PLASTIC', 1018),
+ ('PAY_AS_YOU_GO_BAG', 1018),
+ ('NON_FLAMMABLE', 1019),
+ ('IRON', 1019),
+ ('PLASTIC', 1019),
+ ('PRO_FACILITY', 1020),
+ ('ONLY_BOX', 1021),
+ ('BULKY', 1022),
+ ('NON_FLAMMABLE', 1023),
+ ('PAY_AS_YOU_GO_BAG', 1024),
+ ('PAY_AS_YOU_GO_BAG', 1025),
+ ('BULKY', 1025),
+ ('IRON', 1026),
+ ('PAY_AS_YOU_GO_BAG', 1027),
+ ('PAY_AS_YOU_GO_BAG', 1028),
+ ('BULKY', 1029),
+ ('HOME_APPLIANCES', 1030),
+ ('BULKY', 1030),
+ ('IRON', 1031),
+ ('PAY_AS_YOU_GO_BAG', 1031),
+ ('NON_FLAMMABLE', 1032),
+ ('PAY_AS_YOU_GO_BAG', 1033),
+ ('HOME_APPLIANCES', 1034),
+ ('HOME_APPLIANCES', 1035),
+ ('CAUTION', 1036),
+ ('HOME_APPLIANCES', 1037),
+ ('IRON', 1038),
+ ('PAY_AS_YOU_GO_BAG', 1038),
+ ('PAY_AS_YOU_GO_BAG', 1039),
+ ('PLASTIC', 1039),
+ ('NON_FLAMMABLE', 1040),
+ ('PAY_AS_YOU_GO_BAG', 1041),
+ ('HOME_APPLIANCES', 1042),
+ ('NON_FLAMMABLE', 1043),
+ ('HOME_APPLIANCES', 1044),
+ ('PAY_AS_YOU_GO_BAG', 1045),
+ ('CAUTION', 1045),
+ ('PAY_AS_YOU_GO_BAG', 1046),
+ ('BULKY', 1046),
+ ('PAY_AS_YOU_GO_BAG', 1047),
+ ('PAPER', 1048),
+ ('HOME_APPLIANCES', 1049),
+ ('PLASTIC', 1050),
+ ('PAY_AS_YOU_GO_BAG', 1051),
+ ('PAY_AS_YOU_GO_BAG', 1052),
+ ('PAY_AS_YOU_GO_BAG', 1053),
+ ('BULKY', 1054),
+ ('IRON', 1055),
+ ('NON_FLAMMABLE', 1056),
+ ('CLOTHES', 1057),
+ ('PLASTIC', 1058),
+ ('PAY_AS_YOU_GO_BAG', 1059),
+ ('PAY_AS_YOU_GO_BAG', 1060),
+ ('CAUTION', 1060),
+ ('PAY_AS_YOU_GO_BAG', 1061),
+ ('PAPER', 1062),
+ ('PAY_AS_YOU_GO_BAG', 1062),
+ ('PAY_AS_YOU_GO_BAG', 1063),
+ ('HOME_APPLIANCES', 1064),
+ ('CLOTHES', 1065),
+ ('PAY_AS_YOU_GO_BAG', 1065),
+ ('IRON', 1066),
+ ('SEPARATION_BY_MATERIAL', 1066),
+ ('BULKY', 1066),
+ ('PAY_AS_YOU_GO_BAG', 1067),
+ ('BULKY', 1067),
+ ('BULKY', 1068),
+ ('PAY_AS_YOU_GO_BAG', 1069),
+ ('PAY_AS_YOU_GO_BAG', 1070),
+ ('PROG', 1071),
+ ('PAY_AS_YOU_GO_BAG', 1072),
+ ('BULKY', 1072),
+ ('BULKY', 1073),
+ ('BULKY', 1074),
+ ('PAY_AS_YOU_GO_BAG', 1075),
+ ('PAPER', 1076),
+ ('NON_FLAMMABLE', 1077),
+ ('PAY_AS_YOU_GO_BAG', 1078),
+ ('NON_FLAMMABLE', 1079),
+ ('BULKY', 1080),
+ ('PAY_AS_YOU_GO_BAG', 1081),
+ ('HOME_APPLIANCES', 1082),
+ ('PAY_AS_YOU_GO_BAG', 1083),
+ ('PLASTIC', 1084),
+ ('PLASTIC', 1085),
+ ('IRON', 1086),
+ ('PAY_AS_YOU_GO_BAG', 1087),
+ ('BULKY', 1088),
+ ('VINYL', 1089),
+ ('PAY_AS_YOU_GO_BAG', 1090),
+ ('BULKY', 1090),
+ ('PAY_AS_YOU_GO_BAG', 1091),
+ ('HOME_APPLIANCES', 1092),
+ ('PLASTIC', 1093),
+ ('SEPARATION_BY_MATERIAL', 1094),
+ ('PAY_AS_YOU_GO_BAG', 1094),
+ ('PLASTIC', 1095),
+ ('IRON', 1096),
+ ('BULKY', 1096),
+ ('PAY_AS_YOU_GO_BAG', 1097),
+ ('PAY_AS_YOU_GO_BAG', 1098),
+ ('PAY_AS_YOU_GO_BAG', 1099),
+ ('SEPARATION_BY_MATERIAL', 1100),
+ ('BULKY', 1100),
+ ('PROG', 1101),
+ ('PROG', 1102),
+ ('PAY_AS_YOU_GO_BAG', 1102),
+ ('PAY_AS_YOU_GO_BAG', 1103),
+ ('PLASTIC', 1104),
+ ('BULKY', 1105),
+ ('PAPER', 1106),
+ ('HOME_APPLIANCES', 1107),
+ ('PAY_AS_YOU_GO_BAG', 1108),
+ ('BULKY', 1109),
+ ('HOME_APPLIANCES', 1110),
+ ('PAY_AS_YOU_GO_BAG', 1111),
+ ('PLASTIC', 1112),
+ ('SEPARATION_BY_MATERIAL', 1112),
+ ('PAY_AS_YOU_GO_BAG', 1113),
+ ('PAY_AS_YOU_GO_BAG', 1114),
+ ('BULKY', 1115),
+ ('PAY_AS_YOU_GO_BAG', 1116),
+ ('PAY_AS_YOU_GO_BAG', 1117),
+ ('BULKY', 1118),
+ ('PAPER', 1119),
+ ('SEPARATION_BY_MATERIAL', 1119),
+ ('PAY_AS_YOU_GO_BAG', 1120),
+ ('BULKY', 1121),
+ ('HOME_APPLIANCES', 1122),
+ ('BULKY', 1123),
+ ('BULKY', 1124),
+ ('HOME_APPLIANCES', 1125),
+ ('PAY_AS_YOU_GO_BAG', 1126),
+ ('METAL', 1127),
+ ('CAUTION', 1127),
+ ('HOME_APPLIANCES', 1128),
+ ('HOME_APPLIANCES', 1129),
+ ('PAY_AS_YOU_GO_BAG', 1130),
+ ('BULKY', 1130),
+ ('PLASTIC', 1131),
+ ('PAPER', 1132),
+ ('PAY_AS_YOU_GO_BAG', 1133),
+ ('BULKY', 1134),
+ ('BULKY', 1135),
+ ('BULKY', 1136),
+ ('PAPER', 1137),
+ ('SEPARATION_BY_MATERIAL', 1137),
+ ('PLASTIC', 1138),
+ ('IRON', 1138),
+ ('SEPARATION_BY_MATERIAL', 1139),
+ ('BULKY', 1139),
+ ('IRON', 1140),
+ ('SEPARATION_BY_MATERIAL', 1140),
+ ('PAY_AS_YOU_GO_BAG', 1141),
+ ('PAY_AS_YOU_GO_BAG', 1142),
+ ('BULKY', 1142),
+ ('IRON', 1143),
+ ('PAY_AS_YOU_GO_BAG', 1144),
+ ('METAL', 1145),
+ ('CAUTION', 1145),
+ ('SEPARATION_BY_MATERIAL', 1146),
+ ('PAY_AS_YOU_GO_BAG', 1147),
+ ('BULKY', 1147),
+ ('PAY_AS_YOU_GO_BAG', 1148),
+ ('BULKY', 1148),
+ ('PAY_AS_YOU_GO_BAG', 1149),
+ ('PAY_AS_YOU_GO_BAG', 1150),
+ ('SEPARATION_BY_MATERIAL', 1151),
+ ('PAY_AS_YOU_GO_BAG', 1151),
+ ('ONLY_BOX', 1152),
+ ('PAY_AS_YOU_GO_BAG', 1153),
+ ('PAY_AS_YOU_GO_BAG', 1154),
+ ('BULKY', 1154),
+ ('HOME_APPLIANCES', 1155),
+ ('PRO_FACILITY', 1156),
+ ('BULKY', 1157),
+ ('IRON', 1158),
+ ('PAY_AS_YOU_GO_BAG', 1159),
+ ('PAY_AS_YOU_GO_BAG', 1160),
+ ('HOME_APPLIANCES', 1161),
+ ('PROG', 1162),
+ ('HOME_APPLIANCES', 1163),
+ ('IRON', 1164),
+ ('SEPARATION_BY_MATERIAL', 1165),
+ ('HOME_APPLIANCES', 1166),
+ ('VINYL', 1167),
+ ('PAY_AS_YOU_GO_BAG', 1168),
+ ('PAY_AS_YOU_GO_BAG', 1169),
+ ('GLASS', 1170),
+ ('CAUTION', 1170),
+ ('IRON', 1171),
+ ('NON_FLAMMABLE', 1172),
+ ('BULKY', 1172),
+ ('BULKY', 1173),
+ ('PRO_FACILITY', 1174),
+ ('BULKY', 1175),
+ ('CLOTHES', 1176),
+ ('IRON', 1177),
+ ('PLASTIC', 1177),
+ ('SEPARATION_BY_MATERIAL', 1177),
+ ('BULKY', 1178),
+ ('PAY_AS_YOU_GO_BAG', 1179),
+ ('BULKY', 1179),
+ ('PAY_AS_YOU_GO_BAG', 1180),
+ ('BULKY', 1180),
+ ('PRO_FACILITY', 1181),
+ ('BULKY', 1182),
+ ('PAY_AS_YOU_GO_BAG', 1183),
+ ('PRO_FACILITY', 1184),
+ ('BULKY', 1184),
+ ('PAPER', 1185),
+ ('SEPARATION_BY_MATERIAL', 1186),
+ ('PAY_AS_YOU_GO_BAG', 1186),
+ ('BULKY', 1186),
+ ('BULKY', 1187),
+ ('BULKY', 1188),
+ ('BULKY', 1189),
+ ('NON_FLAMMABLE', 1190),
+ ('IRON', 1190),
+ ('HOME_APPLIANCES', 1191),
+ ('HOME_APPLIANCES', 1192),
+ ('HOME_APPLIANCES', 1193),
+ ('PAY_AS_YOU_GO_BAG', 1194),
+ ('HOME_APPLIANCES', 1195),
+ ('PAPER', 1196),
+ ('PAY_AS_YOU_GO_BAG', 1197),
+ ('HOME_APPLIANCES', 1198),
+ ('HOME_APPLIANCES', 1199),
+ ('PAY_AS_YOU_GO_BAG', 1200),
+ ('BULKY', 1200),
+ ('ONLY_BOX', 1201),
+ ('HOME_APPLIANCES', 1202),
+ ('HOME_APPLIANCES', 1203),
+ ('PAPER', 1204),
+ ('PAY_AS_YOU_GO_BAG', 1205),
+ ('BULKY', 1206),
+ ('HOME_APPLIANCES', 1207),
+ ('PAY_AS_YOU_GO_BAG', 1208),
+ ('PLASTIC', 1209),
+ ('SEPARATION_BY_MATERIAL', 1209),
+ ('PAY_AS_YOU_GO_BAG', 1210),
+ ('PAY_AS_YOU_GO_BAG', 1211),
+ ('PAPER', 1212),
+ ('PAPER', 1213),
+ ('PAPER', 1214),
+ ('CARTON', 1215),
+ ('IRON', 1216),
+ ('SEPARATION_BY_MATERIAL', 1217),
+ ('PAY_AS_YOU_GO_BAG', 1217),
+ ('PAY_AS_YOU_GO_BAG', 1218),
+ ('SEPARATION_BY_MATERIAL', 1219),
+ ('BULKY', 1219),
+ ('PAY_AS_YOU_GO_BAG', 1220),
+ ('BULKY', 1221),
+ ('NON_FLAMMABLE', 1222),
+ ('PAPER', 1223),
+ ('BULKY', 1224),
+ ('BULKY', 1225),
+ ('IRON', 1226),
+ ('IRON', 1227),
+ ('HOME_APPLIANCES', 1228),
+ ('PAY_AS_YOU_GO_BAG', 1229),
+ ('PAY_AS_YOU_GO_BAG', 1230),
+ ('BULKY', 1230),
+ ('PAY_AS_YOU_GO_BAG', 1231),
+ ('ONLY_BOX', 1232),
+ ('PLASTIC', 1233),
+ ('SEPARATION_BY_MATERIAL', 1233),
+ ('PAPER', 1234),
+ ('PAY_AS_YOU_GO_BAG', 1235),
+ ('BULKY', 1235),
+ ('BULKY', 1236),
+ ('PAY_AS_YOU_GO_BAG', 1237),
+ ('PAY_AS_YOU_GO_BAG', 1238),
+ ('BULKY', 1238),
+ ('BULKY', 1239),
+ ('IRON', 1240),
+ ('PLASTIC', 1241),
+ ('PAY_AS_YOU_GO_BAG', 1242),
+ ('BULKY', 1242),
+ ('HOME_APPLIANCES', 1243),
+ ('PAY_AS_YOU_GO_BAG', 1244),
+ ('HOME_APPLIANCES', 1245),
+ ('NON_FLAMMABLE', 1246),
+ ('IRON', 1246),
+ ('PLASTIC', 1246),
+ ('PAY_AS_YOU_GO_BAG', 1247),
+ ('SEPARATION_BY_MATERIAL', 1247),
+ ('PAY_AS_YOU_GO_BAG', 1248),
+ ('PAY_AS_YOU_GO_BAG', 1249),
+ ('PAY_AS_YOU_GO_BAG', 1250),
+ ('PAY_AS_YOU_GO_BAG', 1251),
+ ('PAY_AS_YOU_GO_BAG', 1252),
+ ('BULKY', 1252),
+ ('PAY_AS_YOU_GO_BAG', 1253),
+ ('HOME_APPLIANCES', 1254),
+ ('PRO_FACILITY', 1255),
+ ('PAPER', 1256),
+ ('HOME_APPLIANCES', 1257),
+ ('PAY_AS_YOU_GO_BAG', 1258),
+ ('BULKY', 1258),
+ ('HOME_APPLIANCES', 1259),
+ ('HOME_APPLIANCES', 1260),
+ ('IRON', 1261),
+ ('ONLY_BOX', 1262),
+ ('PAY_AS_YOU_GO_BAG', 1263),
+ ('PAY_AS_YOU_GO_BAG', 1264),
+ ('PAY_AS_YOU_GO_BAG', 1265),
+ ('HOME_APPLIANCES', 1266),
+ ('PLASTIC', 1267),
+ ('PAPER', 1268),
+ ('HOME_APPLIANCES', 1269),
+ ('BULKY', 1270),
+ ('PRO_FACILITY', 1270),
+ ('PLASTIC', 1271),
+ ('PAPER', 1272),
+ ('PAY_AS_YOU_GO_BAG', 1273),
+ ('PAY_AS_YOU_GO_BAG', 1274),
+ ('BULKY', 1275),
+ ('PAY_AS_YOU_GO_BAG', 1276),
+ ('HOME_APPLIANCES', 1277),
+ ('PAY_AS_YOU_GO_BAG', 1278),
+ ('SEPARATION_BY_MATERIAL', 1278),
+ ('ONLY_BOX', 1279),
+ ('PAY_AS_YOU_GO_BAG', 1280),
+ ('BULKY', 1281),
+ ('NON_FLAMMABLE', 1282),
+ ('SEPARATION_BY_MATERIAL', 1282),
+ ('BULKY', 1283),
+ ('HOME_APPLIANCES', 1284),
+ ('IRON', 1285),
+ ('HOME_APPLIANCES', 1286),
+ ('HOME_APPLIANCES', 1287);
\ No newline at end of file
diff --git a/app/src/main/resources/db/migration/V5__insert_account.sql b/app/src/main/resources/db/migration/V5__insert_account.sql
new file mode 100644
index 0000000..e69de29
diff --git a/app/src/main/resources/db/migration/V6__insert_board_values.sql b/app/src/main/resources/db/migration/V6__insert_board_values.sql
new file mode 100644
index 0000000..e69de29
diff --git a/app/src/main/resources/db/migration/V7__insert_reply_values.sql b/app/src/main/resources/db/migration/V7__insert_reply_values.sql
new file mode 100644
index 0000000..e69de29
diff --git a/autoexecute.bat b/autoexecute.bat
new file mode 100644
index 0000000..9c765c5
--- /dev/null
+++ b/autoexecute.bat
@@ -0,0 +1,15 @@
+@echo off
+
+echo Current directory: %cd%
+
+cd .\infrastructure\internal\src\main\resources
+
+if exist static (
+ rmdir /s /q static
+)
+
+if not exist static (
+ mkdir static
+)
+
+move /y assets .\static\assets
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..7299852
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,60 @@
+buildscript {
+ repositories {
+ mavenCentral()
+ }
+
+ dependencies {
+ classpath("org.springframework.boot:spring-boot-gradle-plugin:+")
+ }
+}
+
+subprojects {
+ if (name == 'infrastructure' || name == 'interfaces') return
+
+ group = 'blisgo'
+ version = 'V4'
+
+ apply {
+ plugin 'java'
+ plugin 'org.springframework.boot'
+ plugin 'io.spring.dependency-management'
+ }
+
+ repositories {
+ mavenCentral()
+ }
+
+ dependencies {
+ developmentOnly 'org.springframework.boot:spring-boot-devtools'
+
+ // componentScan
+ implementation 'org.springframework.boot:spring-boot-autoconfigure'
+
+ // configuration processor
+ annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
+
+ // minimal web
+ implementation 'org.springframework.boot:spring-boot-starter:+'
+
+ // spring-data-commons(ex. pageable)
+ implementation 'org.springframework.data:spring-data-commons'
+
+ // ddd hexagonal
+ implementation 'org.jmolecules:jmolecules-hexagonal-architecture:+'
+
+ // lombok
+ implementation 'org.projectlombok:lombok'
+ annotationProcessor 'org.projectlombok:lombok'
+
+ // modelmapper
+ implementation 'org.modelmapper:modelmapper:+'
+ implementation 'org.modelmapper:modelmapper-module-record:+'
+
+ // docker
+ developmentOnly 'org.springframework.boot:spring-boot-docker-compose'
+ }
+
+ test {
+ useJUnitPlatform()
+ }
+}
\ No newline at end of file
diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle
new file mode 100644
index 0000000..71d3d85
--- /dev/null
+++ b/buildSrc/build.gradle
@@ -0,0 +1,9 @@
+repositories {
+ mavenCentral()
+}
+
+java {
+ toolchain {
+ languageVersion = JavaLanguageVersion.of(21)
+ }
+}
\ No newline at end of file
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..2261322
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,18 @@
+services:
+ mysql:
+ image: mysql:latest
+ container_name: blisgo-db
+ environment:
+ - MYSQL_DATABASE=blisgo
+ - MYSQL_ROOT_PASSWORD=root
+ ports:
+ - 3306:3306
+
+ redis:
+ image: redis:latest
+ container_name: blisgo-documents
+ ports:
+ - 6379:6379
+
+networks:
+ network:
\ No newline at end of file
diff --git a/domain/build.gradle b/domain/build.gradle
new file mode 100644
index 0000000..dfa8623
--- /dev/null
+++ b/domain/build.gradle
@@ -0,0 +1,5 @@
+bootJar.enabled = false
+jar.enabled = true
+
+dependencies {
+}
diff --git a/domain/src/main/java/blisgo/domain/DomainRoot.java b/domain/src/main/java/blisgo/domain/DomainRoot.java
new file mode 100644
index 0000000..704bf95
--- /dev/null
+++ b/domain/src/main/java/blisgo/domain/DomainRoot.java
@@ -0,0 +1,7 @@
+package blisgo.domain;
+
+import org.springframework.context.annotation.ComponentScan;
+
+@ComponentScan(basePackageClasses = DomainRoot.class)
+public interface DomainRoot {
+}
diff --git a/domain/src/main/java/blisgo/domain/common/Author.java b/domain/src/main/java/blisgo/domain/common/Author.java
new file mode 100644
index 0000000..441d956
--- /dev/null
+++ b/domain/src/main/java/blisgo/domain/common/Author.java
@@ -0,0 +1,15 @@
+package blisgo.domain.common;
+
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@Getter
+@AllArgsConstructor(staticName = "of")
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+public class Author {
+ private String email;
+ private String name;
+ private Picture picture;
+}
\ No newline at end of file
diff --git a/domain/src/main/java/blisgo/domain/common/Content.java b/domain/src/main/java/blisgo/domain/common/Content.java
new file mode 100644
index 0000000..f6949f4
--- /dev/null
+++ b/domain/src/main/java/blisgo/domain/common/Content.java
@@ -0,0 +1,19 @@
+package blisgo.domain.common;
+
+import lombok.*;
+
+@Builder
+@Getter
+@AllArgsConstructor(staticName = "of")
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+public class Content {
+ private String text;
+ private Picture thumbnail;
+ private String preview;
+
+ public static Content of(String text) {
+ return Content.builder()
+ .text(text)
+ .build();
+ }
+}
diff --git a/domain/src/main/java/blisgo/domain/common/Hashtag.java b/domain/src/main/java/blisgo/domain/common/Hashtag.java
new file mode 100644
index 0000000..9cb77e3
--- /dev/null
+++ b/domain/src/main/java/blisgo/domain/common/Hashtag.java
@@ -0,0 +1,13 @@
+package blisgo.domain.common;
+
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@Getter
+@AllArgsConstructor(staticName = "of")
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+public class Hashtag {
+ private String content;
+}
diff --git a/domain/src/main/java/blisgo/domain/common/Picture.java b/domain/src/main/java/blisgo/domain/common/Picture.java
new file mode 100644
index 0000000..565631c
--- /dev/null
+++ b/domain/src/main/java/blisgo/domain/common/Picture.java
@@ -0,0 +1,13 @@
+package blisgo.domain.common;
+
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@Getter
+@AllArgsConstructor(staticName = "of")
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+public class Picture {
+ private String url;
+}
diff --git a/domain/src/main/java/blisgo/domain/community/Post.java b/domain/src/main/java/blisgo/domain/community/Post.java
new file mode 100644
index 0000000..cca7164
--- /dev/null
+++ b/domain/src/main/java/blisgo/domain/community/Post.java
@@ -0,0 +1,51 @@
+package blisgo.domain.community;
+
+import blisgo.domain.common.Author;
+import blisgo.domain.common.Content;
+import blisgo.domain.community.vo.PostId;
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+
+import java.time.LocalDateTime;
+
+@Getter
+@SuperBuilder(toBuilder = true)
+@AllArgsConstructor(access = AccessLevel.PROTECTED)
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+public class Post {
+ private PostId postId;
+ private String title;
+ private Author author;
+ private Content content;
+ private long views;
+ private long likes;
+ private long replies;
+ private LocalDateTime createdDate;
+ private LocalDateTime modifiedDate;
+
+ public static Post create(Long postId, String title, String content) {
+ return Post.builder()
+ .postId(PostId.of(postId))
+ .title(title)
+ .content(Content.of(content))
+ .build();
+ }
+
+ public static Post create(String title, String content) {
+ return Post.builder()
+ .title(title)
+ .content(Content.of(content))
+ .build();
+ }
+
+ public void likePost() {
+ this.likes++;
+ }
+
+ public boolean isAuthor(String email) {
+ return this.author.email().equals(email);
+ }
+}
diff --git a/domain/src/main/java/blisgo/domain/community/Reply.java b/domain/src/main/java/blisgo/domain/community/Reply.java
new file mode 100644
index 0000000..de76ebd
--- /dev/null
+++ b/domain/src/main/java/blisgo/domain/community/Reply.java
@@ -0,0 +1,25 @@
+package blisgo.domain.community;
+
+import blisgo.domain.common.Author;
+import blisgo.domain.community.vo.PostId;
+import blisgo.domain.community.vo.ReplyId;
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+
+import java.time.LocalDateTime;
+
+@Getter
+@SuperBuilder(toBuilder = true)
+@AllArgsConstructor(access = AccessLevel.PROTECTED)
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+public class Reply {
+ private ReplyId replyId;
+ private PostId postId;
+ private Author author;
+ private String content;
+ private LocalDateTime createdDate;
+ private LocalDateTime modifiedDate;
+}
diff --git a/domain/src/main/java/blisgo/domain/community/vo/PostId.java b/domain/src/main/java/blisgo/domain/community/vo/PostId.java
new file mode 100644
index 0000000..72a8d48
--- /dev/null
+++ b/domain/src/main/java/blisgo/domain/community/vo/PostId.java
@@ -0,0 +1,13 @@
+package blisgo.domain.community.vo;
+
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@Getter
+@AllArgsConstructor(staticName = "of")
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+public class PostId {
+ private Long id;
+}
\ No newline at end of file
diff --git a/domain/src/main/java/blisgo/domain/community/vo/ReplyId.java b/domain/src/main/java/blisgo/domain/community/vo/ReplyId.java
new file mode 100644
index 0000000..c737842
--- /dev/null
+++ b/domain/src/main/java/blisgo/domain/community/vo/ReplyId.java
@@ -0,0 +1,13 @@
+package blisgo.domain.community.vo;
+
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@Getter
+@AllArgsConstructor(staticName = "of")
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+public class ReplyId {
+ private Long id;
+}
\ No newline at end of file
diff --git a/domain/src/main/java/blisgo/domain/dictionary/Dogam.java b/domain/src/main/java/blisgo/domain/dictionary/Dogam.java
new file mode 100644
index 0000000..ca6b2c8
--- /dev/null
+++ b/domain/src/main/java/blisgo/domain/dictionary/Dogam.java
@@ -0,0 +1,31 @@
+package blisgo.domain.dictionary;
+
+import blisgo.domain.dictionary.vo.DogamId;
+import blisgo.domain.dictionary.vo.WasteId;
+import blisgo.domain.member.vo.MemberId;
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+
+import java.time.LocalDateTime;
+
+@Getter
+@SuperBuilder(toBuilder = true)
+@AllArgsConstructor(access = AccessLevel.PROTECTED)
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+public class Dogam {
+ private DogamId dogamId;
+ private Waste waste;
+ private LocalDateTime createdDate;
+ private LocalDateTime modifiedDate;
+
+ public static Dogam create(MemberId memberId, WasteId wasteId) {
+ return Dogam.builder()
+ .dogamId(DogamId.of(memberId, wasteId))
+ .waste(Waste.builder().wasteId(wasteId).build())
+ .createdDate(LocalDateTime.now())
+ .build();
+ }
+}
diff --git a/domain/src/main/java/blisgo/domain/dictionary/Waste.java b/domain/src/main/java/blisgo/domain/dictionary/Waste.java
new file mode 100644
index 0000000..e33d18a
--- /dev/null
+++ b/domain/src/main/java/blisgo/domain/dictionary/Waste.java
@@ -0,0 +1,30 @@
+package blisgo.domain.dictionary;
+
+import blisgo.domain.common.Picture;
+import blisgo.domain.dictionary.vo.Category;
+import blisgo.domain.dictionary.vo.WasteId;
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Getter
+@SuperBuilder(toBuilder = true)
+@AllArgsConstructor(access = AccessLevel.PROTECTED)
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+public class Waste {
+ private WasteId wasteId;
+ private String name;
+ private String type;
+ private Picture picture;
+ private String treatment;
+ private Short popularity;
+ private Long views;
+ private List categories;
+ private LocalDateTime createdDate;
+ private LocalDateTime modifiedDate;
+}
\ No newline at end of file
diff --git a/domain/src/main/java/blisgo/domain/dictionary/vo/Category.java b/domain/src/main/java/blisgo/domain/dictionary/vo/Category.java
new file mode 100644
index 0000000..6cdc558
--- /dev/null
+++ b/domain/src/main/java/blisgo/domain/dictionary/vo/Category.java
@@ -0,0 +1,29 @@
+package blisgo.domain.dictionary.vo;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@Getter
+@AllArgsConstructor
+public enum Category {
+ IRON("고철"),
+ METAL("금속캔"),
+ BULKY("대형"),
+ STYROFOAM("발포합성"),
+ NON_FLAMMABLE("불연성-종량제"),
+ VINYL("비닐"),
+ GLASS("유리병"),
+ PROG("음식물"),
+ CLOTHES("의류"),
+ ONLY_BOX("전용함"),
+ PAPER("종이"),
+ CARTON("종이팩"),
+ HOME_APPLIANCES("가전제품"),
+ PLASTIC("플라스틱"),
+ PAY_AS_YOU_GO_BAG("종량제봉투"),
+ SEPARATION_BY_MATERIAL("재질별분리"),
+ CAUTION("주의"),
+ PRO_FACILITY("전문시설");
+
+ final String tag;
+}
diff --git a/domain/src/main/java/blisgo/domain/dictionary/vo/DogamId.java b/domain/src/main/java/blisgo/domain/dictionary/vo/DogamId.java
new file mode 100644
index 0000000..3fd170b
--- /dev/null
+++ b/domain/src/main/java/blisgo/domain/dictionary/vo/DogamId.java
@@ -0,0 +1,13 @@
+package blisgo.domain.dictionary.vo;
+
+import blisgo.domain.member.vo.MemberId;
+import lombok.*;
+
+@Getter
+@AllArgsConstructor(staticName = "of")
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+@Builder(toBuilder = true)
+public class DogamId {
+ private MemberId memberId;
+ private WasteId wasteId;
+}
\ No newline at end of file
diff --git a/domain/src/main/java/blisgo/domain/dictionary/vo/Guide.java b/domain/src/main/java/blisgo/domain/dictionary/vo/Guide.java
new file mode 100644
index 0000000..bf05cfc
--- /dev/null
+++ b/domain/src/main/java/blisgo/domain/dictionary/vo/Guide.java
@@ -0,0 +1,16 @@
+package blisgo.domain.dictionary.vo;
+
+import blisgo.domain.common.Picture;
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@Getter
+@AllArgsConstructor(staticName = "of")
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+public class Guide {
+ private Category category;
+ private String content;
+ private Picture picture;
+}
diff --git a/domain/src/main/java/blisgo/domain/dictionary/vo/WasteId.java b/domain/src/main/java/blisgo/domain/dictionary/vo/WasteId.java
new file mode 100644
index 0000000..edf5080
--- /dev/null
+++ b/domain/src/main/java/blisgo/domain/dictionary/vo/WasteId.java
@@ -0,0 +1,13 @@
+package blisgo.domain.dictionary.vo;
+
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@Getter
+@AllArgsConstructor(staticName = "of")
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+public class WasteId {
+ private Long id;
+}
\ No newline at end of file
diff --git a/domain/src/main/java/blisgo/domain/member/Member.java b/domain/src/main/java/blisgo/domain/member/Member.java
new file mode 100644
index 0000000..f14c868
--- /dev/null
+++ b/domain/src/main/java/blisgo/domain/member/Member.java
@@ -0,0 +1,34 @@
+package blisgo.domain.member;
+
+import blisgo.domain.common.Picture;
+import blisgo.domain.member.vo.MemberId;
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+
+import java.time.LocalDateTime;
+
+@Getter
+@SuperBuilder(toBuilder = true)
+@AllArgsConstructor
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+public class Member {
+ private MemberId memberId;
+ private String name;
+ private String email;
+ private Picture picture;
+ private LocalDateTime createdDate;
+ private LocalDateTime modifiedDate;
+
+ public static Member create(String name, String email, String picture) {
+ return Member.builder()
+ .memberId(MemberId.of(email))
+ .name(name)
+ .email(email)
+ .picture(Picture.of(picture))
+ .build();
+ }
+
+}
\ No newline at end of file
diff --git a/domain/src/main/java/blisgo/domain/member/vo/MemberId.java b/domain/src/main/java/blisgo/domain/member/vo/MemberId.java
new file mode 100644
index 0000000..dc7994a
--- /dev/null
+++ b/domain/src/main/java/blisgo/domain/member/vo/MemberId.java
@@ -0,0 +1,18 @@
+package blisgo.domain.member.vo;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+import java.util.UUID;
+
+@Getter
+@AllArgsConstructor(staticName = "of")
+@NoArgsConstructor
+public class MemberId {
+ private UUID id;
+
+ public static MemberId of(String email) {
+ return new MemberId(UUID.nameUUIDFromBytes(email.getBytes()));
+ }
+}
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..d64cd49
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..a80b22c
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,7 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
+networkTimeout=10000
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..1aa94a4
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,249 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..93e3f59
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,92 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/infrastructure/external/build.gradle b/infrastructure/external/build.gradle
new file mode 100644
index 0000000..2ab9e26
--- /dev/null
+++ b/infrastructure/external/build.gradle
@@ -0,0 +1,13 @@
+bootJar.enabled = false
+jar.enabled = true
+
+description = 'redis 를 사용하여 세션관리'
+
+dependencies {
+ implementation project(':usecase')
+ implementation project(':infrastructure:internal')
+
+ implementation 'org.springframework.session:spring-session-data-redis'
+ implementation 'org.springframework.boot:spring-boot-starter-data-redis'
+ developmentOnly 'org.springframework.boot:spring-boot-docker-compose'
+}
\ No newline at end of file
diff --git a/infrastructure/external/src/main/java/blisgo/infrastructure/external/ExternalRoot.java b/infrastructure/external/src/main/java/blisgo/infrastructure/external/ExternalRoot.java
new file mode 100644
index 0000000..40e9fa4
--- /dev/null
+++ b/infrastructure/external/src/main/java/blisgo/infrastructure/external/ExternalRoot.java
@@ -0,0 +1,7 @@
+package blisgo.infrastructure.external;
+
+import org.springframework.context.annotation.ComponentScan;
+
+@ComponentScan(basePackageClasses = ExternalRoot.class)
+public interface ExternalRoot {
+}
diff --git a/infrastructure/external/src/main/java/blisgo/infrastructure/external/handler/PostViewedEventHandler.java b/infrastructure/external/src/main/java/blisgo/infrastructure/external/handler/PostViewedEventHandler.java
new file mode 100644
index 0000000..764159a
--- /dev/null
+++ b/infrastructure/external/src/main/java/blisgo/infrastructure/external/handler/PostViewedEventHandler.java
@@ -0,0 +1,23 @@
+package blisgo.infrastructure.external.handler;
+
+import blisgo.infrastructure.external.redis.ViewCountCache;
+import blisgo.usecase.event.PostViewedEvent;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Description;
+import org.springframework.context.event.EventListener;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+@Description("게시물 조회 이벤트 핸들러")
+public class PostViewedEventHandler {
+
+ private final ViewCountCache viewCountCache;
+
+ @EventListener
+ public void handlePostViewedEvent(PostViewedEvent event) {
+ viewCountCache.increaseViewCount(event.postId());
+ }
+}
\ No newline at end of file
diff --git a/infrastructure/external/src/main/java/blisgo/infrastructure/external/redis/RedisConfigDev.java b/infrastructure/external/src/main/java/blisgo/infrastructure/external/redis/RedisConfigDev.java
new file mode 100644
index 0000000..c9b929e
--- /dev/null
+++ b/infrastructure/external/src/main/java/blisgo/infrastructure/external/redis/RedisConfigDev.java
@@ -0,0 +1,67 @@
+package blisgo.infrastructure.external.redis;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.cache.CacheManager;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Description;
+import org.springframework.context.annotation.Profile;
+import org.springframework.data.redis.cache.RedisCacheConfiguration;
+import org.springframework.data.redis.cache.RedisCacheManager;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.repository.configuration.EnableRedisRepositories;
+import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
+import org.springframework.data.redis.serializer.RedisSerializationContext;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
+
+import java.time.Duration;
+
+@Profile("dev")
+@Configuration
+@EnableRedisRepositories
+@EnableRedisHttpSession
+@EnableCaching
+public class RedisConfigDev {
+
+ @Value("${spring.data.redis.host}")
+ private String host;
+
+ @Value("${spring.data.redis.port}")
+ private int port;
+
+ @Bean
+ @Profile("dev")
+ @Description("운영 환경용 redis 접속 정보")
+ public RedisConnectionFactory redisConnectionFactoryDev() {
+ return new LettuceConnectionFactory(host, port);
+ }
+
+ @Bean
+ @Description("redisTemplate")
+ public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
+ RedisTemplate redisTemplate = new RedisTemplate<>();
+ redisTemplate.setConnectionFactory(redisConnectionFactory);
+ redisTemplate.setKeySerializer(new StringRedisSerializer());
+ redisTemplate.setValueSerializer(new StringRedisSerializer());
+
+ return redisTemplate;
+ }
+
+ @Bean
+ @Description("cacheManager")
+ public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
+ RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
+ .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
+ .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())) // Value Serializer 변경
+ .entryTtl(Duration.ofMinutes(3L)); // 캐시 수명 30분
+
+ return RedisCacheManager.RedisCacheManagerBuilder
+ .fromConnectionFactory(redisConnectionFactory)
+ .cacheDefaults(redisCacheConfiguration)
+ .build();
+ }
+}
diff --git a/infrastructure/external/src/main/java/blisgo/infrastructure/external/redis/RedisConfigProd.java b/infrastructure/external/src/main/java/blisgo/infrastructure/external/redis/RedisConfigProd.java
new file mode 100644
index 0000000..30db0da
--- /dev/null
+++ b/infrastructure/external/src/main/java/blisgo/infrastructure/external/redis/RedisConfigProd.java
@@ -0,0 +1,80 @@
+package blisgo.infrastructure.external.redis;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.cache.CacheManager;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Description;
+import org.springframework.context.annotation.Profile;
+import org.springframework.data.redis.cache.RedisCacheConfiguration;
+import org.springframework.data.redis.cache.RedisCacheManager;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
+import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.repository.configuration.EnableRedisRepositories;
+import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
+import org.springframework.data.redis.serializer.RedisSerializationContext;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
+
+import java.time.Duration;
+
+@Profile("prod")
+@Configuration
+@EnableRedisRepositories
+@EnableRedisHttpSession
+@EnableCaching
+public class RedisConfigProd {
+
+ @Value("${spring.data.redis.host}")
+ private String host;
+
+ @Value("${spring.data.redis.port}")
+ private int port;
+
+ @Value("${spring.data.redis.username}")
+ private String username;
+
+ @Value("${spring.data.redis.password}")
+ private String password;
+
+ @Bean
+ @Profile("prod")
+ @Description("운영 환경용 redis 접속 정보")
+ public RedisConnectionFactory redisConnectionFactoryProd() {
+ RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
+ redisStandaloneConfiguration.setHostName(host);
+ redisStandaloneConfiguration.setPort(port);
+ redisStandaloneConfiguration.setUsername(username);
+ redisStandaloneConfiguration.setPassword(password);
+
+ return new LettuceConnectionFactory(redisStandaloneConfiguration);
+ }
+
+ @Bean
+ @Description("redisTemplate")
+ public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
+ RedisTemplate redisTemplate = new RedisTemplate<>();
+ redisTemplate.setConnectionFactory(redisConnectionFactory);
+ redisTemplate.setKeySerializer(new StringRedisSerializer());
+ redisTemplate.setValueSerializer(new StringRedisSerializer());
+
+ return redisTemplate;
+ }
+
+ @Bean
+ @Description("cacheManager")
+ public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
+ RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
+ .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
+ .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())) // Value Serializer 변경
+ .entryTtl(Duration.ofMinutes(3L)); // 캐시 수명 30분
+
+ return RedisCacheManager.RedisCacheManagerBuilder
+ .fromConnectionFactory(redisConnectionFactory)
+ .cacheDefaults(redisCacheConfiguration)
+ .build();
+ }
+}
diff --git a/infrastructure/external/src/main/java/blisgo/infrastructure/external/redis/SchedulerConfig.java b/infrastructure/external/src/main/java/blisgo/infrastructure/external/redis/SchedulerConfig.java
new file mode 100644
index 0000000..89788a4
--- /dev/null
+++ b/infrastructure/external/src/main/java/blisgo/infrastructure/external/redis/SchedulerConfig.java
@@ -0,0 +1,26 @@
+package blisgo.infrastructure.external.redis;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Description;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.scheduling.annotation.SchedulingConfigurer;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
+import org.springframework.scheduling.config.ScheduledTaskRegistrar;
+
+@EnableScheduling
+@Configuration
+@Description("스케줄러 설정")
+public class SchedulerConfig implements SchedulingConfigurer {
+ private static final int POOL_SIZE = 2;
+
+ @Override
+ public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
+ ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
+
+ threadPoolTaskScheduler.setPoolSize(POOL_SIZE);
+ threadPoolTaskScheduler.setThreadNamePrefix("scheduled--pool-");
+ threadPoolTaskScheduler.initialize();
+
+ scheduledTaskRegistrar.setTaskScheduler(threadPoolTaskScheduler);
+ }
+}
diff --git a/infrastructure/external/src/main/java/blisgo/infrastructure/external/redis/ViewCountCache.java b/infrastructure/external/src/main/java/blisgo/infrastructure/external/redis/ViewCountCache.java
new file mode 100644
index 0000000..10a557f
--- /dev/null
+++ b/infrastructure/external/src/main/java/blisgo/infrastructure/external/redis/ViewCountCache.java
@@ -0,0 +1,32 @@
+package blisgo.infrastructure.external.redis;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.context.annotation.Description;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.ValueOperations;
+import org.springframework.stereotype.Component;
+
+@Component
+@RequiredArgsConstructor
+@Description("게시물 조회수 캐시")
+public class ViewCountCache {
+ private final RedisTemplate redisTemplate;
+
+ public void increaseViewCount(Long postId) {
+ ValueOperations ops = redisTemplate.opsForValue();
+ ops.increment(getKey(postId), 1);
+ }
+
+ public long getViewCount(Long postId) {
+ var value = redisTemplate.opsForValue().get(getKey(postId));
+ return value == null ? 0 : Long.parseLong(value.toString());
+ }
+
+ private String getKey(Long postId) {
+ return "post:" + postId + ":views";
+ }
+
+ public void remove(Long postId) {
+ redisTemplate.delete(getKey(postId));
+ }
+}
\ No newline at end of file
diff --git a/infrastructure/external/src/main/java/blisgo/infrastructure/external/scheduler/ViewCountScheduler.java b/infrastructure/external/src/main/java/blisgo/infrastructure/external/scheduler/ViewCountScheduler.java
new file mode 100644
index 0000000..c489ce1
--- /dev/null
+++ b/infrastructure/external/src/main/java/blisgo/infrastructure/external/scheduler/ViewCountScheduler.java
@@ -0,0 +1,30 @@
+package blisgo.infrastructure.external.scheduler;
+
+import blisgo.infrastructure.external.redis.ViewCountCache;
+import blisgo.infrastructure.internal.persistence.community.PostMySQLAdapter;
+import lombok.RequiredArgsConstructor;
+import org.springframework.context.annotation.Description;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+@Component
+@RequiredArgsConstructor
+@Description("캐시에 저장된 조회수를 DB에 업데이트하는 스케줄러")
+public class ViewCountScheduler {
+ private final ViewCountCache viewCountCache;
+ private final PostMySQLAdapter postMySQLAdapter;
+
+ @Scheduled(fixedRate = 60000)
+ public void updateViewCounts() {
+ List postIds = postMySQLAdapter.findPostIds();
+
+ for (Long postId : postIds) {
+ long viewCount = viewCountCache.getViewCount(postId);
+ if (viewCount > 0 && (postMySQLAdapter.updateViewCount(postId, viewCount))) {
+ viewCountCache.remove(postId);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/infrastructure/internal/build.gradle b/infrastructure/internal/build.gradle
new file mode 100644
index 0000000..dbb1ad9
--- /dev/null
+++ b/infrastructure/internal/build.gradle
@@ -0,0 +1,90 @@
+plugins {
+ id 'org.springdoc.openapi-gradle-plugin' version '+'
+ id 'io.swagger.swaggerhub' version '+'
+}
+
+bootJar.enabled = false
+jar.enabled = true
+
+dependencies {
+ implementation project(':domain')
+ implementation project(':usecase')
+
+ // jpa
+ implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
+
+ // hibernate & jakarta validator
+ implementation 'org.springframework.boot:spring-boot-starter-validation'
+
+ // querydsl
+ implementation 'com.querydsl:querydsl-jpa:5.+:jakarta'
+ annotationProcessor 'com.querydsl:querydsl-apt:5.+:jakarta'
+
+ // mysql
+ runtimeOnly 'com.mysql:mysql-connector-j'
+
+ // jakarta
+ annotationProcessor 'jakarta.persistence:jakarta.persistence-api'
+ annotationProcessor 'jakarta.annotation:jakarta.annotation-api'
+
+ // auth0 security
+ implementation 'com.okta.spring:okta-spring-boot-starter:+'
+
+ //spring security
+ implementation 'org.springframework.boot:spring-boot-starter-security'
+
+ // 게시판 json 글 저장용
+ implementation 'com.fasterxml.jackson.core:jackson-databind:+'
+
+ // documentation
+ implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:+'
+
+ // htmx + thymeleaf support
+ implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
+ implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6:+'
+ implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect:+'
+ implementation 'org.thymeleaf.extras:thymeleaf-extras-java8time:+'
+
+ implementation 'org.springframework.boot:spring-boot-starter-web'
+
+ // flyway
+ implementation 'org.flywaydb:flyway-core:+'
+ implementation 'org.flywaydb:flyway-mysql:+'
+}
+
+def querydslDir = "${project.projectDir}/build/generated/querydsl"
+
+sourceSets {
+ main.java.srcDirs += [querydslDir]
+}
+
+tasks.withType(JavaCompile).configureEach {
+ options.getGeneratedSourceOutputDirectory().set(file(querydslDir))
+}
+
+clean.doLast {
+ file(querydslDir).deleteDir()
+}
+
+openApi {
+ apiDocsUrl.set('http://localhost:8080/v3/api-docs.yaml')
+ outputDir.set(file("${project.rootDir}/build/docs"))
+ outputFileName.set('swagger.yaml')
+ waitTimeInSeconds.set(10)
+ customBootRun {
+ args.set(['--spring.profiles.active=dev'])
+ }
+}
+
+import java.text.SimpleDateFormat
+
+swaggerhubUpload {
+ def dateVersion = new SimpleDateFormat('yyyy-MM-dd').format(new Date())
+
+ api 'blisgo'
+ owner 'okjaeook'
+ version dateVersion
+ format 'yaml'
+ inputFile "${project.rootDir}/build/docs/swagger.yaml"
+ token System.getenv('SWAGGERHUB_API_KEY')
+}
\ No newline at end of file
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/InternalRoot.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/InternalRoot.java
new file mode 100644
index 0000000..5cfc616
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/InternalRoot.java
@@ -0,0 +1,7 @@
+package blisgo.infrastructure.internal;
+
+import org.springframework.context.annotation.ComponentScan;
+
+@ComponentScan(basePackageClasses = InternalRoot.class)
+public interface InternalRoot {
+}
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/base/ContentConverter.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/base/ContentConverter.java
new file mode 100644
index 0000000..29cc850
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/base/ContentConverter.java
@@ -0,0 +1,80 @@
+package blisgo.infrastructure.internal.persistence.base;
+
+import blisgo.infrastructure.internal.persistence.common.JpaPicture;
+import blisgo.infrastructure.internal.persistence.common.JpaContent;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import jakarta.persistence.AttributeConverter;
+import jakarta.persistence.Converter;
+
+@Converter(autoApply = true)
+public class ContentConverter implements AttributeConverter {
+ private final ObjectMapper objectMapper = new ObjectMapper();
+
+ private static final String TYPE = "type";
+ private static final String DATA = "data";
+ private static final String TEXT = "text";
+ private static final String BLOCKS = "blocks";
+ private static final String URL = "url";
+ private static final String IMAGE = "image";
+ private static final String PARAGRAPH = "paragraph";
+
+ @Override
+ public String convertToDatabaseColumn(JpaContent content) {
+ return content.text();
+ }
+
+ @Override
+ public JpaContent convertToEntityAttribute(String dbData) {
+ JsonNode json;
+ String thumbnail, preview;
+
+ dbData = dbData == null ? "" : dbData;
+
+ try {
+ json = objectMapper.readTree(dbData);
+ } catch (JsonProcessingException e) {
+ throw new RuntimeException(e);
+ }
+
+ thumbnail = parseFirstImageUrl(json);
+ preview = parseFirstParagraph(json);
+
+ return JpaContent.of(dbData, JpaPicture.of(thumbnail), preview);
+ }
+
+ private String parseFirstParagraph(JsonNode json) {
+ JsonNode blocksNode = json.get(BLOCKS);
+
+ if (blocksNode == null) {
+ return null;
+ }
+
+ for (JsonNode blockNode : blocksNode) {
+ if (PARAGRAPH.equals(blockNode.get(TYPE).asText())) {
+ return blockNode.get(DATA).get(TEXT).asText();
+ }
+ }
+
+ return null;
+ }
+
+ private String parseFirstImageUrl(JsonNode json) {
+ JsonNode blocksNode = json.get(BLOCKS);
+
+ if (blocksNode == null) {
+ return null;
+ }
+
+ for (JsonNode blockNode : blocksNode) {
+ if (IMAGE.equals(blockNode.get(TYPE).asText())) {
+ return blockNode.get(DATA).get(URL).asText();
+ }
+ }
+
+ return null;
+ }
+
+
+}
\ No newline at end of file
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/base/JpaConfig.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/base/JpaConfig.java
new file mode 100644
index 0000000..af5ff1b
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/base/JpaConfig.java
@@ -0,0 +1,36 @@
+package blisgo.infrastructure.internal.persistence.base;
+
+import blisgo.infrastructure.internal.InternalRoot;
+import blisgo.infrastructure.internal.persistence.common.JpaAuthor;
+import com.querydsl.jpa.impl.JPAQueryFactory;
+import jakarta.persistence.EntityManager;
+import jakarta.persistence.PersistenceContext;
+import org.springframework.boot.autoconfigure.domain.EntityScan;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Description;
+import org.springframework.data.domain.AuditorAware;
+import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
+import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
+
+@Configuration
+@EnableJpaAuditing
+@EnableJpaRepositories(basePackageClasses = InternalRoot.class)
+@EntityScan(basePackageClasses = InternalRoot.class)
+public class JpaConfig {
+ @PersistenceContext
+ private EntityManager entityManager;
+
+ @Bean
+ @Description("QueryDSL JPA 설정")
+ public JPAQueryFactory init() {
+ return new JPAQueryFactory(entityManager);
+ }
+
+ @Bean
+ @Description("도메인 중 회원 정보 데이터가 필요한 경우 OIDC 에서 받아온 회원 정보를 사용하도록 함")
+ public AuditorAware auditorProvider() {
+ return new OidcAuditorAware();
+ }
+
+}
\ No newline at end of file
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/base/MapperConfig.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/base/MapperConfig.java
new file mode 100644
index 0000000..06db0ad
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/base/MapperConfig.java
@@ -0,0 +1,24 @@
+package blisgo.infrastructure.internal.persistence.base;
+
+import org.modelmapper.ModelMapper;
+import org.modelmapper.convention.MatchingStrategies;
+import org.modelmapper.record.RecordModule;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Description;
+
+@Configuration
+public class MapperConfig {
+ @Bean
+ @Description("ModelMapper 설정")
+ public ModelMapper modelMapper() {
+ ModelMapper modelMapper = new ModelMapper();
+ modelMapper.registerModule(new RecordModule());
+ modelMapper.getConfiguration()
+ .setFieldAccessLevel(org.modelmapper.config.Configuration.AccessLevel.PRIVATE)
+ .setFieldMatchingEnabled(true)
+ .setMatchingStrategy(MatchingStrategies.LOOSE);
+
+ return modelMapper;
+ }
+}
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/base/NoOffsetSliceHelper.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/base/NoOffsetSliceHelper.java
new file mode 100644
index 0000000..d439fd6
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/base/NoOffsetSliceHelper.java
@@ -0,0 +1,17 @@
+package blisgo.infrastructure.internal.persistence.base;
+
+import lombok.experimental.UtilityClass;
+import org.springframework.data.domain.Pageable;
+
+import java.util.List;
+
+@UtilityClass
+public class NoOffsetSliceHelper {
+ public static boolean checkLastPage(List> results, Pageable pageable) {
+ if (results.size() > pageable.getPageSize()) {
+ results.remove(pageable.getPageSize());
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/base/OidcAuditorAware.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/base/OidcAuditorAware.java
new file mode 100644
index 0000000..3fb61f1
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/base/OidcAuditorAware.java
@@ -0,0 +1,39 @@
+package blisgo.infrastructure.internal.persistence.base;
+
+
+import blisgo.infrastructure.internal.persistence.common.JpaAuthor;
+import blisgo.infrastructure.internal.persistence.common.JpaPicture;
+import lombok.NonNull;
+import org.springframework.data.domain.AuditorAware;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
+import org.springframework.security.oauth2.core.oidc.user.OidcUser;
+
+import java.util.Optional;
+
+public class OidcAuditorAware implements AuditorAware {
+
+ @NonNull
+ @Override
+ public Optional getCurrentAuditor() {
+ Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+
+ if (authentication == null || !authentication.isAuthenticated()) {
+ return Optional.of(JpaAuthor.of(
+ "admin@blisgo.com",
+ "관리자",
+ JpaPicture.of("https://res.cloudinary.com/blisgo/image/upload/f_auto,q_auto/v1/manifest/admin")
+ ));
+ }
+
+ OidcUser oidcUser = (OidcUser) authentication.getPrincipal();
+ OidcUserInfo oidcUserInfo = oidcUser.getUserInfo();
+
+ return Optional.of(JpaAuthor.of(
+ oidcUserInfo.getEmail(),
+ oidcUserInfo.getFullName(),
+ JpaPicture.of(oidcUserInfo.getPicture())
+ ));
+ }
+}
\ No newline at end of file
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/base/PersistenceMapper.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/base/PersistenceMapper.java
new file mode 100644
index 0000000..cd390c3
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/base/PersistenceMapper.java
@@ -0,0 +1,24 @@
+package blisgo.infrastructure.internal.persistence.base;
+
+import java.util.List;
+
+public interface PersistenceMapper {
+
+ E toEntity(D domain);
+
+ D toDomain(E entity);
+
+ default List toDomains(List entities) {
+ return entities.stream().map(this::toDomain).toList();
+ }
+
+ default List toEntities(List domains) {
+ return domains.stream().map(this::toEntity).toList();
+ }
+
+ V toDTO(D domain);
+
+ default List toDTOs(List domains) {
+ return domains.stream().map(this::toDTO).toList();
+ }
+}
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/common/BaseEntity.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/common/BaseEntity.java
new file mode 100644
index 0000000..5f0d28d
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/common/BaseEntity.java
@@ -0,0 +1,25 @@
+package blisgo.infrastructure.internal.persistence.common;
+
+import jakarta.persistence.Embedded;
+import jakarta.persistence.EntityListeners;
+import jakarta.persistence.MappedSuperclass;
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+import org.springframework.data.annotation.LastModifiedBy;
+import org.springframework.data.jpa.domain.support.AuditingEntityListener;
+
+@Getter
+@AllArgsConstructor(access = AccessLevel.PROTECTED)
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+@SuperBuilder(toBuilder = true)
+@MappedSuperclass
+@EntityListeners(AuditingEntityListener.class)
+public abstract class BaseEntity extends BaseTimeEntity {
+
+ @Embedded
+ @LastModifiedBy
+ protected JpaAuthor author;
+}
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/common/BaseTimeEntity.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/common/BaseTimeEntity.java
new file mode 100644
index 0000000..1cef506
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/common/BaseTimeEntity.java
@@ -0,0 +1,34 @@
+package blisgo.infrastructure.internal.persistence.common;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.EntityListeners;
+import jakarta.persistence.MappedSuperclass;
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+import org.hibernate.annotations.Comment;
+import org.springframework.data.annotation.CreatedDate;
+import org.springframework.data.annotation.LastModifiedDate;
+import org.springframework.data.jpa.domain.support.AuditingEntityListener;
+
+import java.time.LocalDateTime;
+
+@Getter
+@AllArgsConstructor(access = AccessLevel.PROTECTED)
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+@SuperBuilder(toBuilder = true)
+@MappedSuperclass
+@EntityListeners(AuditingEntityListener.class)
+public abstract class BaseTimeEntity {
+ @CreatedDate
+ @Column(updatable = false, nullable = false, columnDefinition = "DATETIME DEFAULT CURRENT_TIMESTAMP")
+ @Comment("생성 시각")
+ protected LocalDateTime createdDate;
+
+ @LastModifiedDate
+ @Column(nullable = false, columnDefinition = "DATETIME DEFAULT CURRENT_TIMESTAMP")
+ @Comment("수정 시각")
+ protected LocalDateTime modifiedDate;
+}
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/common/JpaAuthor.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/common/JpaAuthor.java
new file mode 100644
index 0000000..9b0ca6b
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/common/JpaAuthor.java
@@ -0,0 +1,30 @@
+package blisgo.infrastructure.internal.persistence.common;
+
+import jakarta.persistence.AttributeOverride;
+import jakarta.persistence.Column;
+import jakarta.persistence.Embeddable;
+import jakarta.persistence.Embedded;
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import org.hibernate.annotations.Comment;
+
+@Getter
+@Embeddable
+@AllArgsConstructor(staticName = "of")
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+public class JpaAuthor {
+
+ @Column(name = "author_email")
+ @Comment("회원 이메일(FK)")
+ private String email;
+
+ @Column(name = "author_name")
+ @Comment("작성자")
+ private String name;
+
+ @Embedded
+ @AttributeOverride(name = "url", column = @Column(name = "author_picture"))
+ private JpaPicture picture;
+}
\ No newline at end of file
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/common/JpaContent.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/common/JpaContent.java
new file mode 100644
index 0000000..a39ce96
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/common/JpaContent.java
@@ -0,0 +1,15 @@
+package blisgo.infrastructure.internal.persistence.common;
+
+import lombok.*;
+
+@Builder
+@Getter
+@AllArgsConstructor(staticName = "of")
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+public class JpaContent {
+ private String text;
+
+ private JpaPicture thumbnail;
+
+ private String preview;
+}
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/common/JpaPicture.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/common/JpaPicture.java
new file mode 100644
index 0000000..bdbccae
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/common/JpaPicture.java
@@ -0,0 +1,19 @@
+package blisgo.infrastructure.internal.persistence.common;
+
+import jakarta.persistence.Embeddable;
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import org.hibernate.annotations.Comment;
+import org.hibernate.validator.constraints.URL;
+
+@Getter
+@Embeddable
+@AllArgsConstructor(staticName = "of")
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+public class JpaPicture {
+ @URL(protocol = "https")
+ @Comment("이미지")
+ private String url;
+}
\ No newline at end of file
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/community/PostMySQLAdapter.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/community/PostMySQLAdapter.java
new file mode 100644
index 0000000..5269741
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/community/PostMySQLAdapter.java
@@ -0,0 +1,86 @@
+package blisgo.infrastructure.internal.persistence.community;
+
+import blisgo.domain.community.Post;
+import blisgo.infrastructure.internal.persistence.community.model.JpaPost;
+import blisgo.infrastructure.internal.persistence.community.mapper.PostMapper;
+import blisgo.infrastructure.internal.persistence.community.repository.PostCustomRepository;
+import blisgo.infrastructure.internal.persistence.community.repository.PostJpaRepository;
+import blisgo.usecase.port.PostOutputPort;
+import lombok.RequiredArgsConstructor;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Slice;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+import java.util.Map;
+
+@Component
+@Transactional(readOnly = true)
+@RequiredArgsConstructor
+public class PostMySQLAdapter implements PostOutputPort {
+ private final PostJpaRepository jpaRepository;
+ private final PostCustomRepository customRepository;
+ private final PostMapper mapper;
+
+ @Override
+ @Transactional
+ public boolean create(Post domain) {
+ JpaPost jpaPost = mapper.toEntity(domain);
+
+ jpaRepository.save(jpaPost);
+ return true;
+ }
+
+ @Override
+ public Post read(Long postId) {
+ return jpaRepository.findById(postId)
+ .map(mapper::toDomain)
+ .orElse(null);
+ }
+
+ @Override
+ public Slice read(Map columns, Pageable pageable) {
+ Long lastPostId = (Long) columns.get("lastPostId");
+
+ return customRepository.find(pageable, lastPostId)
+ .map(mapper::toDomain);
+ }
+
+ @Override
+ @Transactional
+ public boolean update(Post domain) {
+ if (domain.postId().id() == null) {
+ jpaRepository.save(mapper.toEntity(domain));
+ return true;
+ }
+
+ jpaRepository.findById(domain.postId().id()).ifPresentOrElse(
+ existingPost -> existingPost.updateInfo(mapper.toEntity(domain)),
+ () -> jpaRepository.save(mapper.toEntity(domain))
+ );
+
+ return true;
+ }
+
+ @Override
+ @Transactional
+ public boolean delete(Long identifier) {
+ return jpaRepository.deleteByPostId(identifier) > 0;
+ }
+
+ @Transactional
+ public boolean updateLike(Long postId, Boolean isLike) {
+ return customRepository.updateLike(postId, isLike);
+ }
+
+ @Override
+ public List findPostIds() {
+ return customRepository.findAllPostIds();
+ }
+
+ @Transactional
+ public boolean updateViewCount(Long postId, Long views) {
+ return customRepository.updateViewCount(postId, views);
+ }
+}
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/community/ReplyMySQLAdapter.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/community/ReplyMySQLAdapter.java
new file mode 100644
index 0000000..5f600b6
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/community/ReplyMySQLAdapter.java
@@ -0,0 +1,50 @@
+package blisgo.infrastructure.internal.persistence.community;
+
+import blisgo.domain.community.Reply;
+import blisgo.infrastructure.internal.persistence.community.mapper.ReplyMapper;
+import blisgo.infrastructure.internal.persistence.community.repository.ReplyCustomRepository;
+import blisgo.infrastructure.internal.persistence.community.repository.ReplyJpaRepository;
+import blisgo.usecase.port.ReplyOutputPort;
+import lombok.RequiredArgsConstructor;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Slice;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+@Component
+@Transactional
+@RequiredArgsConstructor
+public class ReplyMySQLAdapter implements ReplyOutputPort {
+ private final ReplyJpaRepository jpaRepository;
+ private final ReplyCustomRepository customRepository;
+ private final ReplyMapper mapper;
+
+ @Override
+ public boolean delete(Long identifier) {
+ return jpaRepository.deleteByReplyId(identifier) > 0;
+ }
+
+ @Override
+ public boolean create(Reply domain) {
+ jpaRepository.save(mapper.toEntity(domain));
+ return true;
+ }
+
+ @Override
+ public Slice read(Long postId, Pageable pageable, Long lastReplyId) {
+ return customRepository.find(pageable, postId, lastReplyId)
+ .map(mapper::toDomain);
+ }
+
+ @Override
+ public boolean update(Reply domain) {
+ var jpaReply = mapper.toEntity(domain);
+
+ jpaRepository.findById(jpaReply.replyId()).ifPresentOrElse(
+ existingReply -> existingReply.updateInfo(jpaReply),
+ () -> jpaRepository.save(jpaReply)
+ );
+
+ return true;
+ }
+}
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/community/mapper/PostMapper.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/community/mapper/PostMapper.java
new file mode 100644
index 0000000..10538ea
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/community/mapper/PostMapper.java
@@ -0,0 +1,34 @@
+package blisgo.infrastructure.internal.persistence.community.mapper;
+
+import blisgo.domain.community.Post;
+import blisgo.infrastructure.internal.persistence.base.PersistenceMapper;
+import blisgo.infrastructure.internal.persistence.community.model.JpaPost;
+import blisgo.infrastructure.internal.ui.response.PostDTO;
+import lombok.RequiredArgsConstructor;
+import org.modelmapper.ModelMapper;
+import org.springframework.stereotype.Component;
+
+@Component
+@RequiredArgsConstructor
+public class PostMapper implements PersistenceMapper {
+ private final ModelMapper mapper;
+
+ @Override
+ public JpaPost toEntity(Post domain) {
+ return mapper.map(domain, JpaPost.class);
+ }
+
+ @Override
+ public Post toDomain(JpaPost entity) {
+ return mapper.map(entity, Post.class);
+ }
+
+ @Override
+ public PostDTO toDTO(Post domain) {
+ return mapper.map(domain, PostDTO.class);
+ }
+
+ public Post toDomain(PostDTO dto) {
+ return mapper.map(dto, Post.class);
+ }
+}
\ No newline at end of file
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/community/mapper/ReplyMapper.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/community/mapper/ReplyMapper.java
new file mode 100644
index 0000000..2b1fae8
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/community/mapper/ReplyMapper.java
@@ -0,0 +1,30 @@
+package blisgo.infrastructure.internal.persistence.community.mapper;
+
+import blisgo.domain.community.Reply;
+import blisgo.infrastructure.internal.persistence.base.PersistenceMapper;
+import blisgo.infrastructure.internal.persistence.community.model.JpaReply;
+import blisgo.infrastructure.internal.ui.response.ReplyDTO;
+import lombok.RequiredArgsConstructor;
+import org.modelmapper.ModelMapper;
+import org.springframework.stereotype.Component;
+
+@Component
+@RequiredArgsConstructor
+public class ReplyMapper implements PersistenceMapper {
+ private final ModelMapper mapper;
+
+ @Override
+ public JpaReply toEntity(Reply domain) {
+ return mapper.map(domain, JpaReply.class);
+ }
+
+ @Override
+ public Reply toDomain(JpaReply entity) {
+ return mapper.map(entity, Reply.class);
+ }
+
+ @Override
+ public ReplyDTO toDTO(Reply domain) {
+ return mapper.map(domain, ReplyDTO.class);
+ }
+}
\ No newline at end of file
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/community/model/JpaPost.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/community/model/JpaPost.java
new file mode 100644
index 0000000..f913784
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/community/model/JpaPost.java
@@ -0,0 +1,54 @@
+package blisgo.infrastructure.internal.persistence.community.model;
+
+import blisgo.infrastructure.internal.persistence.common.BaseEntity;
+import blisgo.infrastructure.internal.persistence.common.JpaContent;
+import blisgo.infrastructure.internal.persistence.base.ContentConverter;
+import jakarta.persistence.Table;
+import jakarta.persistence.*;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+import org.hibernate.annotations.*;
+
+@DynamicInsert
+@DynamicUpdate
+@Getter
+@SuperBuilder(toBuilder = true)
+@NoArgsConstructor
+@Entity
+@Table(name = "post")
+@Comment("게시글")
+public class JpaPost extends BaseEntity {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Comment("게시글 postId")
+ private Long postId;
+
+ @Comment("제목")
+ private String title;
+
+ @Comment("내용")
+ @Column(name = "content", columnDefinition = "JSON")
+ @Convert(converter = ContentConverter.class)
+ private JpaContent content;
+
+ @ColumnDefault("0")
+ @Comment("조회수")
+ private long views;
+
+ @ColumnDefault("0")
+ @Comment("좋아요")
+ private long likes;
+
+ @Formula("(SELECT count(*) FROM reply r WHERE r.post_id = post_id)")
+ private long replies;
+
+ @Version
+ @Comment("좋아요 동시성 해결을 위한 낙관적 락")
+ private Long version;
+
+ public void updateInfo(JpaPost entity) {
+ this.title = entity.title;
+ this.content = entity.content;
+ }
+}
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/community/model/JpaReply.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/community/model/JpaReply.java
new file mode 100644
index 0000000..c07c346
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/community/model/JpaReply.java
@@ -0,0 +1,36 @@
+package blisgo.infrastructure.internal.persistence.community.model;
+
+import blisgo.infrastructure.internal.persistence.common.BaseEntity;
+import jakarta.persistence.*;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+import org.hibernate.annotations.Comment;
+import org.hibernate.annotations.DynamicInsert;
+import org.hibernate.annotations.DynamicUpdate;
+
+@DynamicInsert
+@DynamicUpdate
+@Getter
+@SuperBuilder(toBuilder = true)
+@NoArgsConstructor
+@Entity
+@Table(name = "reply")
+@Comment("댓글")
+public class JpaReply extends BaseEntity {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Comment("댓글 Id")
+ private Long replyId;
+
+ @Comment("게시글 Id")
+ private Long postId;
+
+ @Lob
+ @Comment("내용")
+ private String content;
+
+ public void updateInfo(JpaReply jpaReply) {
+ this.content = jpaReply.content;
+ }
+}
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/community/repository/PostCustomRepository.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/community/repository/PostCustomRepository.java
new file mode 100644
index 0000000..5c1bdf8
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/community/repository/PostCustomRepository.java
@@ -0,0 +1,75 @@
+package blisgo.infrastructure.internal.persistence.community.repository;
+
+import blisgo.infrastructure.internal.persistence.base.NoOffsetSliceHelper;
+import blisgo.infrastructure.internal.persistence.community.model.JpaPost;
+import blisgo.infrastructure.internal.persistence.community.model.QJpaPost;
+import com.querydsl.core.types.Projections;
+import com.querydsl.jpa.impl.JPAQueryFactory;
+import lombok.RequiredArgsConstructor;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Slice;
+import org.springframework.data.domain.SliceImpl;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+@Repository
+@RequiredArgsConstructor
+public class PostCustomRepository {
+ private final JPAQueryFactory jpaQueryFactory;
+ private final JdbcTemplate jdbcTemplate;
+
+ public Slice find(Pageable pageable, long lastPostId) {
+ var qJpaPost = QJpaPost.jpaPost;
+
+ var fields = Projections.fields(JpaPost.class,
+ qJpaPost.postId,
+ qJpaPost.title,
+ qJpaPost.content,
+ qJpaPost.author,
+ qJpaPost.views,
+ qJpaPost.likes,
+ qJpaPost.replies,
+ qJpaPost.modifiedDate
+ );
+
+ var results = jpaQueryFactory.select(fields)
+ .from(qJpaPost)
+ .where(qJpaPost.postId.lt(lastPostId))
+ .orderBy(qJpaPost.postId.desc())
+ .limit(pageable.getPageSize() + 1L)
+ .fetch();
+
+ boolean hasNext = NoOffsetSliceHelper.checkLastPage(results, pageable);
+
+ return new SliceImpl<>(results, pageable, hasNext);
+ }
+
+ public List findAllPostIds() {
+ var qJpaPost = QJpaPost.jpaPost;
+
+ return jpaQueryFactory.select(qJpaPost.postId)
+ .from(qJpaPost)
+ .fetch();
+ }
+
+ public boolean updateViewCount(Long postId, Long views) {
+ var qJpaPost = QJpaPost.jpaPost;
+
+ return jpaQueryFactory.update(qJpaPost)
+ .set(qJpaPost.views, views)
+ .where(qJpaPost.postId.eq(postId))
+ .execute() > 0;
+ }
+
+ public boolean updateLike(Long postId, Boolean isLike) {
+ var qJpaPost = QJpaPost.jpaPost;
+
+ return jpaQueryFactory.update(qJpaPost)
+ .set(qJpaPost.likes, qJpaPost.likes.add(Boolean.TRUE.equals(isLike) ? 1 : -1))
+ .where(qJpaPost.postId.eq(postId))
+ .execute() > 0;
+ }
+}
+
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/community/repository/PostJpaRepository.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/community/repository/PostJpaRepository.java
new file mode 100644
index 0000000..2f5bfc9
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/community/repository/PostJpaRepository.java
@@ -0,0 +1,8 @@
+package blisgo.infrastructure.internal.persistence.community.repository;
+
+import blisgo.infrastructure.internal.persistence.community.model.JpaPost;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface PostJpaRepository extends JpaRepository {
+ long deleteByPostId(Long identifier);
+}
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/community/repository/ReplyCustomRepository.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/community/repository/ReplyCustomRepository.java
new file mode 100644
index 0000000..f991c3c
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/community/repository/ReplyCustomRepository.java
@@ -0,0 +1,34 @@
+package blisgo.infrastructure.internal.persistence.community.repository;
+
+import blisgo.infrastructure.internal.persistence.base.NoOffsetSliceHelper;
+import blisgo.infrastructure.internal.persistence.community.model.JpaReply;
+import blisgo.infrastructure.internal.persistence.community.model.QJpaReply;
+import com.querydsl.jpa.impl.JPAQueryFactory;
+import lombok.RequiredArgsConstructor;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Slice;
+import org.springframework.data.domain.SliceImpl;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Repository;
+
+@Repository
+@RequiredArgsConstructor
+public class ReplyCustomRepository {
+ private final JPAQueryFactory jpaQueryFactory;
+ private final JdbcTemplate jdbcTemplate;
+
+ public Slice find(Pageable pageable, Long postId, long lastReplyId) {
+ var qJpaReply = QJpaReply.jpaReply;
+
+
+ var results = jpaQueryFactory.selectFrom(qJpaReply)
+ .where(qJpaReply.postId.eq(postId))
+ .where(qJpaReply.replyId.gt(lastReplyId))
+ .limit(pageable.getPageSize() + 1L)
+ .fetch();
+
+ boolean hasNext = NoOffsetSliceHelper.checkLastPage(results, pageable);
+
+ return new SliceImpl<>(results, pageable, hasNext);
+ }
+}
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/community/repository/ReplyJpaRepository.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/community/repository/ReplyJpaRepository.java
new file mode 100644
index 0000000..0116798
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/community/repository/ReplyJpaRepository.java
@@ -0,0 +1,8 @@
+package blisgo.infrastructure.internal.persistence.community.repository;
+
+import blisgo.infrastructure.internal.persistence.community.model.JpaReply;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface ReplyJpaRepository extends JpaRepository {
+ long deleteByReplyId(Long identifier);
+}
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/dictionary/DogamMySQLAdapter.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/dictionary/DogamMySQLAdapter.java
new file mode 100644
index 0000000..d418917
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/dictionary/DogamMySQLAdapter.java
@@ -0,0 +1,41 @@
+package blisgo.infrastructure.internal.persistence.dictionary;
+
+import blisgo.domain.dictionary.Dogam;
+import blisgo.domain.dictionary.vo.DogamId;
+import blisgo.infrastructure.internal.persistence.dictionary.mapper.DogamMapper;
+import blisgo.infrastructure.internal.persistence.dictionary.mapper.WasteMapper;
+import blisgo.infrastructure.internal.persistence.dictionary.model.JpaDogamId;
+import blisgo.infrastructure.internal.persistence.dictionary.repository.DogamCustomRepository;
+import blisgo.infrastructure.internal.persistence.dictionary.repository.DogamJpaRepository;
+import blisgo.usecase.port.DogamOutputPort;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+@Component
+@Transactional(readOnly = true)
+@RequiredArgsConstructor
+public class DogamMySQLAdapter implements DogamOutputPort {
+ private final DogamJpaRepository jpaRepository;
+ private final DogamCustomRepository customRepository;
+ private final DogamMapper mapper;
+ private final WasteMapper wasteMapper;
+
+ @Override
+ @Transactional
+ public boolean delete(DogamId identifier) {
+ JpaDogamId jpaDogamId = JpaDogamId.of(
+ identifier.memberId().id(),
+ identifier.wasteId().id()
+ );
+
+ return jpaRepository.deleteByDogamId(jpaDogamId);
+ }
+
+ @Override
+ @Transactional
+ public boolean create(Dogam domain) {
+ jpaRepository.save(mapper.toEntity(domain));
+ return true;
+ }
+}
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/dictionary/WasteMySQLAdapter.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/dictionary/WasteMySQLAdapter.java
new file mode 100644
index 0000000..992706d
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/dictionary/WasteMySQLAdapter.java
@@ -0,0 +1,60 @@
+package blisgo.infrastructure.internal.persistence.dictionary;
+
+import blisgo.domain.dictionary.Waste;
+import blisgo.domain.dictionary.vo.Category;
+import blisgo.domain.dictionary.vo.Guide;
+import blisgo.infrastructure.internal.persistence.dictionary.mapper.GuideMapper;
+import blisgo.infrastructure.internal.persistence.dictionary.mapper.WasteMapper;
+import blisgo.infrastructure.internal.persistence.dictionary.repository.GuideJpaRepository;
+import blisgo.infrastructure.internal.persistence.dictionary.repository.WasteCustomRepository;
+import blisgo.infrastructure.internal.persistence.dictionary.repository.WasteJpaRepository;
+import blisgo.usecase.port.WasteOutputPort;
+import lombok.RequiredArgsConstructor;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Slice;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.UUID;
+
+@Component
+@Transactional(readOnly = true)
+@RequiredArgsConstructor
+public class WasteMySQLAdapter implements WasteOutputPort {
+ private final WasteJpaRepository wasteJpaRepository;
+ private final WasteCustomRepository wasteCustomRepository;
+ private final GuideJpaRepository guideJpaRepository;
+ private final WasteMapper wasteMapper;
+ private final GuideMapper guideMapper;
+
+ @Override
+ public Waste read(Long wasteId) {
+ return wasteJpaRepository.findFirstByWasteId(wasteId)
+ .map(wasteMapper::toDomain)
+ .orElseThrow(() -> new IllegalArgumentException("Waste not found"));
+ }
+
+ @Override
+ public Slice read(Pageable pageable, Long lastWasteId) {
+ return wasteCustomRepository.findPartial(pageable, lastWasteId)
+ .map(wasteMapper::toDomain);
+ }
+
+ @Override
+ public List readGuides(List categories) {
+ return guideMapper.toDomains(guideJpaRepository.findAllById(categories));
+ }
+
+ @Override
+ public Slice readWastesFromDogam(UUID memberId, Pageable pageable, LocalDateTime lastDogamCreatedDate) {
+ return wasteCustomRepository.findWastesByMemberIdFromDogam(memberId, pageable, lastDogamCreatedDate)
+ .map(wasteMapper::toDomain);
+ }
+
+ @Override
+ public List readWastesRelated(List categories) {
+ return wasteMapper.toDomains(wasteCustomRepository.findWastesByCategories(categories));
+ }
+}
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/dictionary/mapper/DogamMapper.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/dictionary/mapper/DogamMapper.java
new file mode 100644
index 0000000..4182e92
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/dictionary/mapper/DogamMapper.java
@@ -0,0 +1,30 @@
+package blisgo.infrastructure.internal.persistence.dictionary.mapper;
+
+import blisgo.domain.dictionary.Dogam;
+import blisgo.infrastructure.internal.persistence.base.PersistenceMapper;
+import blisgo.infrastructure.internal.persistence.dictionary.model.JpaDogam;
+import blisgo.infrastructure.internal.ui.response.DogamDTO;
+import lombok.RequiredArgsConstructor;
+import org.modelmapper.ModelMapper;
+import org.springframework.stereotype.Component;
+
+@Component
+@RequiredArgsConstructor
+public class DogamMapper implements PersistenceMapper {
+ private final ModelMapper mapper;
+
+ @Override
+ public JpaDogam toEntity(Dogam domain) {
+ return mapper.map(domain, JpaDogam.class);
+ }
+
+ @Override
+ public Dogam toDomain(JpaDogam entity) {
+ return mapper.map(entity, Dogam.class);
+ }
+
+ @Override
+ public DogamDTO toDTO(Dogam domain) {
+ return mapper.map(domain, DogamDTO.class);
+ }
+}
\ No newline at end of file
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/dictionary/mapper/GuideMapper.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/dictionary/mapper/GuideMapper.java
new file mode 100644
index 0000000..854a78b
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/dictionary/mapper/GuideMapper.java
@@ -0,0 +1,30 @@
+package blisgo.infrastructure.internal.persistence.dictionary.mapper;
+
+import blisgo.domain.dictionary.vo.Guide;
+import blisgo.infrastructure.internal.persistence.base.PersistenceMapper;
+import blisgo.infrastructure.internal.persistence.dictionary.model.JpaGuide;
+import blisgo.infrastructure.internal.ui.response.GuideDTO;
+import lombok.RequiredArgsConstructor;
+import org.modelmapper.ModelMapper;
+import org.springframework.stereotype.Component;
+
+@Component
+@RequiredArgsConstructor
+public class GuideMapper implements PersistenceMapper {
+ private final ModelMapper mapper;
+
+ @Override
+ public JpaGuide toEntity(Guide domain) {
+ return mapper.map(domain, JpaGuide.class);
+ }
+
+ @Override
+ public Guide toDomain(JpaGuide entity) {
+ return mapper.map(entity, Guide.class);
+ }
+
+ @Override
+ public GuideDTO toDTO(Guide domain) {
+ return mapper.map(domain, GuideDTO.class);
+ }
+}
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/dictionary/mapper/WasteMapper.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/dictionary/mapper/WasteMapper.java
new file mode 100644
index 0000000..c468fca
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/dictionary/mapper/WasteMapper.java
@@ -0,0 +1,30 @@
+package blisgo.infrastructure.internal.persistence.dictionary.mapper;
+
+import blisgo.domain.dictionary.Waste;
+import blisgo.infrastructure.internal.persistence.base.PersistenceMapper;
+import blisgo.infrastructure.internal.persistence.dictionary.model.JpaWaste;
+import blisgo.infrastructure.internal.ui.response.WasteDTO;
+import lombok.RequiredArgsConstructor;
+import org.modelmapper.ModelMapper;
+import org.springframework.stereotype.Component;
+
+@Component
+@RequiredArgsConstructor
+public class WasteMapper implements PersistenceMapper {
+ private final ModelMapper mapper;
+
+ @Override
+ public JpaWaste toEntity(Waste domain) {
+ return mapper.map(domain, JpaWaste.class);
+ }
+
+ @Override
+ public Waste toDomain(JpaWaste entity) {
+ return mapper.map(entity, Waste.class);
+ }
+
+ @Override
+ public WasteDTO toDTO(Waste domain) {
+ return mapper.map(domain, WasteDTO.class);
+ }
+}
\ No newline at end of file
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/dictionary/model/JpaDogam.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/dictionary/model/JpaDogam.java
new file mode 100644
index 0000000..619511e
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/dictionary/model/JpaDogam.java
@@ -0,0 +1,25 @@
+package blisgo.infrastructure.internal.persistence.dictionary.model;
+
+import blisgo.infrastructure.internal.persistence.common.BaseTimeEntity;
+import jakarta.persistence.*;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+import org.hibernate.annotations.Comment;
+
+@Getter
+@SuperBuilder(toBuilder = true)
+@NoArgsConstructor
+@Entity
+@Table(name = "dogam")
+@Comment("도감")
+public class JpaDogam extends BaseTimeEntity {
+ @EmbeddedId
+ private JpaDogamId dogamId;
+
+ @MapsId("wasteId")
+ @OneToOne(fetch = FetchType.EAGER)
+ @JoinColumn(name = "waste_id")
+ @Comment("폐기물 번호(PK, FK)")
+ private JpaWaste waste;
+}
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/dictionary/model/JpaDogamId.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/dictionary/model/JpaDogamId.java
new file mode 100644
index 0000000..be9c2a2
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/dictionary/model/JpaDogamId.java
@@ -0,0 +1,21 @@
+package blisgo.infrastructure.internal.persistence.dictionary.model;
+
+import jakarta.persistence.Embeddable;
+import lombok.*;
+import org.hibernate.annotations.Comment;
+
+import java.io.Serializable;
+import java.util.UUID;
+
+@Getter
+@Embeddable
+@EqualsAndHashCode
+@AllArgsConstructor(staticName = "of")
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+public class JpaDogamId implements Serializable {
+ @Comment("회원 memberId(FK)")
+ private UUID memberId;
+
+ @Comment("폐기물 wasteId(FK)")
+ private Long wasteId;
+}
\ No newline at end of file
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/dictionary/model/JpaGuide.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/dictionary/model/JpaGuide.java
new file mode 100644
index 0000000..3d63deb
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/dictionary/model/JpaGuide.java
@@ -0,0 +1,30 @@
+package blisgo.infrastructure.internal.persistence.dictionary.model;
+
+import blisgo.domain.dictionary.vo.Category;
+import blisgo.infrastructure.internal.persistence.common.JpaPicture;
+import jakarta.persistence.*;
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import org.hibernate.annotations.Comment;
+
+@Getter
+@Entity
+@Table(name = "guide")
+@AllArgsConstructor(staticName = "of")
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+public class JpaGuide {
+ @Id
+ @Enumerated(EnumType.STRING)
+ @Comment("카테고리(PK)")
+ private Category category;
+
+ @Lob
+ @Comment("폐기물 처리 안내")
+ private String content;
+
+ @Embedded
+ @AttributeOverride(name = "url", column = @Column(name = "picture"))
+ private JpaPicture picture;
+}
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/dictionary/model/JpaWaste.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/dictionary/model/JpaWaste.java
new file mode 100644
index 0000000..37dc01f
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/dictionary/model/JpaWaste.java
@@ -0,0 +1,61 @@
+package blisgo.infrastructure.internal.persistence.dictionary.model;
+
+import blisgo.domain.dictionary.vo.Category;
+import blisgo.infrastructure.internal.persistence.common.BaseTimeEntity;
+import blisgo.infrastructure.internal.persistence.common.JpaPicture;
+import jakarta.persistence.*;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+import org.hibernate.annotations.ColumnDefault;
+import org.hibernate.annotations.Comment;
+import org.hibernate.validator.constraints.Range;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Getter
+@SuperBuilder(toBuilder = true)
+@NoArgsConstructor
+@Entity
+@Table(name = "waste")
+@Comment("폐기물")
+public class JpaWaste extends BaseTimeEntity {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Comment("폐기물 Id")
+ private Long wasteId;
+
+ @Column(length = 45)
+ @Comment("이름")
+ private String name;
+
+ @Column(length = 45)
+ @Comment("유형")
+ private String type;
+
+ @Embedded
+ @AttributeOverride(name = "url", column = @Column(name = "picture"))
+ private JpaPicture picture;
+
+ @Lob
+ @Basic(fetch = FetchType.LAZY)
+ @Comment("처리 안내")
+ private String treatment;
+
+
+ @Range(min = 1, max = 10)
+ @ColumnDefault("5")
+ @Comment("인지도")
+ private Short popularity;
+
+ @Comment("조회 수")
+ @ColumnDefault("0")
+ private Long views;
+
+ @ElementCollection
+ @CollectionTable(name = "waste_categories", joinColumns = @JoinColumn(name = "waste_id"))
+ @Enumerated(EnumType.STRING)
+ @Comment("폐기 분류")
+ private final List categories = new ArrayList<>();
+}
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/dictionary/repository/DogamCustomRepository.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/dictionary/repository/DogamCustomRepository.java
new file mode 100644
index 0000000..7fd0107
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/dictionary/repository/DogamCustomRepository.java
@@ -0,0 +1,13 @@
+package blisgo.infrastructure.internal.persistence.dictionary.repository;
+
+import com.querydsl.jpa.impl.JPAQueryFactory;
+import lombok.RequiredArgsConstructor;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Repository;
+
+@Repository
+@RequiredArgsConstructor
+public class DogamCustomRepository {
+ private final JPAQueryFactory jpaQueryFactory;
+ private final JdbcTemplate jdbcTemplate;
+}
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/dictionary/repository/DogamJpaRepository.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/dictionary/repository/DogamJpaRepository.java
new file mode 100644
index 0000000..fb5bc87
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/dictionary/repository/DogamJpaRepository.java
@@ -0,0 +1,18 @@
+package blisgo.infrastructure.internal.persistence.dictionary.repository;
+
+import blisgo.infrastructure.internal.persistence.dictionary.model.JpaDogam;
+import blisgo.infrastructure.internal.persistence.dictionary.model.JpaDogamId;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Slice;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.Optional;
+import java.util.UUID;
+
+public interface DogamJpaRepository extends JpaRepository {
+ Optional findFirstByDogamId(JpaDogamId dogamId);
+
+ Slice findByDogamIdMemberId(UUID memberId, Pageable pageable);
+
+ boolean deleteByDogamId(JpaDogamId dogamId);
+}
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/dictionary/repository/GuideJpaRepository.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/dictionary/repository/GuideJpaRepository.java
new file mode 100644
index 0000000..879f53a
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/dictionary/repository/GuideJpaRepository.java
@@ -0,0 +1,8 @@
+package blisgo.infrastructure.internal.persistence.dictionary.repository;
+
+import blisgo.domain.dictionary.vo.Category;
+import blisgo.infrastructure.internal.persistence.dictionary.model.JpaGuide;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface GuideJpaRepository extends JpaRepository {
+}
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/dictionary/repository/WasteCustomRepository.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/dictionary/repository/WasteCustomRepository.java
new file mode 100644
index 0000000..3249c3c
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/dictionary/repository/WasteCustomRepository.java
@@ -0,0 +1,79 @@
+package blisgo.infrastructure.internal.persistence.dictionary.repository;
+
+import blisgo.domain.dictionary.vo.Category;
+import blisgo.infrastructure.internal.persistence.base.NoOffsetSliceHelper;
+import blisgo.infrastructure.internal.persistence.dictionary.model.JpaWaste;
+import com.querydsl.core.types.Projections;
+import com.querydsl.core.types.dsl.Expressions;
+import com.querydsl.jpa.impl.JPAQueryFactory;
+import lombok.RequiredArgsConstructor;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Slice;
+import org.springframework.data.domain.SliceImpl;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Repository;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.UUID;
+
+import static blisgo.infrastructure.internal.persistence.dictionary.model.QJpaDogam.jpaDogam;
+import static blisgo.infrastructure.internal.persistence.dictionary.model.QJpaWaste.jpaWaste;
+
+@Repository
+@RequiredArgsConstructor
+public class WasteCustomRepository {
+ private final JPAQueryFactory jpaQueryFactory;
+ private final JdbcTemplate jdbcTemplate;
+
+ public Slice findWastesByMemberIdFromDogam(UUID memberId, Pageable pageable, LocalDateTime lastDogamCreatedDate) {
+ var results = jpaQueryFactory.selectFrom(jpaWaste)
+ .join(jpaDogam).on(jpaWaste.wasteId.eq(jpaDogam.dogamId.wasteId))
+ .where(jpaDogam.dogamId.memberId.eq(memberId))
+ .where(jpaWaste.createdDate.lt(lastDogamCreatedDate))
+ .orderBy(jpaWaste.createdDate.desc())
+ .limit(pageable.getPageSize() + 1L)
+ .fetch();
+
+ boolean hasNext = NoOffsetSliceHelper.checkLastPage(results, pageable);
+
+ return new SliceImpl<>(results, pageable, hasNext);
+ }
+
+ public Slice findPartial(Pageable pageable, long lastWasteId) {
+ var fields = Projections.fields(
+ JpaWaste.class,
+ jpaWaste.wasteId,
+ jpaWaste.name,
+ jpaWaste.picture
+ );
+
+ var results = jpaQueryFactory.select(fields)
+ .from(jpaWaste)
+ .where(jpaWaste.wasteId.gt(lastWasteId))
+ .orderBy(jpaWaste.wasteId.asc())
+ .limit(pageable.getPageSize() + 1L)
+ .fetch();
+
+ boolean hasNext = NoOffsetSliceHelper.checkLastPage(results, pageable);
+
+ return new SliceImpl<>(results, pageable, hasNext);
+ }
+
+ public List findWastesByCategories(List categories) {
+ var fields = Projections.fields(
+ JpaWaste.class,
+ jpaWaste.wasteId,
+ jpaWaste.name,
+ jpaWaste.picture
+ );
+
+ return jpaQueryFactory.select(fields)
+ .from(jpaWaste)
+ .where(jpaWaste.categories.any().in(categories))
+ .orderBy(Expressions.numberTemplate(Integer.class, "function('rand')").asc())
+ .limit(4)
+ .fetch();
+ }
+
+}
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/dictionary/repository/WasteJpaRepository.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/dictionary/repository/WasteJpaRepository.java
new file mode 100644
index 0000000..14dcc8c
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/dictionary/repository/WasteJpaRepository.java
@@ -0,0 +1,10 @@
+package blisgo.infrastructure.internal.persistence.dictionary.repository;
+
+import blisgo.infrastructure.internal.persistence.dictionary.model.JpaWaste;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.Optional;
+
+public interface WasteJpaRepository extends JpaRepository {
+ Optional findFirstByWasteId(Long wasteId);
+}
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/member/MemberMySQLAdapter.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/member/MemberMySQLAdapter.java
new file mode 100644
index 0000000..c430deb
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/member/MemberMySQLAdapter.java
@@ -0,0 +1,58 @@
+package blisgo.infrastructure.internal.persistence.member;
+
+import blisgo.domain.member.Member;
+import blisgo.infrastructure.internal.persistence.member.mapper.MemberMapper;
+import blisgo.infrastructure.internal.persistence.member.repository.MemberCustomRepository;
+import blisgo.infrastructure.internal.persistence.member.repository.MemberJpaRepository;
+import blisgo.usecase.port.MemberOutputPort;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Map;
+
+@Slf4j
+@Component
+@Transactional(readOnly = true)
+@RequiredArgsConstructor
+public class MemberMySQLAdapter implements MemberOutputPort {
+ private final MemberJpaRepository jpaRepository;
+ private final MemberCustomRepository customRepository;
+ private final MemberMapper mapper;
+
+ @Override
+ @Transactional
+ public boolean update(Member domain) {
+ var jpaMember = mapper.toEntity(domain);
+
+ jpaRepository.findFirstByEmail(jpaMember.email()).ifPresentOrElse(
+ existingMember -> existingMember.updateInfo(jpaMember),
+ () -> jpaRepository.save(jpaMember)
+ );
+
+ return true;
+ }
+
+ @Override
+ @Transactional
+ public boolean delete(String email) {
+ return jpaRepository.deleteByEmail(email);
+ }
+
+ @Override
+ @Transactional
+ public boolean create(Member domain) {
+ jpaRepository.save(mapper.toEntity(domain));
+ return true;
+ }
+
+ @Override
+ public Member read(Map columns) {
+ String email = String.valueOf(columns.get("email"));
+
+ return jpaRepository.findFirstByEmail(email)
+ .map(mapper::toDomain)
+ .orElseThrow(() -> new IllegalArgumentException("해당 회원이 존재하지 않습니다: " + email));
+ }
+}
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/member/mapper/MemberMapper.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/member/mapper/MemberMapper.java
new file mode 100644
index 0000000..2d23435
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/member/mapper/MemberMapper.java
@@ -0,0 +1,30 @@
+package blisgo.infrastructure.internal.persistence.member.mapper;
+
+import blisgo.domain.member.Member;
+import blisgo.infrastructure.internal.persistence.base.PersistenceMapper;
+import blisgo.infrastructure.internal.persistence.member.model.JpaMember;
+import blisgo.infrastructure.internal.ui.response.MemberDTO;
+import lombok.RequiredArgsConstructor;
+import org.modelmapper.ModelMapper;
+import org.springframework.stereotype.Component;
+
+@Component
+@RequiredArgsConstructor
+public class MemberMapper implements PersistenceMapper {
+ private final ModelMapper mapper;
+
+ @Override
+ public JpaMember toEntity(Member domain) {
+ return mapper.map(domain, JpaMember.class);
+ }
+
+ @Override
+ public Member toDomain(JpaMember entity) {
+ return mapper.map(entity, Member.class);
+ }
+
+ @Override
+ public MemberDTO toDTO(Member domain) {
+ return mapper.map(domain, MemberDTO.class);
+ }
+}
\ No newline at end of file
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/member/model/JpaMember.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/member/model/JpaMember.java
new file mode 100644
index 0000000..f1c7e04
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/member/model/JpaMember.java
@@ -0,0 +1,45 @@
+package blisgo.infrastructure.internal.persistence.member.model;
+
+import blisgo.infrastructure.internal.persistence.common.BaseTimeEntity;
+import blisgo.infrastructure.internal.persistence.common.JpaPicture;
+import jakarta.persistence.*;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+import org.hibernate.annotations.Comment;
+import org.hibernate.annotations.DynamicInsert;
+import org.hibernate.annotations.DynamicUpdate;
+
+import java.util.UUID;
+
+@Getter
+@SuperBuilder(toBuilder = true)
+@NoArgsConstructor
+@AllArgsConstructor
+@Entity
+@DynamicUpdate
+@DynamicInsert
+@Table(name = "member")
+@Comment("회원")
+public class JpaMember extends BaseTimeEntity {
+ @Id
+ @Comment("ID")
+ private UUID memberId;
+
+ @Column(unique = true)
+ @Comment("이름")
+ private String name;
+
+ @Comment("이메일")
+ private String email;
+
+ @Embedded
+ @AttributeOverride(name = "url", column = @Column(name = "picture"))
+ private JpaPicture picture;
+
+ public void updateInfo(JpaMember jpaMember) {
+ this.name = jpaMember.name();
+ this.picture = jpaMember.picture();
+ }
+}
\ No newline at end of file
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/member/repository/MemberCustomRepository.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/member/repository/MemberCustomRepository.java
new file mode 100644
index 0000000..42d6202
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/member/repository/MemberCustomRepository.java
@@ -0,0 +1,13 @@
+package blisgo.infrastructure.internal.persistence.member.repository;
+
+import com.querydsl.jpa.impl.JPAQueryFactory;
+import lombok.RequiredArgsConstructor;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Repository;
+
+@Repository
+@RequiredArgsConstructor
+public class MemberCustomRepository {
+ private final JPAQueryFactory jpaQueryFactory;
+ private final JdbcTemplate jdbcTemplate;
+}
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/member/repository/MemberJpaRepository.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/member/repository/MemberJpaRepository.java
new file mode 100644
index 0000000..fb51a9c
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/persistence/member/repository/MemberJpaRepository.java
@@ -0,0 +1,13 @@
+package blisgo.infrastructure.internal.persistence.member.repository;
+
+import blisgo.infrastructure.internal.persistence.member.model.JpaMember;
+import org.springframework.data.repository.CrudRepository;
+
+import java.util.Optional;
+import java.util.UUID;
+
+public interface MemberJpaRepository extends CrudRepository {
+ Optional findFirstByEmail(String email);
+
+ boolean deleteByEmail(String email);
+}
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/rest/JacksonConfig.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/rest/JacksonConfig.java
new file mode 100644
index 0000000..a070064
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/rest/JacksonConfig.java
@@ -0,0 +1,20 @@
+package blisgo.infrastructure.internal.rest;
+
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.PropertyNamingStrategies;
+import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class JacksonConfig {
+ @Bean
+ public Jackson2ObjectMapperBuilderCustomizer jacksonCustomizer() {
+ return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder
+ .featuresToEnable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)
+ .defaultViewInclusion(true)
+ .indentOutput(true)
+ .propertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE)
+ .build();
+ }
+}
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/rest/Uploadable.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/rest/Uploadable.java
new file mode 100644
index 0000000..31a3923
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/rest/Uploadable.java
@@ -0,0 +1,11 @@
+package blisgo.infrastructure.internal.rest;
+
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+@RestController
+public interface Uploadable {
+ @PostMapping("/upload")
+ String uploadAndReturnResourceUrl(final MultipartFile file);
+}
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/security/CustomOidcUserService.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/security/CustomOidcUserService.java
new file mode 100644
index 0000000..8cbb857
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/security/CustomOidcUserService.java
@@ -0,0 +1,40 @@
+package blisgo.infrastructure.internal.security;
+
+import blisgo.usecase.port.MemberInputPort;
+import blisgo.usecase.request.member.UpdateMember;
+import lombok.RequiredArgsConstructor;
+import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
+import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;
+import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
+import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
+import org.springframework.security.oauth2.core.oidc.user.OidcUser;
+import org.springframework.stereotype.Service;
+
+@Service
+@RequiredArgsConstructor
+public class CustomOidcUserService extends OidcUserService {
+ private final MemberInputPort usecase;
+
+ @Override
+ public OidcUser loadUser(OidcUserRequest oidcUserRequest) throws OAuth2AuthenticationException {
+ OidcUser oidcUser = super.loadUser(oidcUserRequest);
+
+ registerOrUpdate(oidcUser.getUserInfo());
+
+ return oidcUser;
+ }
+
+ public void registerOrUpdate(OidcUserInfo oidcUserInfo) {
+ String email = oidcUserInfo.getEmail();
+ String name = oidcUserInfo.getFullName();
+ String picture = oidcUserInfo.getPicture();
+
+ UpdateMember command = UpdateMember.builder()
+ .email(email)
+ .name(name)
+ .picture(picture)
+ .build();
+
+ usecase.updateMember(command);
+ }
+}
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/security/SecurityConfig.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/security/SecurityConfig.java
new file mode 100644
index 0000000..7388e09
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/security/SecurityConfig.java
@@ -0,0 +1,96 @@
+package blisgo.infrastructure.internal.security;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.authentication.logout.LogoutHandler;
+import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.CorsConfigurationSource;
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
+import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
+
+import java.io.IOException;
+import java.util.List;
+
+@RequiredArgsConstructor
+@EnableMethodSecurity
+@Configuration
+public class SecurityConfig {
+ private final CustomOidcUserService customOidcUserService;
+
+ @Value("${okta.oauth2.issuer}")
+ private String issuer;
+
+ @Value("${okta.oauth2.client-id}")
+ private String clientId;
+
+ @Bean
+ public SecurityFilterChain configure(HttpSecurity http) throws Exception {
+
+ http.headers(headers -> headers
+ .httpStrictTransportSecurity(hsts -> hsts
+ .includeSubDomains(true)
+ .preload(true)
+ .maxAgeInSeconds(31536000)
+ )
+ );
+
+ http.oauth2Login(oauth2Login -> oauth2Login
+ .loginPage("/oauth2/authorization/okta")
+ .defaultSuccessUrl("/", false)
+ .userInfoEndpoint(userInfoEndpoint -> userInfoEndpoint
+ .oidcUserService(customOidcUserService)
+ )
+ );
+
+ http.formLogin(AbstractHttpConfigurer::disable);
+ http.httpBasic(AbstractHttpConfigurer::disable);
+
+ http.csrf(AbstractHttpConfigurer::disable);
+
+ http.cors(cors -> cors.configurationSource(corsConfigurationSource()));
+
+ http.logout(logout -> logout
+ .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
+ .invalidateHttpSession(true)
+ .deleteCookies("JSESSIONID")
+ .clearAuthentication(true)
+ .addLogoutHandler(logoutHandler())
+ );
+
+ http.sessionManagement(session -> session
+ .maximumSessions(1)
+ .maxSessionsPreventsLogin(true)
+ );
+
+ return http.build();
+ }
+
+ private LogoutHandler logoutHandler() {
+ return (request, response, authentication) -> {
+ try {
+ String baseUrl = ServletUriComponentsBuilder.fromCurrentContextPath().build().toUriString();
+ response.sendRedirect(issuer + "v2/logout?client_id=" + clientId + "&returnTo=" + baseUrl);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ };
+ }
+
+ @Bean
+ CorsConfigurationSource corsConfigurationSource() {
+ CorsConfiguration configuration = new CorsConfiguration();
+ configuration.setAllowedOrigins(List.of("*"));
+ configuration.setAllowedMethods(List.of("*"));
+ configuration.setAllowedHeaders(List.of("*"));
+ UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
+ source.registerCorsConfiguration("/**", configuration);
+ return source;
+ }
+}
\ No newline at end of file
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/base/CustomServletConfig.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/base/CustomServletConfig.java
new file mode 100644
index 0000000..ce9369b
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/base/CustomServletConfig.java
@@ -0,0 +1,16 @@
+package blisgo.infrastructure.internal.ui.base;
+
+import org.springframework.boot.web.server.MimeMappings;
+import org.springframework.boot.web.server.WebServerFactoryCustomizer;
+import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class CustomServletConfig implements WebServerFactoryCustomizer {
+ @Override
+ public void customize(ConfigurableServletWebServerFactory factory) {
+ MimeMappings mappings = new MimeMappings(MimeMappings.DEFAULT);
+ mappings.add("mjs", "application/javascript;charset=utf-8");
+ factory.setMimeMappings(mappings);
+ }
+}
\ No newline at end of file
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/base/Router.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/base/Router.java
new file mode 100644
index 0000000..7df9711
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/base/Router.java
@@ -0,0 +1,32 @@
+package blisgo.infrastructure.internal.ui.base;
+
+import java.util.StringJoiner;
+
+public class Router {
+ protected String routes(Object... strings) {
+ StringJoiner sj = new StringJoiner("/", "/", "");
+
+ for (Object str : strings) {
+ sj.add(str.toString().toLowerCase());
+ }
+
+ return sj.toString();
+ }
+
+ protected String fragment(Object target) {
+ return "::" + target.toString().toLowerCase();
+ }
+
+ protected enum Folder {
+ COMMUNITY, DICTIONARY, MEMBER
+ }
+
+ protected enum Page {
+ INDEX, PROFILE, CATALOGUE, INFO, BOARD, CONTENT, WRITE, EDIT
+ }
+
+ public enum Fragment {
+ POSTS, DOGAMS, MEMBER, POST, DICTIONARIES, WASTE, WASTES, WASTE_RELATED, REPLIES
+ }
+
+}
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/base/TimeDiffUtil.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/base/TimeDiffUtil.java
new file mode 100644
index 0000000..09494e2
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/base/TimeDiffUtil.java
@@ -0,0 +1,42 @@
+package blisgo.infrastructure.internal.ui.base;
+
+import lombok.experimental.UtilityClass;
+
+import java.time.Duration;
+import java.time.LocalDateTime;
+
+@UtilityClass
+public class TimeDiffUtil {
+ public static String calcTimeDiff(LocalDateTime localDateTime) {
+ LocalDateTime now = LocalDateTime.now();
+ Duration duration = Duration.between(localDateTime, now);
+
+ long diffSeconds = duration.getSeconds();
+ if (diffSeconds < 60) {
+ return diffSeconds + "초 전";
+ }
+
+ long diffMinutes = diffSeconds / 60;
+ if (diffMinutes < 60) {
+ return diffMinutes + "분 전";
+ }
+
+ long diffHours = diffMinutes / 60;
+ if (diffHours < 24) {
+ return diffHours + "시간 전";
+ }
+
+ long diffDays = diffHours / 24;
+ if (diffDays < 30) {
+ return diffDays + "일 전";
+ }
+
+ long diffMonths = diffDays / 30;
+ if (diffMonths < 12) {
+ return diffMonths + "개월 전";
+ }
+
+ long diffYears = diffMonths / 12;
+ return diffYears + "년 전";
+ }
+}
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/component/UnsplashClient.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/component/UnsplashClient.java
new file mode 100644
index 0000000..810e083
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/component/UnsplashClient.java
@@ -0,0 +1,71 @@
+package blisgo.infrastructure.internal.ui.component;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.nio.channels.Channels;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Optional;
+
+@Slf4j
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class UnsplashClient {
+ static final String HOST = "https://api.unsplash.com/photos/random";
+ static final String QUERY = "waste,garbage,trash,recycling";
+ static final String OPTIONS = "&fm=webp&w=1500&q=50&blur=50";
+ static final String CLIENT_ID = "CTW7rq3n5wwaqHphLTlv47RsPHweBqy4QWe7_YVCvk8";
+
+ public static void changeWallpaper() throws IOException, InterruptedException {
+ Optional imageUrl = Optional.of(getImageUrl());
+ replaceImage(imageUrl.get());
+ }
+
+
+ public static String getImageUrl() throws IOException, InterruptedException {
+ String link = String.format(HOST + "?query=" + QUERY + "&client_id=" + CLIENT_ID);
+ HttpRequest request = HttpRequest.newBuilder().uri(URI.create(link)).method("GET", HttpRequest.BodyPublishers.noBody()).build();
+
+ try (HttpClient client = HttpClient.newHttpClient()) {
+ HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
+
+ ObjectMapper mapper = new ObjectMapper();
+ JsonNode jsonObj = mapper.readTree(response.body());
+ jsonObj = jsonObj.get("urls");
+
+ return jsonObj.get("raw").asText().concat(OPTIONS);
+ }
+ }
+
+ private static void replaceImage(String editedImageLink) throws IOException, InterruptedException {
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(URI.create(editedImageLink))
+ .build();
+
+ try (HttpClient client = HttpClient.newHttpClient()) {
+ HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofInputStream());
+
+ ReadableByteChannel rbc = Channels.newChannel(response.body());
+ Resource resource = new ClassPathResource("static/assets/img/index_wallpaper.webp");
+ Path wallpaperDir = Paths.get(resource.getURI());
+ log.info("파일 위치>" + wallpaperDir);
+
+ try (FileOutputStream fos = new FileOutputStream(wallpaperDir.toFile())) {
+ fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
+ }
+ }
+ }
+}
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/component/WallpaperChanger.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/component/WallpaperChanger.java
new file mode 100644
index 0000000..e40724c
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/component/WallpaperChanger.java
@@ -0,0 +1,21 @@
+package blisgo.infrastructure.internal.ui.component;
+
+import jakarta.annotation.PostConstruct;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import java.io.IOException;
+
+@Getter
+@Slf4j
+@Component
+public class WallpaperChanger {
+ private volatile String wallpaperUrl;
+
+ @PostConstruct
+ public void changeIndexWallpaperDaily() throws IOException, InterruptedException {
+ wallpaperUrl = UnsplashClient.getImageUrl();
+ }
+
+}
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/render/DogamRender.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/render/DogamRender.java
new file mode 100644
index 0000000..4fc8661
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/render/DogamRender.java
@@ -0,0 +1,70 @@
+package blisgo.infrastructure.internal.ui.render;
+
+import blisgo.infrastructure.internal.persistence.dictionary.mapper.WasteMapper;
+import blisgo.infrastructure.internal.ui.base.Router;
+import blisgo.usecase.request.dogam.AddDogam;
+import blisgo.usecase.request.dogam.DogamCommand;
+import blisgo.usecase.request.dogam.DogamQuery;
+import blisgo.usecase.request.dogam.GetDogam;
+import blisgo.usecase.request.waste.WasteQuery;
+import lombok.RequiredArgsConstructor;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.web.PageableDefault;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.security.core.annotation.AuthenticationPrincipal;
+import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+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.servlet.ModelAndView;
+
+import java.time.LocalDateTime;
+import java.util.Map;
+
+import static org.springframework.data.domain.Sort.Direction.DESC;
+
+@Controller
+@RequestMapping("/dogams")
+@RequiredArgsConstructor
+public class DogamRender extends Router {
+ private final DogamQuery queryUsecase;
+ private final DogamCommand commandUsecase;
+ private final WasteQuery wasteQuery;
+ private final WasteMapper wasteMapper;
+
+ // 도감 추가
+ @PostMapping
+ public void createDogam(AddDogam command) {
+ commandUsecase.addDogam(command);
+ }
+
+ // 도감 삭제
+
+ @GetMapping
+ @PreAuthorize("isAuthenticated()")
+ public ModelAndView dogam(
+ @AuthenticationPrincipal DefaultOidcUser oidcUser,
+ @PageableDefault(size = 12, sort = "createdDate", direction = DESC) Pageable pageable,
+ @RequestParam(required = false) LocalDateTime lastDogamCreatedDate
+ ) {
+ if (lastDogamCreatedDate == null) {
+ lastDogamCreatedDate = LocalDateTime.now();
+ }
+
+ GetDogam query = GetDogam.builder()
+ .email(oidcUser.getEmail())
+ .pageable(pageable)
+ .lastDogamCreatedDate(lastDogamCreatedDate)
+ .build();
+
+ var wastes = wasteQuery.getWastesFromDogam(query);
+
+ return new ModelAndView(
+ routes(Folder.MEMBER, Page.PROFILE) + fragment(Fragment.WASTES),
+ Map.of("wastes", wastes.map(wasteMapper::toDTO))
+ );
+
+ }
+}
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/render/MemberRender.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/render/MemberRender.java
new file mode 100644
index 0000000..c995051
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/render/MemberRender.java
@@ -0,0 +1,39 @@
+package blisgo.infrastructure.internal.ui.render;
+
+import blisgo.infrastructure.internal.persistence.member.mapper.MemberMapper;
+import blisgo.infrastructure.internal.ui.base.Router;
+import blisgo.usecase.request.member.GetMember;
+import blisgo.usecase.request.member.MemberQuery;
+import lombok.RequiredArgsConstructor;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.security.core.annotation.AuthenticationPrincipal;
+import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.servlet.ModelAndView;
+
+import java.util.Map;
+
+@Controller
+@RequestMapping("/members")
+@RequiredArgsConstructor
+public class MemberRender extends Router {
+ private final MemberQuery queryUsecase;
+ private final MemberMapper mapper;
+
+ @GetMapping
+ @PreAuthorize("isAuthenticated()")
+ public ModelAndView profile(@AuthenticationPrincipal DefaultOidcUser user) {
+ var query = GetMember.builder()
+ .email(user.getEmail())
+ .build();
+
+ var member = queryUsecase.getMember(query);
+
+ return new ModelAndView(
+ routes(Router.Folder.MEMBER, Router.Page.PROFILE) + fragment(Router.Fragment.MEMBER),
+ Map.of("member", mapper.toDTO(member))
+ );
+ }
+}
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/render/PostRender.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/render/PostRender.java
new file mode 100644
index 0000000..288f2a0
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/render/PostRender.java
@@ -0,0 +1,102 @@
+package blisgo.infrastructure.internal.ui.render;
+
+import blisgo.infrastructure.internal.persistence.community.mapper.PostMapper;
+import blisgo.infrastructure.internal.ui.base.Router;
+import blisgo.usecase.request.post.*;
+import lombok.RequiredArgsConstructor;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.web.PageableDefault;
+import org.springframework.http.HttpStatus;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.servlet.ModelAndView;
+import org.springframework.web.servlet.view.RedirectView;
+
+import java.util.Map;
+
+import static org.springframework.data.domain.Sort.Direction.DESC;
+
+@Controller
+@RequestMapping("/posts")
+@RequiredArgsConstructor
+public class PostRender extends Router {
+ private final PostCommand commandUsecase;
+ private final PostQuery queryUsecase;
+ private final PostMapper mapper;
+
+ @GetMapping
+ public ModelAndView posts(
+ @PageableDefault(sort = "createdDate", direction = DESC) Pageable pageable,
+ @RequestParam(required = false, defaultValue = "" + Long.MAX_VALUE) Long lastPostId,
+ @RequestParam(required = false) Long postId
+ ) {
+ if (postId == null) {
+ var query = GetPost.builder()
+ .pageable(pageable)
+ .postId(lastPostId)
+ .build();
+
+ var posts = queryUsecase.getPosts(query);
+
+ return new ModelAndView(
+ routes(Folder.COMMUNITY, Page.BOARD) + fragment(Fragment.POSTS),
+ Map.of("posts", posts.map(mapper::toDTO))
+ );
+ } else {
+ var query = GetPost.builder()
+ .postId(postId)
+ .build();
+
+ var post = queryUsecase.getPost(query);
+
+ return new ModelAndView(
+ routes(Folder.COMMUNITY, Page.CONTENT) + fragment(Fragment.POST),
+ Map.ofEntries(
+ Map.entry("post", mapper.toDTO(post)),
+ Map.entry("readOnly", true)
+ )
+ );
+ }
+ }
+
+ @PostMapping
+ @PatchMapping
+ public RedirectView updateOrCreatePost(UpdatePost command) {
+ if (command.postId() == null)
+ commandUsecase.addPost(new AddPost(command.title(), command.content()));
+ else {
+ commandUsecase.updatePost(command);
+ }
+
+ return new RedirectView(routes(Folder.COMMUNITY), false);
+ }
+
+ @DeleteMapping("/{postId}")
+ public RedirectView removePost(@PathVariable long postId) {
+ commandUsecase.removePost(new RemovePost(postId));
+
+ return new RedirectView(routes(Folder.COMMUNITY), false);
+ }
+
+ @PostMapping("/{postId}/like")
+ @ResponseStatus(HttpStatus.OK)
+ public void like(@PathVariable Long postId) {
+ PostLike command = PostLike.builder()
+ .postId(postId)
+ .isLike(true)
+ .build();
+
+ commandUsecase.like(command);
+ }
+
+ @PostMapping("/{postId}/dislike")
+ @ResponseStatus(HttpStatus.OK)
+ public void dislike(@PathVariable Long postId) {
+ PostLike command = PostLike.builder()
+ .postId(postId)
+ .isLike(false)
+ .build();
+
+ commandUsecase.like(command);
+ }
+}
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/render/ReplyRender.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/render/ReplyRender.java
new file mode 100644
index 0000000..4217b1d
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/render/ReplyRender.java
@@ -0,0 +1,78 @@
+package blisgo.infrastructure.internal.ui.render;
+
+import blisgo.infrastructure.internal.persistence.community.mapper.ReplyMapper;
+import blisgo.infrastructure.internal.ui.base.Router;
+import blisgo.infrastructure.internal.ui.response.ReplyDTO;
+import blisgo.usecase.request.reply.*;
+import lombok.RequiredArgsConstructor;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.web.PageableDefault;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.servlet.ModelAndView;
+
+import java.util.Map;
+
+import static org.springframework.data.domain.Sort.Direction.ASC;
+
+@Controller
+@RequestMapping("/replies")
+@RequiredArgsConstructor
+public class ReplyRender extends Router {
+ private final ReplyCommand commandUsecase;
+ private final ReplyQuery queryUsecase;
+ private final ReplyMapper mapper;
+
+
+ @GetMapping("/{postId}")
+ public ModelAndView replies(
+ @PathVariable Long postId,
+ @PageableDefault(sort = "createdDate", direction = ASC) Pageable pageable,
+ @RequestParam(required = false, defaultValue = "0") Long lastReplyId
+ ) {
+ GetReply query = GetReply.builder()
+ .postId(postId)
+ .lastReplyId(lastReplyId)
+ .pageable(pageable)
+ .build();
+
+ return new ModelAndView(
+ routes(Router.Folder.COMMUNITY, Router.Page.CONTENT) + fragment(Router.Fragment.REPLIES),
+ Map.of("replies", queryUsecase.getReplies(query)
+ .map(mapper::toDTO)
+ .map(ReplyDTO::withTimeDiff)
+ )
+ );
+ }
+
+ @PostMapping("/{postId}")
+ @PreAuthorize("isAuthenticated()")
+ public ModelAndView create(
+ @PathVariable Long postId,
+ @RequestParam String content
+ ) {
+ AddReply command = AddReply.builder()
+ .postId(postId)
+ .content(content)
+ .build();
+
+ commandUsecase.addReply(command);
+
+ return new ModelAndView(
+ routes(Router.Folder.COMMUNITY, Router.Page.CONTENT) + fragment(Router.Fragment.REPLIES)
+ );
+ }
+
+ @DeleteMapping("/{replyId}")
+ @PreAuthorize("isAuthenticated()")
+ public ModelAndView delete(
+ @PathVariable Long replyId
+ ) {
+ commandUsecase.removeReply(new RemoveReply(replyId));
+
+ return new ModelAndView(
+ routes(Router.Folder.COMMUNITY, Router.Page.CONTENT) + fragment(Router.Fragment.REPLIES)
+ );
+ }
+}
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/render/WasteRender.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/render/WasteRender.java
new file mode 100644
index 0000000..32a1760
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/render/WasteRender.java
@@ -0,0 +1,66 @@
+package blisgo.infrastructure.internal.ui.render;
+
+import blisgo.infrastructure.internal.persistence.dictionary.mapper.GuideMapper;
+import blisgo.infrastructure.internal.persistence.dictionary.mapper.WasteMapper;
+import blisgo.infrastructure.internal.ui.base.Router;
+import blisgo.usecase.request.waste.GetWaste;
+import blisgo.usecase.request.waste.WasteQuery;
+import lombok.RequiredArgsConstructor;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.web.PageableDefault;
+import org.springframework.stereotype.Controller;
+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.servlet.ModelAndView;
+
+import java.util.Map;
+
+import static org.springframework.data.domain.Sort.Direction.ASC;
+
+@Controller
+@RequestMapping("/wastes")
+@RequiredArgsConstructor
+public class WasteRender extends Router {
+ private final WasteQuery queryUsecase;
+ private final WasteMapper wasteMapper;
+ private final GuideMapper guideMapper;
+
+ @GetMapping
+ public ModelAndView wastes(
+ @PageableDefault(size = 24, sort = "wasteId", direction = ASC) Pageable pageable,
+ @RequestParam(required = false, defaultValue = "0") Long lastWasteId,
+ @RequestParam(required = false) Long wasteId
+ ) {
+ if (wasteId == null) {
+ GetWaste query = GetWaste.builder()
+ .pageable(pageable)
+ .lastWasteId(lastWasteId)
+ .build();
+
+ var wastes = queryUsecase.getWastes(query);
+
+ return new ModelAndView(
+ routes(Folder.DICTIONARY, Page.CATALOGUE) + fragment(Fragment.WASTES),
+ Map.of("wastes", wastes.map(wasteMapper::toDTO))
+ );
+ } else {
+ var query = GetWaste.builder()
+ .wasteId(wasteId)
+ .build();
+
+ var waste = queryUsecase.getWaste(query);
+ var guides = queryUsecase.getGuides(waste.categories());
+ var relatedWastes = queryUsecase.getWastesRelated(waste.categories());
+
+ return new ModelAndView(
+ routes(Folder.DICTIONARY, Page.INFO) + fragment(Fragment.WASTE),
+ Map.ofEntries(
+ Map.entry("waste", wasteMapper.toDTO(waste)),
+ Map.entry("guides", guideMapper.toDTOs(guides)),
+ Map.entry("relatedWastes", wasteMapper.toDTOs(relatedWastes))
+ )
+ );
+ }
+ }
+}
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/response/DogamDTO.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/response/DogamDTO.java
new file mode 100644
index 0000000..eddd59c
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/response/DogamDTO.java
@@ -0,0 +1,18 @@
+package blisgo.infrastructure.internal.ui.response;
+
+import blisgo.domain.dictionary.Waste;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.time.LocalDateTime;
+
+@Getter
+@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
+public class DogamDTO {
+ private Long memberId;
+ private Long wasteId;
+ private Waste waste;
+ private LocalDateTime createdDate;
+ private LocalDateTime modifiedDate;
+}
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/response/GuideDTO.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/response/GuideDTO.java
new file mode 100644
index 0000000..fd6b431
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/response/GuideDTO.java
@@ -0,0 +1,15 @@
+package blisgo.infrastructure.internal.ui.response;
+
+import blisgo.domain.common.Picture;
+import blisgo.domain.dictionary.vo.Category;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@Getter
+@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
+public class GuideDTO {
+ private Category category;
+ private String content;
+ private Picture picture;
+}
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/response/MemberDTO.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/response/MemberDTO.java
new file mode 100644
index 0000000..e7f11f9
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/response/MemberDTO.java
@@ -0,0 +1,21 @@
+package blisgo.infrastructure.internal.ui.response;
+
+import blisgo.domain.common.Picture;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.time.LocalDateTime;
+import java.util.UUID;
+
+@Getter
+@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
+public class MemberDTO {
+ private UUID id;
+ private String name;
+ private String email;
+ private Picture picture;
+ private LocalDateTime createdDate;
+ private LocalDateTime modifiedDate;
+}
+
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/response/PostDTO.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/response/PostDTO.java
new file mode 100644
index 0000000..662e02c
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/response/PostDTO.java
@@ -0,0 +1,23 @@
+package blisgo.infrastructure.internal.ui.response;
+
+import blisgo.domain.common.Author;
+import blisgo.domain.common.Content;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.time.LocalDateTime;
+
+@Getter
+@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
+public class PostDTO {
+ private Long postId;
+ private Author author;
+ private String title;
+ private Content content;
+ private Long views;
+ private Long likes;
+ private Long replies;
+ private LocalDateTime createdDate;
+ private LocalDateTime modifiedDate;
+}
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/response/ReplyDTO.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/response/ReplyDTO.java
new file mode 100644
index 0000000..e772a9f
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/response/ReplyDTO.java
@@ -0,0 +1,27 @@
+package blisgo.infrastructure.internal.ui.response;
+
+import blisgo.domain.common.Author;
+import blisgo.infrastructure.internal.ui.base.TimeDiffUtil;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.time.LocalDateTime;
+
+@Getter
+@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
+public class ReplyDTO {
+ private Long replyId;
+ private Long postId;
+ private Author author;
+ private String content;
+ private LocalDateTime createdDate;
+ private LocalDateTime modifiedDate;
+ private String timeDiff;
+
+ public ReplyDTO withTimeDiff() {
+ this.timeDiff = TimeDiffUtil.calcTimeDiff(this.createdDate);
+ return this;
+ }
+}
+
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/response/WasteDTO.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/response/WasteDTO.java
new file mode 100644
index 0000000..01feac8
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/response/WasteDTO.java
@@ -0,0 +1,25 @@
+package blisgo.infrastructure.internal.ui.response;
+
+import blisgo.domain.common.Picture;
+import blisgo.domain.dictionary.vo.Category;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Getter
+@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
+public class WasteDTO {
+ private Long wasteId;
+ private String name;
+ private String type;
+ private Picture picture;
+ private String treatment;
+ private Short popularity;
+ private Long views;
+ private List categories;
+ private LocalDateTime createdDate;
+ private LocalDateTime modifiedDate;
+}
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/view/CommunityView.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/view/CommunityView.java
new file mode 100644
index 0000000..6c0a7cf
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/view/CommunityView.java
@@ -0,0 +1,68 @@
+package blisgo.infrastructure.internal.ui.view;
+
+import blisgo.infrastructure.internal.persistence.community.mapper.PostMapper;
+import blisgo.infrastructure.internal.ui.base.Router;
+import blisgo.usecase.request.post.GetPost;
+import blisgo.usecase.request.post.PostQuery;
+import lombok.RequiredArgsConstructor;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.security.core.annotation.AuthenticationPrincipal;
+import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.servlet.ModelAndView;
+
+import java.util.Map;
+
+@Controller
+@RequestMapping("/community")
+@RequiredArgsConstructor
+public class CommunityView extends Router {
+ private final PostQuery queryUsecase;
+ private final PostMapper mapper;
+
+ @GetMapping
+ public ModelAndView board() {
+ return new ModelAndView(routes(Folder.COMMUNITY, Page.BOARD));
+ }
+
+ @GetMapping("/{postId}")
+ public ModelAndView content(@PathVariable Long postId) {
+ return new ModelAndView(
+ routes(Folder.COMMUNITY, Page.CONTENT),
+ Map.ofEntries(
+ Map.entry("postId", postId),
+ Map.entry("readOnly", false)
+ )
+ );
+ }
+
+ @GetMapping("/write")
+ @PreAuthorize("isAuthenticated()")
+ public ModelAndView write(
+ @AuthenticationPrincipal DefaultOidcUser oidcUser,
+ @RequestParam(required = false) Long postId,
+ Model model
+ ) {
+ if (postId != null) {
+ GetPost query = GetPost.builder()
+ .postId(postId)
+ .build();
+
+ var post = queryUsecase.getPost(query);
+
+ if (post != null && post.isAuthor(oidcUser.getEmail())) {
+ model.addAttribute("post", mapper.toDTO(post));
+ model.addAttribute("readOnly", false);
+ }
+ }
+
+ return new ModelAndView(
+ routes(Folder.COMMUNITY, Page.WRITE)
+ );
+ }
+}
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/view/DictionaryView.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/view/DictionaryView.java
new file mode 100644
index 0000000..a06f914
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/view/DictionaryView.java
@@ -0,0 +1,29 @@
+package blisgo.infrastructure.internal.ui.view;
+
+import blisgo.infrastructure.internal.ui.base.Router;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.servlet.ModelAndView;
+
+import java.util.Map;
+
+@Controller
+@RequestMapping("/dictionary")
+@RequiredArgsConstructor
+public class DictionaryView extends Router {
+ @GetMapping
+ public String dictionary() {
+ return routes(Folder.DICTIONARY, Page.CATALOGUE);
+ }
+
+ @GetMapping("/{wasteId}")
+ public ModelAndView product(@PathVariable Long wasteId) {
+ return new ModelAndView(
+ routes(Folder.DICTIONARY, Page.INFO),
+ Map.of("wasteId", wasteId)
+ );
+ }
+}
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/view/HomeView.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/view/HomeView.java
new file mode 100644
index 0000000..64b48c9
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/view/HomeView.java
@@ -0,0 +1,34 @@
+package blisgo.infrastructure.internal.ui.view;
+
+import blisgo.infrastructure.internal.ui.base.Router;
+import blisgo.infrastructure.internal.ui.component.WallpaperChanger;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.servlet.ModelAndView;
+import org.springframework.web.servlet.view.RedirectView;
+
+import java.util.Map;
+
+@Slf4j
+@RequiredArgsConstructor
+@Controller
+@RequestMapping("/")
+public class HomeView extends Router {
+ private final WallpaperChanger wallpaperChanger;
+
+ @GetMapping
+ public ModelAndView index() {
+ return new ModelAndView(
+ routes(Page.INDEX),
+ Map.of("wallpaper", wallpaperChanger.wallpaperUrl())
+ );
+ }
+
+ @GetMapping("/login")
+ public RedirectView login() {
+ return new RedirectView("/oauth2/authorization/okta");
+ }
+}
diff --git a/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/view/MemberView.java b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/view/MemberView.java
new file mode 100644
index 0000000..eae45c1
--- /dev/null
+++ b/infrastructure/internal/src/main/java/blisgo/infrastructure/internal/ui/view/MemberView.java
@@ -0,0 +1,17 @@
+package blisgo.infrastructure.internal.ui.view;
+
+import blisgo.infrastructure.internal.ui.base.Router;
+import lombok.RequiredArgsConstructor;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+
+@Controller
+@RequiredArgsConstructor
+public class MemberView extends Router {
+ @GetMapping("/profile")
+ @PreAuthorize("isAuthenticated()")
+ public String profile() {
+ return routes(Folder.MEMBER, Page.PROFILE);
+ }
+}
diff --git a/infrastructure/internal/src/main/resources/application-common.yml b/infrastructure/internal/src/main/resources/application-common.yml
new file mode 100644
index 0000000..e69de29
diff --git a/infrastructure/internal/src/main/resources/application-oauth.yml b/infrastructure/internal/src/main/resources/application-oauth.yml
new file mode 100644
index 0000000..31d9b8a
--- /dev/null
+++ b/infrastructure/internal/src/main/resources/application-oauth.yml
@@ -0,0 +1,13 @@
+okta:
+ oauth2:
+ issuer: https://${AUTH0_DOMAIN}/
+ client-id: ${AUTH0_CLIENT_ID}
+ client-secret: ${AUTH0_CLIENT_SECRET}
+ audience: https://${AUTH0_DOMAIN}/userinfo
+
+spring:
+ security:
+ oauth2:
+ resourceserver:
+ jwt:
+ issuer-uri: https://${AUTH0_DOMAIN}/
\ No newline at end of file
diff --git a/infrastructure/internal/src/main/resources/application-redis.yml b/infrastructure/internal/src/main/resources/application-redis.yml
new file mode 100644
index 0000000..9da0068
--- /dev/null
+++ b/infrastructure/internal/src/main/resources/application-redis.yml
@@ -0,0 +1,14 @@
+server:
+ servlet:
+ session:
+ cookie:
+ path: /
+ name: JSESSIONID
+ http-only: true
+ secure: true
+ timeout: 3600
+
+spring:
+ session:
+ redis:
+ namespace: spring:session
\ No newline at end of file
diff --git a/infrastructure/internal/src/main/resources/application-thymeleaf.yml b/infrastructure/internal/src/main/resources/application-thymeleaf.yml
new file mode 100644
index 0000000..989436b
--- /dev/null
+++ b/infrastructure/internal/src/main/resources/application-thymeleaf.yml
@@ -0,0 +1,34 @@
+spring:
+ mvc:
+ hiddenmethod:
+ filter:
+ enabled: true
+ thymeleaf:
+ prefix: classpath:/templates
+ check-template-location: true
+ suffix: .html
+ mode: HTML
+ servlet:
+ multipart:
+ maxRequestSize: 10MB
+ maxFileSize: 10MB
+
+server:
+ servlet:
+ encoding:
+ enabled: true
+ force: true
+ charset: UTF-8
+ session:
+ tracking-modes: cookie
+ cookie:
+ http-only: false
+ secure: true
+
+ error:
+ include-stacktrace: ALWAYS
+ include-exception: true
+ compression:
+ min-response-size: 512
+ mime-types: text/html,text/plain,text/xml,text/css,application/javascript,application/json
+ enabled: true
\ No newline at end of file
diff --git a/infrastructure/internal/src/main/resources/static/assets/css/cmmn/editor-js.css b/infrastructure/internal/src/main/resources/static/assets/css/cmmn/editor-js.css
new file mode 100644
index 0000000..366aaaa
--- /dev/null
+++ b/infrastructure/internal/src/main/resources/static/assets/css/cmmn/editor-js.css
@@ -0,0 +1,24 @@
+.ce-block__content, .ce-toolbar__content {
+ max-width: calc(100% - 30px) !important;
+}
+
+.cdx-block {
+ max-width: 100% !important;
+}
+
+.codex-editor__loader {
+ height: 38px !important;
+}
+
+.codex-editor__redactor {
+ padding-bottom: 38px !important;
+}
+
+.ce-toolbar__plus, .ce-toolbar__settings-btn {
+ background-color: var(--bs-light);
+}
+
+.cdx-search-field__input {
+ color: var(--bs-dark);
+}
+
diff --git a/infrastructure/internal/src/main/resources/static/assets/css/cmmn/style.css b/infrastructure/internal/src/main/resources/static/assets/css/cmmn/style.css
new file mode 100644
index 0000000..0e97c35
--- /dev/null
+++ b/infrastructure/internal/src/main/resources/static/assets/css/cmmn/style.css
@@ -0,0 +1,46 @@
+#accounticon-desktop {
+ cursor: pointer;
+}
+
+#accounticon-mobile {
+ cursor: pointer;
+}
+
+::-webkit-scrollbar {
+ display: none;
+}
+
+#post-title {
+ font-weight: bold;
+}
+
+#comment-table {
+ overflow-y: auto;
+ height: 50vh;
+}
+
+.ql-editor {
+ font-size: 16px;
+}
+
+.aa-DetachedCancelButton {
+ font-size: 0;
+}
+
+.aa-DetachedCancelButton::before {
+ content: '✕';
+ font-size: 1rem;
+}
+
+main {
+ padding-right: 12px;
+ padding-left: 12px;
+ padding-top: 68px;
+}
+
+#index {
+ padding-top: 0px;
+ padding-left: 0px;
+ padding-right: 0px;
+}
+
diff --git a/infrastructure/internal/src/main/resources/static/assets/img/favicon/16x16.png b/infrastructure/internal/src/main/resources/static/assets/img/favicon/16x16.png
new file mode 100644
index 0000000..bfb18fd
Binary files /dev/null and b/infrastructure/internal/src/main/resources/static/assets/img/favicon/16x16.png differ
diff --git a/infrastructure/internal/src/main/resources/static/assets/img/favicon/180x180.png b/infrastructure/internal/src/main/resources/static/assets/img/favicon/180x180.png
new file mode 100644
index 0000000..6cc2c01
Binary files /dev/null and b/infrastructure/internal/src/main/resources/static/assets/img/favicon/180x180.png differ
diff --git a/infrastructure/internal/src/main/resources/static/assets/img/favicon/192x192.png b/infrastructure/internal/src/main/resources/static/assets/img/favicon/192x192.png
new file mode 100644
index 0000000..f258c9d
Binary files /dev/null and b/infrastructure/internal/src/main/resources/static/assets/img/favicon/192x192.png differ
diff --git a/infrastructure/internal/src/main/resources/static/assets/img/favicon/32x32.png b/infrastructure/internal/src/main/resources/static/assets/img/favicon/32x32.png
new file mode 100644
index 0000000..9eaf6ae
Binary files /dev/null and b/infrastructure/internal/src/main/resources/static/assets/img/favicon/32x32.png differ
diff --git a/infrastructure/internal/src/main/resources/static/assets/img/favicon/512x512.png b/infrastructure/internal/src/main/resources/static/assets/img/favicon/512x512.png
new file mode 100644
index 0000000..b6d8691
Binary files /dev/null and b/infrastructure/internal/src/main/resources/static/assets/img/favicon/512x512.png differ
diff --git a/infrastructure/internal/src/main/resources/static/assets/img/screenshots/mobile_1.webp b/infrastructure/internal/src/main/resources/static/assets/img/screenshots/mobile_1.webp
new file mode 100644
index 0000000..5fc4474
Binary files /dev/null and b/infrastructure/internal/src/main/resources/static/assets/img/screenshots/mobile_1.webp differ
diff --git a/infrastructure/internal/src/main/resources/static/assets/img/screenshots/mobile_2.webp b/infrastructure/internal/src/main/resources/static/assets/img/screenshots/mobile_2.webp
new file mode 100644
index 0000000..cfd3608
Binary files /dev/null and b/infrastructure/internal/src/main/resources/static/assets/img/screenshots/mobile_2.webp differ
diff --git a/infrastructure/internal/src/main/resources/static/assets/img/screenshots/mobile_3.webp b/infrastructure/internal/src/main/resources/static/assets/img/screenshots/mobile_3.webp
new file mode 100644
index 0000000..262ee32
Binary files /dev/null and b/infrastructure/internal/src/main/resources/static/assets/img/screenshots/mobile_3.webp differ
diff --git a/infrastructure/internal/src/main/resources/static/assets/img/screenshots/pc_1.webp b/infrastructure/internal/src/main/resources/static/assets/img/screenshots/pc_1.webp
new file mode 100644
index 0000000..214886e
Binary files /dev/null and b/infrastructure/internal/src/main/resources/static/assets/img/screenshots/pc_1.webp differ
diff --git a/infrastructure/internal/src/main/resources/static/assets/img/screenshots/pc_2.webp b/infrastructure/internal/src/main/resources/static/assets/img/screenshots/pc_2.webp
new file mode 100644
index 0000000..93eb5dc
Binary files /dev/null and b/infrastructure/internal/src/main/resources/static/assets/img/screenshots/pc_2.webp differ
diff --git a/infrastructure/internal/src/main/resources/static/assets/img/screenshots/pc_3.webp b/infrastructure/internal/src/main/resources/static/assets/img/screenshots/pc_3.webp
new file mode 100644
index 0000000..62a764f
Binary files /dev/null and b/infrastructure/internal/src/main/resources/static/assets/img/screenshots/pc_3.webp differ
diff --git a/infrastructure/internal/src/main/resources/static/assets/js/bs-init.js b/infrastructure/internal/src/main/resources/static/assets/js/bs-init.js
new file mode 100644
index 0000000..fc46900
--- /dev/null
+++ b/infrastructure/internal/src/main/resources/static/assets/js/bs-init.js
@@ -0,0 +1,46 @@
+
+if (window.innerWidth < 768) {
+ [].slice.call(document.querySelectorAll('[data-bss-disabled-mobile]')).forEach(function (elem) {
+ elem.classList.remove('animated');
+ elem.removeAttribute('data-bss-hover-animate');
+ elem.removeAttribute('data-aos');
+ elem.removeAttribute('data-bss-parallax-bg');
+ elem.removeAttribute('data-bss-scroll-zoom');
+ });
+}
+
+document.addEventListener('DOMContentLoaded', function() {
+
+ var hoverAnimationTriggerList = [].slice.call(document.querySelectorAll('[data-bss-hover-animate]'));
+ var hoverAnimationList = hoverAnimationTriggerList.forEach(function (hoverAnimationEl) {
+ hoverAnimationEl.addEventListener('mouseenter', function(e){ e.target.classList.add('animated', e.target.dataset.bssHoverAnimate) });
+ hoverAnimationEl.addEventListener('mouseleave', function(e){ e.target.classList.remove('animated', e.target.dataset.bssHoverAnimate) });
+ });
+
+ var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bss-tooltip]'));
+ var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
+ return new bootstrap.Tooltip(tooltipTriggerEl);
+ })
+
+ var toastTriggers = document.querySelectorAll('[data-bs-toggle="toast"]');
+
+ for (let toastTrigger of toastTriggers) {
+ toastTrigger.addEventListener('click', function () {
+ var toastSelector = toastTrigger.getAttribute('data-bs-target');
+
+ if (!toastSelector) return;
+
+ try {
+ var toastEl = document.querySelector(toastSelector);
+
+ if (!toastEl) return;
+
+ var toast = new bootstrap.Toast(toastEl);
+ toast.show();
+ }
+ catch(e) {
+ console.error(e);
+ }
+ })
+ }
+}, false);
\ No newline at end of file
diff --git a/infrastructure/internal/src/main/resources/static/assets/js/cmmn/algolia.mjs b/infrastructure/internal/src/main/resources/static/assets/js/cmmn/algolia.mjs
new file mode 100644
index 0000000..a8a3bbe
--- /dev/null
+++ b/infrastructure/internal/src/main/resources/static/assets/js/cmmn/algolia.mjs
@@ -0,0 +1,83 @@
+const algoliasearch = window['algoliasearch'];
+const { autocomplete, getAlgoliaResults } = window["@algolia/autocomplete-js"];
+
+const appId = 'DZSY6U0S0J';
+const apiKey = '6558cbc4f72828fe1cdad3d2a87264cb';
+const searchClient = algoliasearch(appId, apiKey);
+
+document.body.setAttribute('data-theme', document.documentElement.getAttribute('data-bs-theme'));
+
+function debouncePromise(fn, time) {
+ let timerId = undefined;
+
+ return function debounced(...args) {
+ if (timerId) {
+ clearTimeout(timerId);
+ }
+
+ return new Promise((resolve) => {
+ timerId = setTimeout(() => resolve(fn(...args)), time);
+ });
+ };
+}
+
+const debounced = debouncePromise((items) => Promise.resolve(items), 250);
+
+
+$(autocomplete({
+ container: '#autocomplete',
+ placeholder: '그릇, 가방...',
+ openOnFocus: false,
+ getSources({ query }) {
+ return debounced([
+ {
+ getItems() {
+ return getAlgoliaResults({
+ searchClient,
+ queries: [
+ {
+ indexName: 'waste_collection',
+ query,
+ params: {
+ hitsPerPage: 5
+ }
+ }
+ ]
+ });
+ },
+ getItemUrl({ item }) {
+ return "dictionary/" + item.dicNo;
+ },
+ templates: {
+ detachedCancelButtonText: "asd",
+ item({ item, components, html }) {
+ return html`
+
+
+
+
+
+ ${components.Highlight({
+ hit: item,
+ attribute: 'name'
+ })}
+
+
+
+
+
+ `;
+ },
+ noResults() {
+ return '일치하는 검색결과가 없습니다.';
+ }
+ }
+ }
+ ]);
+ }
+}));
\ No newline at end of file
diff --git a/infrastructure/internal/src/main/resources/static/assets/js/cmmn/aos.mjs b/infrastructure/internal/src/main/resources/static/assets/js/cmmn/aos.mjs
new file mode 100644
index 0000000..f46378f
--- /dev/null
+++ b/infrastructure/internal/src/main/resources/static/assets/js/cmmn/aos.mjs
@@ -0,0 +1,3 @@
+AOS.init(
+ {duration: 250, offset:20, delay: 0, once: true}
+);
\ No newline at end of file
diff --git a/infrastructure/internal/src/main/resources/templates/base/header.html b/infrastructure/internal/src/main/resources/templates/base/header.html
new file mode 100644
index 0000000..a0707d8
--- /dev/null
+++ b/infrastructure/internal/src/main/resources/templates/base/header.html
@@ -0,0 +1,146 @@
+
+
+
+
+
+
+ 분리ㅅㄱ
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/infrastructure/internal/src/main/resources/templates/base/layout.html b/infrastructure/internal/src/main/resources/templates/base/layout.html
new file mode 100644
index 0000000..60a8d98
--- /dev/null
+++ b/infrastructure/internal/src/main/resources/templates/base/layout.html
@@ -0,0 +1,154 @@
+
+
+
+
+
+
+ 분리ㅅㄱ
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Nullam id dolor id nibh ultricies vehicula ut id elit. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Donec id elit non mi porta gravida at eget metus.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/infrastructure/internal/src/main/resources/templates/community/board.html b/infrastructure/internal/src/main/resources/templates/community/board.html
new file mode 100644
index 0000000..9a89ad1
--- /dev/null
+++ b/infrastructure/internal/src/main/resources/templates/community/board.html
@@ -0,0 +1,121 @@
+
+
+
+
+
+
+ 게시판 - 분리ㅅㄱ
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/infrastructure/internal/src/main/resources/templates/community/content.html b/infrastructure/internal/src/main/resources/templates/community/content.html
new file mode 100644
index 0000000..0136750
--- /dev/null
+++ b/infrastructure/internal/src/main/resources/templates/community/content.html
@@ -0,0 +1,212 @@
+
+
+
+
+
+
+ 게시글 - 분리ㅅㄱ
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/infrastructure/internal/src/main/resources/templates/community/write.html b/infrastructure/internal/src/main/resources/templates/community/write.html
new file mode 100644
index 0000000..e9f924b
--- /dev/null
+++ b/infrastructure/internal/src/main/resources/templates/community/write.html
@@ -0,0 +1,265 @@
+
+
+
+
+
+
+ 게시글 - 분리ㅅㄱ
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/infrastructure/internal/src/main/resources/templates/dictionary/catalogue.html b/infrastructure/internal/src/main/resources/templates/dictionary/catalogue.html
new file mode 100644
index 0000000..f00a7e6
--- /dev/null
+++ b/infrastructure/internal/src/main/resources/templates/dictionary/catalogue.html
@@ -0,0 +1,135 @@
+
+
+
+
+
+
+ 사전 - 분리ㅅㄱ
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 품명
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/infrastructure/internal/src/main/resources/templates/dictionary/info.html b/infrastructure/internal/src/main/resources/templates/dictionary/info.html
new file mode 100644
index 0000000..5cf43ba
--- /dev/null
+++ b/infrastructure/internal/src/main/resources/templates/dictionary/info.html
@@ -0,0 +1,186 @@
+
+
+
+
+
+
+ 상세정보 - 분리ㅅㄱ
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
이름
+
+
태그에 해당되는 폐기물 공통 처리 안내
+
해당 폐기물 만의 취급 내용
+
+
+
+
+
+
+
+
+
+
+
이런! 해당 가이드에 대해 등록된 정보가 없습니다. 문의해주시면 빠른 시일 내 추가하겠습니다
+
+
+
이런! 해당 가이드에 대해 등록된 정보가 없습니다. 문의해주시면 빠른 시일 내 추가하겠습니다
+
+
+
+
+
+
+
+
+
연관된 폐기물
+
+
+
+ 폐기물명
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/infrastructure/internal/src/main/resources/templates/index.html b/infrastructure/internal/src/main/resources/templates/index.html
new file mode 100644
index 0000000..1e9e145
--- /dev/null
+++ b/infrastructure/internal/src/main/resources/templates/index.html
@@ -0,0 +1,133 @@
+
+
+
+
+
+
+ 분리ㅅㄱ
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
분리ㅅㄱ
+
분리배출을 더욱 편리하게!
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/infrastructure/internal/src/main/resources/templates/member/profile.html b/infrastructure/internal/src/main/resources/templates/member/profile.html
new file mode 100644
index 0000000..c25b446
--- /dev/null
+++ b/infrastructure/internal/src/main/resources/templates/member/profile.html
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+ 마이페이지 - 분리ㅅㄱ
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
옥재욱
+ okjaeook98@gmail.com since. 2020-03-01
\ No newline at end of file
diff --git a/lombok.config b/lombok.config
new file mode 100644
index 0000000..3426a54
--- /dev/null
+++ b/lombok.config
@@ -0,0 +1,17 @@
+config.stopBubbling = true
+
+#setter 차단. record 식 데이터 접근
+lombok.accessors.fluent = true
+
+# supressWarnings 차단
+lombok.addSuppressWarnings = false
+
+# no argments constructor default accessor protected 설정
+lombok.accessors.makeFinal = true
+
+# builder 패턴 사용시 생성자 자동 생성
+lombok.builder = true
+
+lombok.setter.flagUsage = ERROR
+lombok.sneakyThrows.flagUsage = ERROR
+lombok.data.flagUsage = ERROR
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..18f18ac
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,13 @@
+rootProject.name = "blisgo"
+
+include("app")
+
+include("domain")
+
+include("usecase")
+
+include 'infrastructure:internal'
+findProject(':infrastructure:internal')?.name = 'internal'
+include 'infrastructure:external'
+findProject(':infrastructure:external')?.name = 'external'
+
diff --git a/usecase/build.gradle b/usecase/build.gradle
new file mode 100644
index 0000000..334170f
--- /dev/null
+++ b/usecase/build.gradle
@@ -0,0 +1,6 @@
+bootJar.enabled = false
+jar.enabled = true
+
+dependencies {
+ implementation project(':domain')
+}
\ No newline at end of file
diff --git a/usecase/src/main/java/blisgo/usecase/UseCaseRoot.java b/usecase/src/main/java/blisgo/usecase/UseCaseRoot.java
new file mode 100644
index 0000000..1d14353
--- /dev/null
+++ b/usecase/src/main/java/blisgo/usecase/UseCaseRoot.java
@@ -0,0 +1,7 @@
+package blisgo.usecase;
+
+import org.springframework.context.annotation.ComponentScan;
+
+@ComponentScan(basePackageClasses = UseCaseRoot.class)
+public interface UseCaseRoot {
+}
diff --git a/usecase/src/main/java/blisgo/usecase/base/Events.java b/usecase/src/main/java/blisgo/usecase/base/Events.java
new file mode 100644
index 0000000..04603a5
--- /dev/null
+++ b/usecase/src/main/java/blisgo/usecase/base/Events.java
@@ -0,0 +1,20 @@
+package blisgo.usecase.base;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.springframework.context.ApplicationEventPublisher;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class Events {
+ private static ApplicationEventPublisher publisher;
+
+ static void setPublisher(ApplicationEventPublisher publisher) {
+ Events.publisher = publisher;
+ }
+
+ public static void raise(Object event) {
+ if (publisher != null) {
+ publisher.publishEvent(event);
+ }
+ }
+}
\ No newline at end of file
diff --git a/usecase/src/main/java/blisgo/usecase/base/EventsConfiguration.java b/usecase/src/main/java/blisgo/usecase/base/EventsConfiguration.java
new file mode 100644
index 0000000..a17b79f
--- /dev/null
+++ b/usecase/src/main/java/blisgo/usecase/base/EventsConfiguration.java
@@ -0,0 +1,18 @@
+package blisgo.usecase.base;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@RequiredArgsConstructor
+public class EventsConfiguration {
+ private final ApplicationContext applicationContext;
+
+ @Bean
+ public InitializingBean eventsInitializer() {
+ return () -> Events.setPublisher(applicationContext);
+ }
+}
\ No newline at end of file
diff --git a/usecase/src/main/java/blisgo/usecase/event/PostViewedEvent.java b/usecase/src/main/java/blisgo/usecase/event/PostViewedEvent.java
new file mode 100644
index 0000000..5f3fb4b
--- /dev/null
+++ b/usecase/src/main/java/blisgo/usecase/event/PostViewedEvent.java
@@ -0,0 +1,4 @@
+package blisgo.usecase.event;
+
+public record PostViewedEvent(Long postId) {
+}
\ No newline at end of file
diff --git a/usecase/src/main/java/blisgo/usecase/port/DogamInputPort.java b/usecase/src/main/java/blisgo/usecase/port/DogamInputPort.java
new file mode 100644
index 0000000..7c98811
--- /dev/null
+++ b/usecase/src/main/java/blisgo/usecase/port/DogamInputPort.java
@@ -0,0 +1,43 @@
+package blisgo.usecase.port;
+
+import blisgo.domain.dictionary.Dogam;
+import blisgo.domain.dictionary.vo.DogamId;
+import blisgo.domain.dictionary.vo.WasteId;
+import blisgo.domain.member.vo.MemberId;
+import blisgo.usecase.request.dogam.AddDogam;
+import blisgo.usecase.request.dogam.DogamCommand;
+import blisgo.usecase.request.dogam.DogamQuery;
+import blisgo.usecase.request.dogam.RemoveDogam;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class DogamInputPort implements DogamCommand, DogamQuery {
+ private final DogamOutputPort port;
+
+ @Override
+ public boolean addDogam(AddDogam command) {
+ MemberId memberId = MemberId.of(command.memberId());
+ WasteId wasteId = WasteId.of(command.wasteId());
+
+ Dogam dogam = Dogam.create(memberId, wasteId);
+
+ return port.create(dogam);
+ }
+
+ @Override
+ public boolean removeDogam(RemoveDogam command) {
+ MemberId memberId = MemberId.of(command.memberId());
+ WasteId wasteId = WasteId.of(command.wasteId());
+
+ DogamId dogamId = DogamId.builder()
+ .memberId(memberId)
+ .wasteId(wasteId)
+ .build();
+
+ return port.delete(dogamId);
+ }
+}
diff --git a/usecase/src/main/java/blisgo/usecase/port/DogamOutputPort.java b/usecase/src/main/java/blisgo/usecase/port/DogamOutputPort.java
new file mode 100644
index 0000000..edcf9e9
--- /dev/null
+++ b/usecase/src/main/java/blisgo/usecase/port/DogamOutputPort.java
@@ -0,0 +1,12 @@
+package blisgo.usecase.port;
+
+import blisgo.domain.dictionary.Dogam;
+import blisgo.domain.dictionary.vo.DogamId;
+import org.jmolecules.architecture.hexagonal.SecondaryPort;
+
+@SecondaryPort
+public interface DogamOutputPort {
+ boolean delete(DogamId identifier);
+
+ boolean create(Dogam domain);
+}
diff --git a/usecase/src/main/java/blisgo/usecase/port/MemberInputPort.java b/usecase/src/main/java/blisgo/usecase/port/MemberInputPort.java
new file mode 100644
index 0000000..9bfa2e3
--- /dev/null
+++ b/usecase/src/main/java/blisgo/usecase/port/MemberInputPort.java
@@ -0,0 +1,52 @@
+package blisgo.usecase.port;
+
+import blisgo.domain.member.Member;
+import blisgo.usecase.request.member.*;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.util.Map;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class MemberInputPort implements MemberCommand, MemberQuery {
+ private final MemberOutputPort port;
+
+ @Override
+ public boolean addMember(AddMember command) {
+ var member = Member.create(
+ command.name(),
+ command.email(),
+ command.picture()
+ );
+
+ return port.create(member);
+ }
+
+ @Override
+ public boolean updateMember(UpdateMember command) {
+ var member = Member.create(
+ command.name(),
+ command.email(),
+ command.picture()
+ );
+
+ return port.update(member);
+ }
+
+ @Override
+ public boolean removeMember(RemoveMember command) {
+ var email = command.email();
+
+ return port.delete(email);
+ }
+
+ @Override
+ public Member getMember(GetMember query) {
+ var columns = Map.of("email", query.email());
+
+ return port.read(columns);
+ }
+}
diff --git a/usecase/src/main/java/blisgo/usecase/port/MemberOutputPort.java b/usecase/src/main/java/blisgo/usecase/port/MemberOutputPort.java
new file mode 100644
index 0000000..d4ed270
--- /dev/null
+++ b/usecase/src/main/java/blisgo/usecase/port/MemberOutputPort.java
@@ -0,0 +1,17 @@
+package blisgo.usecase.port;
+
+import blisgo.domain.member.Member;
+import org.jmolecules.architecture.hexagonal.SecondaryPort;
+
+import java.util.Map;
+
+@SecondaryPort
+public interface MemberOutputPort {
+ boolean update(Member domain);
+
+ boolean delete(String email);
+
+ boolean create(Member domain);
+
+ Member read(Map columns);
+}
diff --git a/usecase/src/main/java/blisgo/usecase/port/PostInputPort.java b/usecase/src/main/java/blisgo/usecase/port/PostInputPort.java
new file mode 100644
index 0000000..dc3e961
--- /dev/null
+++ b/usecase/src/main/java/blisgo/usecase/port/PostInputPort.java
@@ -0,0 +1,66 @@
+package blisgo.usecase.port;
+
+import blisgo.domain.community.Post;
+import blisgo.usecase.base.Events;
+import blisgo.usecase.event.PostViewedEvent;
+import blisgo.usecase.request.post.*;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.data.domain.Slice;
+import org.springframework.stereotype.Service;
+
+import java.util.Map;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class PostInputPort implements PostCommand, PostQuery {
+ private final PostOutputPort port;
+
+ @Override
+ public boolean addPost(AddPost command) {
+ var post = Post.create(
+ command.title(),
+ command.content()
+ );
+
+ return port.create(post);
+ }
+
+ @Override
+ public boolean updatePost(UpdatePost command) {
+ var post = Post.create(
+ command.postId(),
+ command.title(),
+ command.content()
+ );
+
+ return port.update(post);
+ }
+
+ @Override
+ public boolean removePost(RemovePost command) {
+ var postId = command.postId();
+
+ return port.delete(postId);
+ }
+
+ @Override
+ public boolean like(PostLike command) {
+ Long postId = command.postId();
+ Boolean isLike = command.isLike();
+
+ return port.updateLike(postId, isLike);
+ }
+
+ @Override
+ public Post getPost(GetPost query) {
+ Events.raise(new PostViewedEvent(query.postId()));
+ return port.read(query.postId());
+ }
+
+ @Override
+ public Slice getPosts(GetPost query) {
+ return port.read(Map.of("lastPostId", query.postId()), query.pageable());
+ }
+}
diff --git a/usecase/src/main/java/blisgo/usecase/port/PostOutputPort.java b/usecase/src/main/java/blisgo/usecase/port/PostOutputPort.java
new file mode 100644
index 0000000..76bbab1
--- /dev/null
+++ b/usecase/src/main/java/blisgo/usecase/port/PostOutputPort.java
@@ -0,0 +1,26 @@
+package blisgo.usecase.port;
+
+import blisgo.domain.community.Post;
+import org.jmolecules.architecture.hexagonal.SecondaryPort;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Slice;
+
+import java.util.List;
+import java.util.Map;
+
+@SecondaryPort
+public interface PostOutputPort {
+ boolean create(Post domain);
+
+ Post read(Long postId);
+
+ Slice read(Map columns, Pageable pageable);
+
+ boolean update(Post domain);
+
+ boolean delete(Long identifier);
+
+ boolean updateLike(Long postId, Boolean isLike);
+
+ List findPostIds();
+}
diff --git a/usecase/src/main/java/blisgo/usecase/port/ReplyInputPort.java b/usecase/src/main/java/blisgo/usecase/port/ReplyInputPort.java
new file mode 100644
index 0000000..fe96ab9
--- /dev/null
+++ b/usecase/src/main/java/blisgo/usecase/port/ReplyInputPort.java
@@ -0,0 +1,36 @@
+package blisgo.usecase.port;
+
+import blisgo.domain.community.Reply;
+import blisgo.domain.community.vo.PostId;
+import blisgo.usecase.request.reply.*;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.data.domain.Slice;
+import org.springframework.stereotype.Service;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class ReplyInputPort implements ReplyCommand, ReplyQuery {
+ private final ReplyOutputPort port;
+
+ @Override
+ public boolean addReply(AddReply command) {
+ var reply = Reply.builder()
+ .postId(PostId.of(command.postId()))
+ .content(command.content())
+ .build();
+
+ return port.create(reply);
+ }
+
+ @Override
+ public boolean removeReply(RemoveReply command) {
+ return port.delete(command.replyId());
+ }
+
+ @Override
+ public Slice getReplies(GetReply query) {
+ return port.read(query.postId(), query.pageable(), query.lastReplyId());
+ }
+}
diff --git a/usecase/src/main/java/blisgo/usecase/port/ReplyOutputPort.java b/usecase/src/main/java/blisgo/usecase/port/ReplyOutputPort.java
new file mode 100644
index 0000000..0fbcb29
--- /dev/null
+++ b/usecase/src/main/java/blisgo/usecase/port/ReplyOutputPort.java
@@ -0,0 +1,17 @@
+package blisgo.usecase.port;
+
+import blisgo.domain.community.Reply;
+import org.jmolecules.architecture.hexagonal.SecondaryPort;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Slice;
+
+@SecondaryPort
+public interface ReplyOutputPort {
+ boolean delete(Long identifier);
+
+ boolean create(Reply domain);
+
+ Slice read(Long postId, Pageable pageable, Long lastReplyId);
+
+ boolean update(Reply domain);
+}
diff --git a/usecase/src/main/java/blisgo/usecase/port/WasteInputPort.java b/usecase/src/main/java/blisgo/usecase/port/WasteInputPort.java
new file mode 100644
index 0000000..e2546de
--- /dev/null
+++ b/usecase/src/main/java/blisgo/usecase/port/WasteInputPort.java
@@ -0,0 +1,47 @@
+package blisgo.usecase.port;
+
+import blisgo.domain.dictionary.Waste;
+import blisgo.domain.dictionary.vo.Category;
+import blisgo.domain.dictionary.vo.Guide;
+import blisgo.domain.member.vo.MemberId;
+import blisgo.usecase.request.dogam.GetDogam;
+import blisgo.usecase.request.waste.GetWaste;
+import blisgo.usecase.request.waste.WasteQuery;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.data.domain.Slice;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class WasteInputPort implements WasteQuery {
+ private final WasteOutputPort port;
+
+ @Override
+ public Slice getWastes(GetWaste query) {
+ return port.read(query.pageable(), query.lastWasteId());
+ }
+
+ @Override
+ public Waste getWaste(GetWaste query) {
+ return port.read(query.wasteId());
+ }
+
+ @Override
+ public List getGuides(List categories) {
+ return port.readGuides(categories);
+ }
+
+ @Override
+ public Slice getWastesFromDogam(GetDogam query) {
+ return port.readWastesFromDogam(MemberId.of(query.email()).id(), query.pageable(), query.lastDogamCreatedDate());
+ }
+
+ @Override
+ public List getWastesRelated(List categories) {
+ return port.readWastesRelated(categories);
+ }
+}
diff --git a/usecase/src/main/java/blisgo/usecase/port/WasteOutputPort.java b/usecase/src/main/java/blisgo/usecase/port/WasteOutputPort.java
new file mode 100644
index 0000000..3c4f47a
--- /dev/null
+++ b/usecase/src/main/java/blisgo/usecase/port/WasteOutputPort.java
@@ -0,0 +1,25 @@
+package blisgo.usecase.port;
+
+import blisgo.domain.dictionary.Waste;
+import blisgo.domain.dictionary.vo.Category;
+import blisgo.domain.dictionary.vo.Guide;
+import org.jmolecules.architecture.hexagonal.SecondaryPort;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Slice;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.UUID;
+
+@SecondaryPort
+public interface WasteOutputPort {
+ Waste read(Long wasteId);
+
+ Slice read(Pageable pageable, Long lastWasteId);
+
+ List readGuides(List categories);
+
+ Slice readWastesFromDogam(UUID memberId, Pageable pageable, LocalDateTime lastDogamCreatedDate);
+
+ List readWastesRelated(List categories);
+}
diff --git a/usecase/src/main/java/blisgo/usecase/request/dogam/AddDogam.java b/usecase/src/main/java/blisgo/usecase/request/dogam/AddDogam.java
new file mode 100644
index 0000000..10ab557
--- /dev/null
+++ b/usecase/src/main/java/blisgo/usecase/request/dogam/AddDogam.java
@@ -0,0 +1,12 @@
+package blisgo.usecase.request.dogam;
+
+import lombok.Builder;
+
+import java.util.UUID;
+
+@Builder
+public record AddDogam(
+ UUID memberId,
+ Long wasteId
+) {
+}
diff --git a/usecase/src/main/java/blisgo/usecase/request/dogam/DogamCommand.java b/usecase/src/main/java/blisgo/usecase/request/dogam/DogamCommand.java
new file mode 100644
index 0000000..f3f1f50
--- /dev/null
+++ b/usecase/src/main/java/blisgo/usecase/request/dogam/DogamCommand.java
@@ -0,0 +1,10 @@
+package blisgo.usecase.request.dogam;
+
+import org.jmolecules.architecture.hexagonal.PrimaryPort;
+
+@PrimaryPort
+public interface DogamCommand {
+ boolean addDogam(AddDogam command);
+
+ boolean removeDogam(RemoveDogam command);
+}
diff --git a/usecase/src/main/java/blisgo/usecase/request/dogam/DogamQuery.java b/usecase/src/main/java/blisgo/usecase/request/dogam/DogamQuery.java
new file mode 100644
index 0000000..105aea4
--- /dev/null
+++ b/usecase/src/main/java/blisgo/usecase/request/dogam/DogamQuery.java
@@ -0,0 +1,7 @@
+package blisgo.usecase.request.dogam;
+
+import org.jmolecules.architecture.hexagonal.PrimaryPort;
+
+@PrimaryPort
+public interface DogamQuery {
+}
diff --git a/usecase/src/main/java/blisgo/usecase/request/dogam/GetDogam.java b/usecase/src/main/java/blisgo/usecase/request/dogam/GetDogam.java
new file mode 100644
index 0000000..41b18ce
--- /dev/null
+++ b/usecase/src/main/java/blisgo/usecase/request/dogam/GetDogam.java
@@ -0,0 +1,14 @@
+package blisgo.usecase.request.dogam;
+
+import lombok.Builder;
+import org.springframework.data.domain.Pageable;
+
+import java.time.LocalDateTime;
+
+@Builder
+public record GetDogam(
+ String email,
+ Pageable pageable,
+ LocalDateTime lastDogamCreatedDate
+) {
+}
diff --git a/usecase/src/main/java/blisgo/usecase/request/dogam/RemoveDogam.java b/usecase/src/main/java/blisgo/usecase/request/dogam/RemoveDogam.java
new file mode 100644
index 0000000..2224601
--- /dev/null
+++ b/usecase/src/main/java/blisgo/usecase/request/dogam/RemoveDogam.java
@@ -0,0 +1,12 @@
+package blisgo.usecase.request.dogam;
+
+import lombok.Builder;
+
+import java.util.UUID;
+
+@Builder
+public record RemoveDogam(
+ UUID memberId,
+ Long wasteId
+) {
+}
diff --git a/usecase/src/main/java/blisgo/usecase/request/member/AddMember.java b/usecase/src/main/java/blisgo/usecase/request/member/AddMember.java
new file mode 100644
index 0000000..1bfa12c
--- /dev/null
+++ b/usecase/src/main/java/blisgo/usecase/request/member/AddMember.java
@@ -0,0 +1,8 @@
+package blisgo.usecase.request.member;
+
+public record AddMember(
+ String email,
+ String name,
+ String picture
+) {
+}
diff --git a/usecase/src/main/java/blisgo/usecase/request/member/GetMember.java b/usecase/src/main/java/blisgo/usecase/request/member/GetMember.java
new file mode 100644
index 0000000..3fd53ba
--- /dev/null
+++ b/usecase/src/main/java/blisgo/usecase/request/member/GetMember.java
@@ -0,0 +1,9 @@
+package blisgo.usecase.request.member;
+
+import lombok.Builder;
+
+@Builder
+public record GetMember(
+ String email
+) {
+}
diff --git a/usecase/src/main/java/blisgo/usecase/request/member/MemberCommand.java b/usecase/src/main/java/blisgo/usecase/request/member/MemberCommand.java
new file mode 100644
index 0000000..341af9b
--- /dev/null
+++ b/usecase/src/main/java/blisgo/usecase/request/member/MemberCommand.java
@@ -0,0 +1,12 @@
+package blisgo.usecase.request.member;
+
+import org.jmolecules.architecture.hexagonal.PrimaryPort;
+
+@PrimaryPort
+public interface MemberCommand {
+ boolean addMember(AddMember command);
+
+ boolean updateMember(UpdateMember command);
+
+ boolean removeMember(RemoveMember command);
+}
diff --git a/usecase/src/main/java/blisgo/usecase/request/member/MemberQuery.java b/usecase/src/main/java/blisgo/usecase/request/member/MemberQuery.java
new file mode 100644
index 0000000..65fe3e1
--- /dev/null
+++ b/usecase/src/main/java/blisgo/usecase/request/member/MemberQuery.java
@@ -0,0 +1,9 @@
+package blisgo.usecase.request.member;
+
+import blisgo.domain.member.Member;
+import org.jmolecules.architecture.hexagonal.PrimaryPort;
+
+@PrimaryPort
+public interface MemberQuery {
+ Member getMember(GetMember getMemberQuery);
+}
diff --git a/usecase/src/main/java/blisgo/usecase/request/member/RemoveMember.java b/usecase/src/main/java/blisgo/usecase/request/member/RemoveMember.java
new file mode 100644
index 0000000..4492f3c
--- /dev/null
+++ b/usecase/src/main/java/blisgo/usecase/request/member/RemoveMember.java
@@ -0,0 +1,9 @@
+package blisgo.usecase.request.member;
+
+import lombok.Builder;
+
+@Builder
+public record RemoveMember(
+ String email
+) {
+}
diff --git a/usecase/src/main/java/blisgo/usecase/request/member/UpdateMember.java b/usecase/src/main/java/blisgo/usecase/request/member/UpdateMember.java
new file mode 100644
index 0000000..93804c9
--- /dev/null
+++ b/usecase/src/main/java/blisgo/usecase/request/member/UpdateMember.java
@@ -0,0 +1,11 @@
+package blisgo.usecase.request.member;
+
+import lombok.Builder;
+
+@Builder
+public record UpdateMember(
+ String email,
+ String name,
+ String picture
+) {
+}
diff --git a/usecase/src/main/java/blisgo/usecase/request/post/AddPost.java b/usecase/src/main/java/blisgo/usecase/request/post/AddPost.java
new file mode 100644
index 0000000..51d312d
--- /dev/null
+++ b/usecase/src/main/java/blisgo/usecase/request/post/AddPost.java
@@ -0,0 +1,10 @@
+package blisgo.usecase.request.post;
+
+import lombok.Builder;
+
+@Builder
+public record AddPost(
+ String title,
+ String content
+) {
+}
diff --git a/usecase/src/main/java/blisgo/usecase/request/post/GetPost.java b/usecase/src/main/java/blisgo/usecase/request/post/GetPost.java
new file mode 100644
index 0000000..f6a819c
--- /dev/null
+++ b/usecase/src/main/java/blisgo/usecase/request/post/GetPost.java
@@ -0,0 +1,11 @@
+package blisgo.usecase.request.post;
+
+import lombok.Builder;
+import org.springframework.data.domain.Pageable;
+
+@Builder
+public record GetPost(
+ Long postId,
+ Pageable pageable
+) {
+}
diff --git a/usecase/src/main/java/blisgo/usecase/request/post/PostCommand.java b/usecase/src/main/java/blisgo/usecase/request/post/PostCommand.java
new file mode 100644
index 0000000..228dfa3
--- /dev/null
+++ b/usecase/src/main/java/blisgo/usecase/request/post/PostCommand.java
@@ -0,0 +1,14 @@
+package blisgo.usecase.request.post;
+
+import org.jmolecules.architecture.hexagonal.PrimaryPort;
+
+@PrimaryPort
+public interface PostCommand {
+ boolean addPost(AddPost command);
+
+ boolean updatePost(UpdatePost command);
+
+ boolean removePost(RemovePost command);
+
+ boolean like(PostLike command);
+}
diff --git a/usecase/src/main/java/blisgo/usecase/request/post/PostLike.java b/usecase/src/main/java/blisgo/usecase/request/post/PostLike.java
new file mode 100644
index 0000000..243d8bd
--- /dev/null
+++ b/usecase/src/main/java/blisgo/usecase/request/post/PostLike.java
@@ -0,0 +1,10 @@
+package blisgo.usecase.request.post;
+
+import lombok.Builder;
+
+@Builder
+public record PostLike(
+ Long postId,
+ Boolean isLike
+) {
+}
diff --git a/usecase/src/main/java/blisgo/usecase/request/post/PostQuery.java b/usecase/src/main/java/blisgo/usecase/request/post/PostQuery.java
new file mode 100644
index 0000000..0a2a134
--- /dev/null
+++ b/usecase/src/main/java/blisgo/usecase/request/post/PostQuery.java
@@ -0,0 +1,12 @@
+package blisgo.usecase.request.post;
+
+import blisgo.domain.community.Post;
+import org.jmolecules.architecture.hexagonal.PrimaryPort;
+import org.springframework.data.domain.Slice;
+
+@PrimaryPort
+public interface PostQuery {
+ Post getPost(GetPost query);
+
+ Slice getPosts(GetPost query);
+}
diff --git a/usecase/src/main/java/blisgo/usecase/request/post/RemovePost.java b/usecase/src/main/java/blisgo/usecase/request/post/RemovePost.java
new file mode 100644
index 0000000..a40fa92
--- /dev/null
+++ b/usecase/src/main/java/blisgo/usecase/request/post/RemovePost.java
@@ -0,0 +1,9 @@
+package blisgo.usecase.request.post;
+
+import lombok.Builder;
+
+@Builder
+public record RemovePost(
+ Long postId
+) {
+}
diff --git a/usecase/src/main/java/blisgo/usecase/request/post/UpdatePost.java b/usecase/src/main/java/blisgo/usecase/request/post/UpdatePost.java
new file mode 100644
index 0000000..9241bfd
--- /dev/null
+++ b/usecase/src/main/java/blisgo/usecase/request/post/UpdatePost.java
@@ -0,0 +1,11 @@
+package blisgo.usecase.request.post;
+
+import lombok.Builder;
+
+@Builder(toBuilder = true)
+public record UpdatePost(
+ Long postId,
+ String title,
+ String content
+) {
+}
diff --git a/usecase/src/main/java/blisgo/usecase/request/reply/AddReply.java b/usecase/src/main/java/blisgo/usecase/request/reply/AddReply.java
new file mode 100644
index 0000000..0e96d2e
--- /dev/null
+++ b/usecase/src/main/java/blisgo/usecase/request/reply/AddReply.java
@@ -0,0 +1,10 @@
+package blisgo.usecase.request.reply;
+
+import lombok.Builder;
+
+@Builder
+public record AddReply(
+ Long postId,
+ String content
+) {
+}
diff --git a/usecase/src/main/java/blisgo/usecase/request/reply/GetReply.java b/usecase/src/main/java/blisgo/usecase/request/reply/GetReply.java
new file mode 100644
index 0000000..defdbc5
--- /dev/null
+++ b/usecase/src/main/java/blisgo/usecase/request/reply/GetReply.java
@@ -0,0 +1,12 @@
+package blisgo.usecase.request.reply;
+
+import lombok.Builder;
+import org.springframework.data.domain.Pageable;
+
+@Builder
+public record GetReply(
+ Long postId,
+ Long lastReplyId,
+ Pageable pageable
+) {
+}
diff --git a/usecase/src/main/java/blisgo/usecase/request/reply/RemoveReply.java b/usecase/src/main/java/blisgo/usecase/request/reply/RemoveReply.java
new file mode 100644
index 0000000..4e03f16
--- /dev/null
+++ b/usecase/src/main/java/blisgo/usecase/request/reply/RemoveReply.java
@@ -0,0 +1,9 @@
+package blisgo.usecase.request.reply;
+
+import lombok.Builder;
+
+@Builder
+public record RemoveReply(
+ Long replyId
+) {
+}
diff --git a/usecase/src/main/java/blisgo/usecase/request/reply/ReplyCommand.java b/usecase/src/main/java/blisgo/usecase/request/reply/ReplyCommand.java
new file mode 100644
index 0000000..009d4e8
--- /dev/null
+++ b/usecase/src/main/java/blisgo/usecase/request/reply/ReplyCommand.java
@@ -0,0 +1,10 @@
+package blisgo.usecase.request.reply;
+
+import org.jmolecules.architecture.hexagonal.PrimaryPort;
+
+@PrimaryPort
+public interface ReplyCommand {
+ boolean addReply(AddReply command);
+
+ boolean removeReply(RemoveReply command);
+}
diff --git a/usecase/src/main/java/blisgo/usecase/request/reply/ReplyQuery.java b/usecase/src/main/java/blisgo/usecase/request/reply/ReplyQuery.java
new file mode 100644
index 0000000..932a9c5
--- /dev/null
+++ b/usecase/src/main/java/blisgo/usecase/request/reply/ReplyQuery.java
@@ -0,0 +1,10 @@
+package blisgo.usecase.request.reply;
+
+import blisgo.domain.community.Reply;
+import org.jmolecules.architecture.hexagonal.PrimaryPort;
+import org.springframework.data.domain.Slice;
+
+@PrimaryPort
+public interface ReplyQuery {
+ Slice getReplies(GetReply query);
+}
diff --git a/usecase/src/main/java/blisgo/usecase/request/waste/GetWaste.java b/usecase/src/main/java/blisgo/usecase/request/waste/GetWaste.java
new file mode 100644
index 0000000..0557a9d
--- /dev/null
+++ b/usecase/src/main/java/blisgo/usecase/request/waste/GetWaste.java
@@ -0,0 +1,12 @@
+package blisgo.usecase.request.waste;
+
+import lombok.Builder;
+import org.springframework.data.domain.Pageable;
+
+@Builder
+public record GetWaste(
+ Pageable pageable,
+ Long wasteId,
+ Long lastWasteId
+) {
+}
diff --git a/usecase/src/main/java/blisgo/usecase/request/waste/WasteQuery.java b/usecase/src/main/java/blisgo/usecase/request/waste/WasteQuery.java
new file mode 100644
index 0000000..ef2819e
--- /dev/null
+++ b/usecase/src/main/java/blisgo/usecase/request/waste/WasteQuery.java
@@ -0,0 +1,25 @@
+package blisgo.usecase.request.waste;
+
+import blisgo.domain.dictionary.Waste;
+import blisgo.domain.dictionary.vo.Category;
+import blisgo.domain.dictionary.vo.Guide;
+import blisgo.usecase.request.dogam.GetDogam;
+import org.jmolecules.architecture.hexagonal.PrimaryPort;
+import org.springframework.data.domain.Slice;
+
+import java.util.List;
+
+@PrimaryPort
+public interface WasteQuery {
+ Slice getWastes(GetWaste query);
+
+ Waste getWaste(GetWaste query);
+
+ List getGuides(List categories);
+
+ Slice getWastesFromDogam(GetDogam query);
+
+ List getWastesRelated(List categories);
+
+
+}
diff --git "a/\353\266\204\353\246\254\343\205\205\343\204\261V4.bsdesign" "b/\353\266\204\353\246\254\343\205\205\343\204\261V4.bsdesign"
new file mode 100644
index 0000000..16886fc
Binary files /dev/null and "b/\353\266\204\353\246\254\343\205\205\343\204\261V4.bsdesign" differ