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` + +
+
${item.name} +
+
+ ${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 @@ + + + + + + + 분리ㅅㄱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+ + + + + + + + + + + + \ 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 @@ + + + + + + + 상세정보 - 분리ㅅㄱ + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
사전 이미지
+
+
+
+ +
+
+

이름

+
+
+ + + 별점/10
+ + + 태그명 +
+
태그에 해당되는 폐기물 공통 처리 안내  +

해당 폐기물 만의 취급 내용

+
+
+
+
+
+
+
+ +
+
설명서 + +
+
설명서 + +
+
+
+
+
+
+
+
+

연관된 폐기물

+
+
+
사전 이미지 +
폐기물명
+
+
+
+
+
+
+
+
+ + + + + + \ 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