diff --git a/.github/workflows/automatic-erd.yml b/.github/workflows/automatic-erd.yml
new file mode 100644
index 00000000..309bd73c
--- /dev/null
+++ b/.github/workflows/automatic-erd.yml
@@ -0,0 +1,96 @@
+name: ERD to GitHub Pages
+
+on:
+ pull_request:
+ branches: [ "**" ]
+ paths: [ "**" ]
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+permissions:
+ contents: write
+ pull-requests: write
+
+jobs:
+ generate-erd-from-flyway:
+ runs-on: ubuntu-latest
+
+ services:
+ mysql:
+ image: mysql:8.0
+ env:
+ MYSQL_USER: ${{ secrets.TEST_DB_USER }}
+ MYSQL_PASSWORD: ${{ secrets.TEST_DB_PASSWORD }}
+ MYSQL_ROOT_PASSWORD: ${{ secrets.TEST_DB_ROOT_PASSWORD }}
+ MYSQL_DATABASE: testdb
+ ports:
+ - 3306:3306
+ options: >-
+ --health-cmd="mysqladmin ping"
+ --health-interval=10s
+ --health-timeout=5s
+ --health-retries=3
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Set up JDK 21
+ uses: actions/setup-java@v4
+ with:
+ java-version: '21'
+ distribution: 'temurin'
+
+ - name: Download Flyway
+ run: |
+ curl -sSL "https://repo1.maven.org/maven2/org/flywaydb/flyway-commandline/10.13.0/flyway-commandline-10.13.0-linux-x64.tar.gz" -o flyway.tar.gz
+ tar -xzf flyway.tar.gz
+ echo "$(pwd)/flyway-10.13.0" >> $GITHUB_PATH
+
+ - name: Run Flyway migrations
+ run: |
+ flyway -url="jdbc:mysql://127.0.0.1:3306/testdb?allowPublicKeyRetrieval=true&useSSL=false" -user=${{ secrets.TEST_DB_USER }} -password=${{ secrets.TEST_DB_PASSWORD }} -locations=filesystem:src/main/resources/db/migration migrate
+ shell: bash
+
+ - name: Dump database schema
+ run: |
+ mkdir -p erd
+ mysqldump -h 127.0.0.1 -u ${{ secrets.TEST_DB_USER }} -p${{ secrets.TEST_DB_PASSWORD }} --no-data --skip-comments testdb > erd/schema.sql
+
+ - name: Set up Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '20'
+
+ - name: Install DBML Tools
+ run: npm install -g @dbml/cli @softwaretechnik/dbml-renderer
+
+ - name: Convert SQL to DBML
+ run: |
+ npx sql2dbml erd/schema.sql --mysql -o erd/schema.dbml
+
+ - name: Render ERD to SVG
+ run: |
+ npx dbml-renderer -i erd/schema.dbml -o erd/erd.svg
+
+ - name: Generate HTML wrapper
+ run: |
+ echo "
ERD PreviewERD Preview for PR #${{ github.event.pull_request.number }}
" > erd/index.html
+
+ - name: Deploy ERD to GitHub Pages
+ uses: peaceiris/actions-gh-pages@v4
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ publish_dir: ./erd
+ force_orphan: true
+
+ - name: Comment on PR with ERD Link
+ uses: peter-evans/create-or-update-comment@v4
+ with:
+ issue-number: ${{ github.event.pull_request.number }}
+ body: |
+ ๐ ์ต์ ERD๊ฐ ์๋ ์์ฑ๋์์ต๋๋ค.
+
+ ๐ [ERD ๋ณด๋ฌ๊ฐ๊ธฐ](https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}/)
diff --git a/build.gradle b/build.gradle
index 50c942db..20ee0587 100644
--- a/build.gradle
+++ b/build.gradle
@@ -27,8 +27,8 @@ jacoco {
sonarqube {
properties {
- property "sonar.projectKey", "baegam_timeeat"
- property "sonar.organization", "baegam"
+ property "sonar.projectKey", "YAPP-Github_26th-Web-Team-1-BE"
+ property "sonar.organization", "yapp-github"
property "sonar.host.url", "https://sonarcloud.io"
property 'sonar.sourceEncoding', 'UTF-8'
property 'sonar.java.coveragePlugin', 'jacoco'
@@ -56,6 +56,7 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
+ implementation 'org.springframework.boot:spring-boot-starter-validation'
// Lombok
compileOnly 'org.projectlombok:lombok'
@@ -85,6 +86,8 @@ dependencies {
// aws
implementation 'io.awspring.cloud:spring-cloud-aws-starter-parameter-store:3.2.1'
+ implementation 'io.awspring.cloud:spring-cloud-aws-starter-s3:3.2.1'
+ implementation 'software.amazon.awssdk:s3:2.31.77'
}
bootJar {
@@ -125,18 +128,18 @@ generateSwaggerUI {
openapi3 {
servers = [
- {
- url = "https://api-dev.eatda.net"
- description = "Develop Server"
- },
- {
- url = "https://api.eatda.net"
- description = "Production Server"
- },
- {
- url = "http://localhost:8080"
- description = "Local Server"
- }
+ {
+ url = "https://api-dev.eatda.net"
+ description = "Develop Server"
+ },
+ {
+ url = "https://api.eatda.net"
+ description = "Production Server"
+ },
+ {
+ url = "http://localhost:8080"
+ description = "Local Server"
+ }
]
title = "EatDa API"
description = "EatDa API ๋ช
์ธ์"
diff --git a/src/main/java/timeeat/TimeEatApplication.java b/src/main/java/eatda/EatdaApplication.java
similarity index 63%
rename from src/main/java/timeeat/TimeEatApplication.java
rename to src/main/java/eatda/EatdaApplication.java
index dffe973b..36d4802f 100644
--- a/src/main/java/timeeat/TimeEatApplication.java
+++ b/src/main/java/eatda/EatdaApplication.java
@@ -1,12 +1,12 @@
-package timeeat;
+package eatda;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
-public class TimeEatApplication {
+public class EatdaApplication {
public static void main(String[] args) {
- SpringApplication.run(TimeEatApplication.class, args);
+ SpringApplication.run(EatdaApplication.class, args);
}
}
diff --git a/src/main/java/eatda/client/map/KakaoProperties.java b/src/main/java/eatda/client/map/KakaoProperties.java
new file mode 100644
index 00000000..bd1e0a4b
--- /dev/null
+++ b/src/main/java/eatda/client/map/KakaoProperties.java
@@ -0,0 +1,22 @@
+package eatda.client.map;
+
+import lombok.Getter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@Getter
+@ConfigurationProperties(prefix = "kakao")
+public class KakaoProperties {
+
+ private final String apiKey;
+
+ public KakaoProperties(String apiKey) {
+ validateApiKey(apiKey);
+ this.apiKey = apiKey;
+ }
+
+ private void validateApiKey(String apiKey) {
+ if (apiKey == null || apiKey.isBlank()) {
+ throw new IllegalArgumentException("API key must not be null or blank");
+ }
+ }
+}
diff --git a/src/main/java/eatda/client/map/MapClient.java b/src/main/java/eatda/client/map/MapClient.java
new file mode 100644
index 00000000..913460ab
--- /dev/null
+++ b/src/main/java/eatda/client/map/MapClient.java
@@ -0,0 +1,41 @@
+package eatda.client.map;
+
+import eatda.domain.store.Coordinates;
+import java.util.List;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.http.HttpStatusCode;
+import org.springframework.stereotype.Component;
+import org.springframework.web.client.RestClient;
+
+@Component
+@EnableConfigurationProperties(KakaoProperties.class)
+public class MapClient {
+
+ private final RestClient restClient;
+ private final KakaoProperties kakaoProperties;
+
+ public MapClient(RestClient.Builder restClient, KakaoProperties properties) {
+ this.restClient = restClient
+ .defaultStatusHandler(HttpStatusCode::is5xxServerError, new MapServerErrorHandler())
+ .build();
+ this.kakaoProperties = properties;
+ }
+
+ public List searchShops(String query) {
+ return restClient.get()
+ .uri("https://dapi.kakao.com/v2/local/search/keyword.json", builder -> builder
+ .queryParam("query", query)
+ .queryParam("category", "FD6")
+ .queryParam("rect", "%s,%s,%s,%s".formatted(
+ Coordinates.getMinLongitude(), Coordinates.getMinLatitude(),
+ Coordinates.getMaxLongitude(), Coordinates.getMaxLatitude()))
+ .queryParam("page", 1)
+ .queryParam("size", 15)
+ .queryParam("sort", "accuracy")
+ .build())
+ .header("Authorization", "KakaoAK " + kakaoProperties.getApiKey())
+ .retrieve()
+ .body(StoreSearchResults.class)
+ .results();
+ }
+}
diff --git a/src/main/java/eatda/client/map/MapServerErrorHandler.java b/src/main/java/eatda/client/map/MapServerErrorHandler.java
new file mode 100644
index 00000000..6ffd5bff
--- /dev/null
+++ b/src/main/java/eatda/client/map/MapServerErrorHandler.java
@@ -0,0 +1,16 @@
+package eatda.client.map;
+
+import eatda.exception.BusinessErrorCode;
+import eatda.exception.BusinessException;
+import java.io.IOException;
+import org.springframework.http.HttpRequest;
+import org.springframework.http.client.ClientHttpResponse;
+import org.springframework.web.client.RestClient.ResponseSpec.ErrorHandler;
+
+public class MapServerErrorHandler implements ErrorHandler {
+
+ @Override
+ public void handle(HttpRequest request, ClientHttpResponse response) throws IOException {
+ throw new BusinessException(BusinessErrorCode.MAP_SERVER_ERROR);
+ }
+}
diff --git a/src/main/java/eatda/client/map/StoreSearchResult.java b/src/main/java/eatda/client/map/StoreSearchResult.java
new file mode 100644
index 00000000..04e22cc0
--- /dev/null
+++ b/src/main/java/eatda/client/map/StoreSearchResult.java
@@ -0,0 +1,30 @@
+package eatda.client.map;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public record StoreSearchResult(
+ @JsonProperty("id") String kakaoId,
+ @JsonProperty("category_group_code") String categoryGroupCode,
+ @JsonProperty("category_name") String categoryName,
+ @JsonProperty("phone") String phoneNumber,
+ @JsonProperty("place_name") String name,
+ @JsonProperty("place_url") String placeUrl,
+ @JsonProperty("address_name") String lotNumberAddress,
+ @JsonProperty("road_address_name") String roadAddress,
+ @JsonProperty("y") double latitude,
+ @JsonProperty("x") double longitude
+) {
+
+ public boolean isFoodStore() {
+ return "FD6".equals(categoryGroupCode);
+ }
+
+ public boolean isInSeoul() {
+ if (lotNumberAddress == null || lotNumberAddress.isBlank()) {
+ return false;
+ }
+ return lotNumberAddress.trim().startsWith("์์ธ");
+ }
+}
diff --git a/src/main/java/eatda/client/map/StoreSearchResults.java b/src/main/java/eatda/client/map/StoreSearchResults.java
new file mode 100644
index 00000000..2258ab3f
--- /dev/null
+++ b/src/main/java/eatda/client/map/StoreSearchResults.java
@@ -0,0 +1,9 @@
+package eatda.client.map;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.List;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public record StoreSearchResults(@JsonProperty("documents") List results) {
+}
diff --git a/src/main/java/timeeat/client/oauth/OauthClient.java b/src/main/java/eatda/client/oauth/OauthClient.java
similarity index 95%
rename from src/main/java/timeeat/client/oauth/OauthClient.java
rename to src/main/java/eatda/client/oauth/OauthClient.java
index b358d4db..1d1d0c95 100644
--- a/src/main/java/timeeat/client/oauth/OauthClient.java
+++ b/src/main/java/eatda/client/oauth/OauthClient.java
@@ -1,5 +1,7 @@
-package timeeat.client.oauth;
+package eatda.client.oauth;
+import eatda.exception.BusinessErrorCode;
+import eatda.exception.BusinessException;
import java.net.URI;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.http.HttpStatusCode;
@@ -9,8 +11,6 @@
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestClient;
import org.springframework.web.util.UriComponentsBuilder;
-import timeeat.exception.BusinessErrorCode;
-import timeeat.exception.BusinessException;
@Component
@EnableConfigurationProperties(OauthProperties.class)
diff --git a/src/main/java/timeeat/client/oauth/OauthMemberInformation.java b/src/main/java/eatda/client/oauth/OauthMemberInformation.java
similarity index 56%
rename from src/main/java/timeeat/client/oauth/OauthMemberInformation.java
rename to src/main/java/eatda/client/oauth/OauthMemberInformation.java
index 38fc4ed8..2d03035f 100644
--- a/src/main/java/timeeat/client/oauth/OauthMemberInformation.java
+++ b/src/main/java/eatda/client/oauth/OauthMemberInformation.java
@@ -1,15 +1,15 @@
-package timeeat.client.oauth;
+package eatda.client.oauth;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
-import timeeat.domain.member.Member;
+import eatda.domain.member.Member;
@JsonDeserialize(using = OauthMemberInformationDeserializer.class)
@JsonIgnoreProperties(ignoreUnknown = true)
-public record OauthMemberInformation(long socialId, String nickname) {
+public record OauthMemberInformation(long socialId, String email, String nickname) {
public Member toMember() {
- return new Member(Long.toString(socialId), nickname);
+ return new Member(Long.toString(socialId), email, nickname);
}
}
diff --git a/src/main/java/timeeat/client/oauth/OauthMemberInformationDeserializer.java b/src/main/java/eatda/client/oauth/OauthMemberInformationDeserializer.java
similarity index 78%
rename from src/main/java/timeeat/client/oauth/OauthMemberInformationDeserializer.java
rename to src/main/java/eatda/client/oauth/OauthMemberInformationDeserializer.java
index da79f5ac..b06692cc 100644
--- a/src/main/java/timeeat/client/oauth/OauthMemberInformationDeserializer.java
+++ b/src/main/java/eatda/client/oauth/OauthMemberInformationDeserializer.java
@@ -1,10 +1,10 @@
-package timeeat.client.oauth;
+package eatda.client.oauth;
-import java.io.IOException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
+import java.io.IOException;
public class OauthMemberInformationDeserializer extends JsonDeserializer {
@@ -14,11 +14,15 @@ public OauthMemberInformation deserialize(JsonParser jsonParser,
JsonNode root = jsonParser.getCodec().readTree(jsonParser);
long id = root.path("id").asLong();
+ String email = root
+ .path("kakao_account")
+ .path("email")
+ .asText(null);
String nickname = root
.path("kakao_account")
.path("profile")
.path("nickname")
.asText(null);
- return new OauthMemberInformation(id, nickname);
+ return new OauthMemberInformation(id, email, nickname);
}
}
diff --git a/src/main/java/timeeat/client/oauth/OauthProperties.java b/src/main/java/eatda/client/oauth/OauthProperties.java
similarity index 96%
rename from src/main/java/timeeat/client/oauth/OauthProperties.java
rename to src/main/java/eatda/client/oauth/OauthProperties.java
index b0b89ecd..857db323 100644
--- a/src/main/java/timeeat/client/oauth/OauthProperties.java
+++ b/src/main/java/eatda/client/oauth/OauthProperties.java
@@ -1,10 +1,10 @@
-package timeeat.client.oauth;
+package eatda.client.oauth;
+import eatda.exception.InitializeException;
import java.net.URI;
import java.util.List;
import lombok.Getter;
import org.springframework.boot.context.properties.ConfigurationProperties;
-import timeeat.exception.InitializeException;
@Getter
@ConfigurationProperties(prefix = "oauth")
diff --git a/src/main/java/timeeat/client/oauth/OauthServerErrorHandler.java b/src/main/java/eatda/client/oauth/OauthServerErrorHandler.java
similarity index 81%
rename from src/main/java/timeeat/client/oauth/OauthServerErrorHandler.java
rename to src/main/java/eatda/client/oauth/OauthServerErrorHandler.java
index fdc1bc0b..26fc2ae5 100644
--- a/src/main/java/timeeat/client/oauth/OauthServerErrorHandler.java
+++ b/src/main/java/eatda/client/oauth/OauthServerErrorHandler.java
@@ -1,12 +1,12 @@
-package timeeat.client.oauth;
+package eatda.client.oauth;
+import eatda.exception.BusinessErrorCode;
+import eatda.exception.BusinessException;
import java.io.IOException;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClient.ResponseSpec.ErrorHandler;
-import timeeat.exception.BusinessErrorCode;
-import timeeat.exception.BusinessException;
@Component
public class OauthServerErrorHandler implements ErrorHandler {
diff --git a/src/main/java/timeeat/client/oauth/OauthToken.java b/src/main/java/eatda/client/oauth/OauthToken.java
similarity index 88%
rename from src/main/java/timeeat/client/oauth/OauthToken.java
rename to src/main/java/eatda/client/oauth/OauthToken.java
index 97e8ed07..5cdeeb00 100644
--- a/src/main/java/timeeat/client/oauth/OauthToken.java
+++ b/src/main/java/eatda/client/oauth/OauthToken.java
@@ -1,4 +1,4 @@
-package timeeat.client.oauth;
+package eatda.client.oauth;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
diff --git a/src/main/java/timeeat/config/CorsConfig.java b/src/main/java/eatda/config/CorsConfig.java
similarity index 95%
rename from src/main/java/timeeat/config/CorsConfig.java
rename to src/main/java/eatda/config/CorsConfig.java
index 81927ff7..a8667be4 100644
--- a/src/main/java/timeeat/config/CorsConfig.java
+++ b/src/main/java/eatda/config/CorsConfig.java
@@ -1,11 +1,11 @@
-package timeeat.config;
+package eatda.config;
+import eatda.exception.InitializeException;
import java.util.List;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
-import timeeat.exception.InitializeException;
@Configuration
public class CorsConfig implements WebMvcConfigurer {
diff --git a/src/main/java/timeeat/config/CorsProperties.java b/src/main/java/eatda/config/CorsProperties.java
similarity index 93%
rename from src/main/java/timeeat/config/CorsProperties.java
rename to src/main/java/eatda/config/CorsProperties.java
index 636f73ff..7a8eae65 100644
--- a/src/main/java/timeeat/config/CorsProperties.java
+++ b/src/main/java/eatda/config/CorsProperties.java
@@ -1,10 +1,10 @@
-package timeeat.config;
+package eatda.config;
import java.util.List;
-import org.springframework.boot.context.properties.ConfigurationProperties;
-import org.springframework.stereotype.Component;
import lombok.Getter;
import lombok.Setter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
@Getter
@Setter
diff --git a/src/main/java/timeeat/config/WebConfig.java b/src/main/java/eatda/config/WebConfig.java
similarity index 81%
rename from src/main/java/timeeat/config/WebConfig.java
rename to src/main/java/eatda/config/WebConfig.java
index e1656c2c..6e6eb52b 100644
--- a/src/main/java/timeeat/config/WebConfig.java
+++ b/src/main/java/eatda/config/WebConfig.java
@@ -1,12 +1,12 @@
-package timeeat.config;
+package eatda.config;
+import eatda.controller.web.auth.AuthMemberArgumentResolver;
+import eatda.controller.web.jwt.JwtManager;
import java.util.List;
+import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
-import lombok.RequiredArgsConstructor;
-import timeeat.controller.web.auth.AuthMemberArgumentResolver;
-import timeeat.controller.web.jwt.JwtManager;
@Configuration
@RequiredArgsConstructor
diff --git a/src/main/java/timeeat/controller/auth/AuthController.java b/src/main/java/eatda/controller/auth/AuthController.java
similarity index 92%
rename from src/main/java/timeeat/controller/auth/AuthController.java
rename to src/main/java/eatda/controller/auth/AuthController.java
index 1de6f6e6..eb989fd9 100644
--- a/src/main/java/timeeat/controller/auth/AuthController.java
+++ b/src/main/java/eatda/controller/auth/AuthController.java
@@ -1,5 +1,8 @@
-package timeeat.controller.auth;
+package eatda.controller.auth;
+import eatda.controller.member.MemberResponse;
+import eatda.controller.web.jwt.JwtManager;
+import eatda.service.auth.AuthService;
import java.net.URI;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpHeaders;
@@ -10,9 +13,6 @@
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;
-import timeeat.controller.member.MemberResponse;
-import timeeat.controller.web.jwt.JwtManager;
-import timeeat.service.auth.AuthService;
@RestController
@RequiredArgsConstructor
diff --git a/src/main/java/timeeat/controller/auth/LoginRequest.java b/src/main/java/eatda/controller/auth/LoginRequest.java
similarity index 64%
rename from src/main/java/timeeat/controller/auth/LoginRequest.java
rename to src/main/java/eatda/controller/auth/LoginRequest.java
index 824430fc..9f8fcd0c 100644
--- a/src/main/java/timeeat/controller/auth/LoginRequest.java
+++ b/src/main/java/eatda/controller/auth/LoginRequest.java
@@ -1,4 +1,4 @@
-package timeeat.controller.auth;
+package eatda.controller.auth;
public record LoginRequest(String code, String origin) {
}
diff --git a/src/main/java/timeeat/controller/auth/LoginResponse.java b/src/main/java/eatda/controller/auth/LoginResponse.java
similarity index 50%
rename from src/main/java/timeeat/controller/auth/LoginResponse.java
rename to src/main/java/eatda/controller/auth/LoginResponse.java
index 5b6de9b9..5946e4ee 100644
--- a/src/main/java/timeeat/controller/auth/LoginResponse.java
+++ b/src/main/java/eatda/controller/auth/LoginResponse.java
@@ -1,6 +1,6 @@
-package timeeat.controller.auth;
+package eatda.controller.auth;
-import timeeat.controller.member.MemberResponse;
+import eatda.controller.member.MemberResponse;
public record LoginResponse(TokenResponse token, MemberResponse information) {
diff --git a/src/main/java/timeeat/controller/auth/ReissueRequest.java b/src/main/java/eatda/controller/auth/ReissueRequest.java
similarity index 62%
rename from src/main/java/timeeat/controller/auth/ReissueRequest.java
rename to src/main/java/eatda/controller/auth/ReissueRequest.java
index 82f0dc23..009ff551 100644
--- a/src/main/java/timeeat/controller/auth/ReissueRequest.java
+++ b/src/main/java/eatda/controller/auth/ReissueRequest.java
@@ -1,4 +1,4 @@
-package timeeat.controller.auth;
+package eatda.controller.auth;
public record ReissueRequest(String refreshToken) {
}
diff --git a/src/main/java/timeeat/controller/auth/TokenResponse.java b/src/main/java/eatda/controller/auth/TokenResponse.java
similarity index 69%
rename from src/main/java/timeeat/controller/auth/TokenResponse.java
rename to src/main/java/eatda/controller/auth/TokenResponse.java
index e6e2f916..4786c290 100644
--- a/src/main/java/timeeat/controller/auth/TokenResponse.java
+++ b/src/main/java/eatda/controller/auth/TokenResponse.java
@@ -1,4 +1,4 @@
-package timeeat.controller.auth;
+package eatda.controller.auth;
public record TokenResponse(String accessToken, String refreshToken) {
}
diff --git a/src/main/java/timeeat/controller/member/MemberController.java b/src/main/java/eatda/controller/member/MemberController.java
similarity index 80%
rename from src/main/java/timeeat/controller/member/MemberController.java
rename to src/main/java/eatda/controller/member/MemberController.java
index 00bd53e1..9f6daf4f 100644
--- a/src/main/java/timeeat/controller/member/MemberController.java
+++ b/src/main/java/eatda/controller/member/MemberController.java
@@ -1,5 +1,7 @@
-package timeeat.controller.member;
+package eatda.controller.member;
+import eatda.controller.web.auth.LoginMember;
+import eatda.service.member.MemberService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
@@ -8,8 +10,6 @@
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
-import timeeat.controller.web.auth.LoginMember;
-import timeeat.service.service.MemberService;
@RestController
@RequiredArgsConstructor
@@ -17,6 +17,12 @@ public class MemberController {
private final MemberService memberService;
+ @GetMapping("/api/member")
+ public ResponseEntity getMember(LoginMember member) {
+ MemberResponse response = memberService.getMember(member.id());
+ return ResponseEntity.ok(response);
+ }
+
@GetMapping("/api/member/nickname/check")
public ResponseEntity checkNickname(LoginMember member, @RequestParam String nickname) {
memberService.validateNickname(nickname, member.id());
diff --git a/src/main/java/timeeat/controller/member/MemberResponse.java b/src/main/java/eatda/controller/member/MemberResponse.java
similarity index 77%
rename from src/main/java/timeeat/controller/member/MemberResponse.java
rename to src/main/java/eatda/controller/member/MemberResponse.java
index 45fc302f..91274c27 100644
--- a/src/main/java/timeeat/controller/member/MemberResponse.java
+++ b/src/main/java/eatda/controller/member/MemberResponse.java
@@ -1,21 +1,21 @@
-package timeeat.controller.member;
+package eatda.controller.member;
-import timeeat.domain.member.Member;
+import eatda.domain.member.Member;
public record MemberResponse(long id,
+ String email,
boolean isSignUp,
String nickname,
String phoneNumber,
- String interestArea,
Boolean optInMarketing
) {
public MemberResponse(Member member, boolean isSignUp) {
this(member.getId(),
+ member.getEmail(),
isSignUp,
member.getNickname(),
member.getPhoneNumber(),
- member.getInterestAreaName(),
member.getOptInMarketing());
}
diff --git a/src/main/java/timeeat/controller/member/MemberUpdateRequest.java b/src/main/java/eatda/controller/member/MemberUpdateRequest.java
similarity index 56%
rename from src/main/java/timeeat/controller/member/MemberUpdateRequest.java
rename to src/main/java/eatda/controller/member/MemberUpdateRequest.java
index 028938f9..ec8a893c 100644
--- a/src/main/java/timeeat/controller/member/MemberUpdateRequest.java
+++ b/src/main/java/eatda/controller/member/MemberUpdateRequest.java
@@ -1,14 +1,13 @@
-package timeeat.controller.member;
+package eatda.controller.member;
+import eatda.domain.member.Member;
import jakarta.validation.constraints.NotBlank;
-import timeeat.domain.member.Member;
public record MemberUpdateRequest(@NotBlank String nickname,
@NotBlank String phoneNumber,
- @NotBlank String interestArea,
boolean optInMarketing) {
public Member toMemberUpdater() {
- return new Member(nickname, phoneNumber, interestArea, optInMarketing);
+ return new Member(nickname, phoneNumber, optInMarketing);
}
}
diff --git a/src/main/java/eatda/controller/store/CheerController.java b/src/main/java/eatda/controller/store/CheerController.java
new file mode 100644
index 00000000..b2fc61a4
--- /dev/null
+++ b/src/main/java/eatda/controller/store/CheerController.java
@@ -0,0 +1,23 @@
+package eatda.controller.store;
+
+import eatda.service.store.CheerService;
+import jakarta.validation.constraints.Max;
+import jakarta.validation.constraints.Min;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequiredArgsConstructor
+public class CheerController {
+
+ private final CheerService cheerService;
+
+ @GetMapping("/api/cheer")
+ public ResponseEntity getCheers(@RequestParam @Min(1) @Max(50) int size) {
+ CheersResponse response = cheerService.getCheers(size);
+ return ResponseEntity.ok(response);
+ }
+}
diff --git a/src/main/java/eatda/controller/store/CheerPreviewResponse.java b/src/main/java/eatda/controller/store/CheerPreviewResponse.java
new file mode 100644
index 00000000..73525aef
--- /dev/null
+++ b/src/main/java/eatda/controller/store/CheerPreviewResponse.java
@@ -0,0 +1,29 @@
+package eatda.controller.store;
+
+import eatda.domain.store.Cheer;
+import eatda.domain.store.Store;
+
+public record CheerPreviewResponse(
+ long storeId,
+ String imageUrl,
+ String storeName,
+ String storeDistrict,
+ String storeNeighborhood,
+ String storeCategory,
+ long cheerId,
+ String cheerDescription
+) {
+
+ public CheerPreviewResponse(Cheer cheer, Store store, String imageUrl) {
+ this(
+ store.getId(),
+ imageUrl,
+ store.getName(),
+ store.getAddressDistrict(),
+ store.getAddressNeighborhood(),
+ store.getCategory().getCategoryName(),
+ cheer.getId(),
+ cheer.getDescription()
+ );
+ }
+}
diff --git a/src/main/java/eatda/controller/store/CheersResponse.java b/src/main/java/eatda/controller/store/CheersResponse.java
new file mode 100644
index 00000000..2ad034e5
--- /dev/null
+++ b/src/main/java/eatda/controller/store/CheersResponse.java
@@ -0,0 +1,7 @@
+package eatda.controller.store;
+
+import java.util.List;
+
+public record CheersResponse(List cheers) {
+
+}
diff --git a/src/main/java/eatda/controller/store/StoreController.java b/src/main/java/eatda/controller/store/StoreController.java
new file mode 100644
index 00000000..09d8e0a9
--- /dev/null
+++ b/src/main/java/eatda/controller/store/StoreController.java
@@ -0,0 +1,29 @@
+package eatda.controller.store;
+
+import eatda.controller.web.auth.LoginMember;
+import eatda.service.store.StoreService;
+import jakarta.validation.constraints.Max;
+import jakarta.validation.constraints.Min;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequiredArgsConstructor
+public class StoreController {
+
+ private final StoreService storeService;
+
+ @GetMapping("/api/shops")
+ public ResponseEntity getStores(@RequestParam @Min(1) @Max(50) int size) {
+ return ResponseEntity.ok(storeService.getStores(size));
+ }
+
+ @GetMapping("/api/shop/search")
+ public ResponseEntity searchStore(@RequestParam String query, LoginMember member) {
+ StoreSearchResponses response = storeService.searchStores(query);
+ return ResponseEntity.ok(response);
+ }
+}
diff --git a/src/main/java/eatda/controller/store/StorePreviewResponse.java b/src/main/java/eatda/controller/store/StorePreviewResponse.java
new file mode 100644
index 00000000..00a6e18f
--- /dev/null
+++ b/src/main/java/eatda/controller/store/StorePreviewResponse.java
@@ -0,0 +1,24 @@
+package eatda.controller.store;
+
+import eatda.domain.store.Store;
+
+public record StorePreviewResponse(
+ long id,
+ String imageUrl,
+ String name,
+ String district,
+ String neighborhood,
+ String category
+) {
+
+ public StorePreviewResponse(Store store, String imageUrl) {
+ this(
+ store.getId(),
+ imageUrl,
+ store.getName(),
+ store.getAddressDistrict(),
+ store.getAddressNeighborhood(),
+ store.getCategory().getCategoryName()
+ );
+ }
+}
diff --git a/src/main/java/eatda/controller/store/StoreSearchResponse.java b/src/main/java/eatda/controller/store/StoreSearchResponse.java
new file mode 100644
index 00000000..bc1ae03a
--- /dev/null
+++ b/src/main/java/eatda/controller/store/StoreSearchResponse.java
@@ -0,0 +1,18 @@
+package eatda.controller.store;
+
+import eatda.client.map.StoreSearchResult;
+
+public record StoreSearchResponse(
+ String kakaoId,
+ String name,
+ String address
+) {
+
+ public StoreSearchResponse(StoreSearchResult searchResult) {
+ this(
+ searchResult.kakaoId(),
+ searchResult.name(),
+ searchResult.lotNumberAddress()
+ );
+ }
+}
diff --git a/src/main/java/eatda/controller/store/StoreSearchResponses.java b/src/main/java/eatda/controller/store/StoreSearchResponses.java
new file mode 100644
index 00000000..6b34ef61
--- /dev/null
+++ b/src/main/java/eatda/controller/store/StoreSearchResponses.java
@@ -0,0 +1,14 @@
+package eatda.controller.store;
+
+import eatda.client.map.StoreSearchResult;
+import java.util.List;
+
+public record StoreSearchResponses(List stores) {
+
+ public static StoreSearchResponses from(List searchResults) {
+ List storeResponses = searchResults.stream()
+ .map(StoreSearchResponse::new)
+ .toList();
+ return new StoreSearchResponses(storeResponses);
+ }
+}
diff --git a/src/main/java/eatda/controller/store/StoresResponse.java b/src/main/java/eatda/controller/store/StoresResponse.java
new file mode 100644
index 00000000..a5652aab
--- /dev/null
+++ b/src/main/java/eatda/controller/store/StoresResponse.java
@@ -0,0 +1,6 @@
+package eatda.controller.store;
+
+import java.util.List;
+
+public record StoresResponse(List stores) {
+}
diff --git a/src/main/java/eatda/controller/story/FilteredSearchResult.java b/src/main/java/eatda/controller/story/FilteredSearchResult.java
new file mode 100644
index 00000000..89d94efe
--- /dev/null
+++ b/src/main/java/eatda/controller/story/FilteredSearchResult.java
@@ -0,0 +1,9 @@
+package eatda.controller.story;
+
+public record FilteredSearchResult(
+ String kakaoId,
+ String name,
+ String address,
+ String category
+) {
+}
diff --git a/src/main/java/eatda/controller/story/StoriesResponse.java b/src/main/java/eatda/controller/story/StoriesResponse.java
new file mode 100644
index 00000000..8829449e
--- /dev/null
+++ b/src/main/java/eatda/controller/story/StoriesResponse.java
@@ -0,0 +1,13 @@
+package eatda.controller.story;
+
+import java.util.List;
+
+public record StoriesResponse(
+ List stories
+) {
+ public record StoryPreview(
+ Long storyId,
+ String imageUrl
+ ) {
+ }
+}
diff --git a/src/main/java/eatda/controller/story/StoryController.java b/src/main/java/eatda/controller/story/StoryController.java
new file mode 100644
index 00000000..382ae3d4
--- /dev/null
+++ b/src/main/java/eatda/controller/story/StoryController.java
@@ -0,0 +1,28 @@
+package eatda.controller.story;
+
+import eatda.controller.web.auth.LoginMember;
+import eatda.service.story.StoryService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestPart;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+@RestController
+@RequiredArgsConstructor
+public class StoryController {
+
+ private final StoryService storyService;
+
+ @PostMapping("/api/stories")
+ public ResponseEntity registerStory(
+ @RequestPart("request") StoryRegisterRequest request,
+ @RequestPart("image") MultipartFile image,
+ LoginMember member
+ ) {
+ storyService.registerStory(request, image, member.id());
+ return ResponseEntity.status(HttpStatus.CREATED).build();
+ }
+}
diff --git a/src/main/java/eatda/controller/story/StoryRegisterRequest.java b/src/main/java/eatda/controller/story/StoryRegisterRequest.java
new file mode 100644
index 00000000..aeebfd4e
--- /dev/null
+++ b/src/main/java/eatda/controller/story/StoryRegisterRequest.java
@@ -0,0 +1,8 @@
+package eatda.controller.story;
+
+public record StoryRegisterRequest(
+ String query,
+ String storeKakaoId,
+ String description
+) {
+}
diff --git a/src/main/java/eatda/controller/story/StoryResponse.java b/src/main/java/eatda/controller/story/StoryResponse.java
new file mode 100644
index 00000000..df46d476
--- /dev/null
+++ b/src/main/java/eatda/controller/story/StoryResponse.java
@@ -0,0 +1,11 @@
+package eatda.controller.story;
+
+public record StoryResponse(
+ String storeKakaoId,
+ String category,
+ String storeName,
+ String storeAddress,
+ String description,
+ String imageUrl
+) {
+}
diff --git a/src/main/java/timeeat/controller/web/auth/AuthMemberArgumentResolver.java b/src/main/java/eatda/controller/web/auth/AuthMemberArgumentResolver.java
similarity index 88%
rename from src/main/java/timeeat/controller/web/auth/AuthMemberArgumentResolver.java
rename to src/main/java/eatda/controller/web/auth/AuthMemberArgumentResolver.java
index 6be3ea32..b74fd6a5 100644
--- a/src/main/java/timeeat/controller/web/auth/AuthMemberArgumentResolver.java
+++ b/src/main/java/eatda/controller/web/auth/AuthMemberArgumentResolver.java
@@ -1,5 +1,8 @@
-package timeeat.controller.web.auth;
+package eatda.controller.web.auth;
+import eatda.controller.web.jwt.JwtManager;
+import eatda.exception.BusinessErrorCode;
+import eatda.exception.BusinessException;
import lombok.RequiredArgsConstructor;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
@@ -7,9 +10,6 @@
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
-import timeeat.controller.web.jwt.JwtManager;
-import timeeat.exception.BusinessErrorCode;
-import timeeat.exception.BusinessException;
@RequiredArgsConstructor
public class AuthMemberArgumentResolver implements HandlerMethodArgumentResolver {
diff --git a/src/main/java/timeeat/controller/web/auth/LoginMember.java b/src/main/java/eatda/controller/web/auth/LoginMember.java
similarity index 51%
rename from src/main/java/timeeat/controller/web/auth/LoginMember.java
rename to src/main/java/eatda/controller/web/auth/LoginMember.java
index e50c00d8..ab9d7403 100644
--- a/src/main/java/timeeat/controller/web/auth/LoginMember.java
+++ b/src/main/java/eatda/controller/web/auth/LoginMember.java
@@ -1,4 +1,4 @@
-package timeeat.controller.web.auth;
+package eatda.controller.web.auth;
public record LoginMember(long id) {
}
diff --git a/src/main/java/timeeat/controller/web/jwt/JwtManager.java b/src/main/java/eatda/controller/web/jwt/JwtManager.java
similarity index 95%
rename from src/main/java/timeeat/controller/web/jwt/JwtManager.java
rename to src/main/java/eatda/controller/web/jwt/JwtManager.java
index b48b2d4b..c0be0305 100644
--- a/src/main/java/timeeat/controller/web/jwt/JwtManager.java
+++ b/src/main/java/eatda/controller/web/jwt/JwtManager.java
@@ -1,14 +1,14 @@
-package timeeat.controller.web.jwt;
+package eatda.controller.web.jwt;
+import eatda.exception.BusinessErrorCode;
+import eatda.exception.BusinessException;
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.ExpiredJwtException;
+import io.jsonwebtoken.Jwts;
import java.time.Duration;
import java.util.Date;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Component;
-import io.jsonwebtoken.Claims;
-import io.jsonwebtoken.ExpiredJwtException;
-import io.jsonwebtoken.Jwts;
-import timeeat.exception.BusinessErrorCode;
-import timeeat.exception.BusinessException;
@Component
@EnableConfigurationProperties(JwtProperties.class)
diff --git a/src/main/java/timeeat/controller/web/jwt/JwtProperties.java b/src/main/java/eatda/controller/web/jwt/JwtProperties.java
similarity index 95%
rename from src/main/java/timeeat/controller/web/jwt/JwtProperties.java
rename to src/main/java/eatda/controller/web/jwt/JwtProperties.java
index b294d767..d41e13be 100644
--- a/src/main/java/timeeat/controller/web/jwt/JwtProperties.java
+++ b/src/main/java/eatda/controller/web/jwt/JwtProperties.java
@@ -1,12 +1,12 @@
-package timeeat.controller.web.jwt;
+package eatda.controller.web.jwt;
+import eatda.exception.InitializeException;
import io.jsonwebtoken.security.Keys;
import java.time.Duration;
import java.util.Base64;
import javax.crypto.SecretKey;
import lombok.Getter;
import org.springframework.boot.context.properties.ConfigurationProperties;
-import timeeat.exception.InitializeException;
@Getter
@ConfigurationProperties(prefix = "jwt")
diff --git a/src/main/java/timeeat/controller/web/jwt/TokenType.java b/src/main/java/eatda/controller/web/jwt/TokenType.java
similarity index 63%
rename from src/main/java/timeeat/controller/web/jwt/TokenType.java
rename to src/main/java/eatda/controller/web/jwt/TokenType.java
index 9aaf522e..7397d0bd 100644
--- a/src/main/java/timeeat/controller/web/jwt/TokenType.java
+++ b/src/main/java/eatda/controller/web/jwt/TokenType.java
@@ -1,4 +1,4 @@
-package timeeat.controller.web.jwt;
+package eatda.controller.web.jwt;
public enum TokenType {
ACCESS_TOKEN,
diff --git a/src/main/java/eatda/domain/AuditingEntity.java b/src/main/java/eatda/domain/AuditingEntity.java
new file mode 100644
index 00000000..bac7a511
--- /dev/null
+++ b/src/main/java/eatda/domain/AuditingEntity.java
@@ -0,0 +1,20 @@
+package eatda.domain;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.MappedSuperclass;
+import jakarta.persistence.PrePersist;
+import java.time.LocalDateTime;
+import lombok.Getter;
+
+@Getter
+@MappedSuperclass
+public abstract class AuditingEntity {
+
+ @Column(name = "created_at", nullable = false)
+ private LocalDateTime createdAt;
+
+ @PrePersist
+ protected void onCreate() {
+ this.createdAt = LocalDateTime.now();
+ }
+}
diff --git a/src/main/java/eatda/domain/article/Article.java b/src/main/java/eatda/domain/article/Article.java
new file mode 100644
index 00000000..9e59e72a
--- /dev/null
+++ b/src/main/java/eatda/domain/article/Article.java
@@ -0,0 +1,42 @@
+package eatda.domain.article;
+
+import eatda.domain.AuditingEntity;
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import jakarta.persistence.Table;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@Table(name = "article")
+@Entity
+@Getter
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+public class Article extends AuditingEntity {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @Column(nullable = false)
+ private String title;
+
+ @Column(nullable = false)
+ private String subtitle;
+
+ @Column(name = "article_url", nullable = false, length = 511)
+ private String articleUrl;
+
+ @Column(name = "image_key", nullable = false, length = 511)
+ private String imageKey;
+
+ public Article(String title, String subtitle, String articleUrl, String imageKey) {
+ this.title = title;
+ this.subtitle = subtitle;
+ this.articleUrl = articleUrl;
+ this.imageKey = imageKey;
+ }
+}
diff --git a/src/main/java/timeeat/domain/member/Member.java b/src/main/java/eatda/domain/member/Member.java
similarity index 74%
rename from src/main/java/timeeat/domain/member/Member.java
rename to src/main/java/eatda/domain/member/Member.java
index 6a395090..29d5a138 100644
--- a/src/main/java/timeeat/domain/member/Member.java
+++ b/src/main/java/eatda/domain/member/Member.java
@@ -1,27 +1,28 @@
-package timeeat.domain.member;
+package eatda.domain.member;
+import eatda.domain.AuditingEntity;
+import eatda.exception.BusinessErrorCode;
+import eatda.exception.BusinessException;
import jakarta.annotation.Nullable;
import jakarta.persistence.Column;
import jakarta.persistence.Embedded;
import jakarta.persistence.Entity;
-import jakarta.persistence.EnumType;
-import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
+import java.time.LocalDateTime;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
-import timeeat.enums.InterestArea;
-import timeeat.exception.BusinessErrorCode;
-import timeeat.exception.BusinessException;
@Table(name = "member")
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
-public class Member {
+public class Member extends AuditingEntity {
+
+ private static final String EMAIL_REGEX = "^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$";
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@@ -30,43 +31,43 @@ public class Member {
@Column(name = "social_id", unique = true, nullable = false)
private String socialId;
+ @Column(name = "email", unique = true, nullable = false)
+ private String email;
+
@Column(name = "nickname")
private String nickname;
@Embedded
private MobilePhoneNumber mobilePhoneNumber;
- @Enumerated(EnumType.STRING)
- @Column(name = "interest_area")
- private InterestArea interestArea;
-
@Column(name = "opt_in_marketing")
private Boolean optInMarketing;
- public Member(String socialId, String nickname) {
+ public Member(String socialId, String email, String nickname) {
validateSocialId(socialId);
+ validateEmail(email);
this.socialId = socialId;
+ this.email = email;
this.nickname = nickname;
}
public Member(
String socialId,
+ String email,
String nickname,
String mobilePhoneNumber,
- String interestArea,
Boolean optInMarketing
) {
- this(socialId, nickname);
+ this(socialId, email, nickname);
validateOptInMarketing(optInMarketing);
this.mobilePhoneNumber = new MobilePhoneNumber(mobilePhoneNumber);
- this.interestArea = InterestArea.from(interestArea);
this.optInMarketing = optInMarketing;
}
- public Member(String nickname, String mobilePhoneNumber, String interestArea, boolean optInMarketing) {
+ public Member(String nickname, String mobilePhoneNumber, boolean optInMarketing) {
+ validateOptInMarketing(optInMarketing);
this.nickname = nickname;
this.mobilePhoneNumber = new MobilePhoneNumber(mobilePhoneNumber);
- this.interestArea = InterestArea.from(interestArea);
this.optInMarketing = optInMarketing;
}
@@ -76,6 +77,12 @@ private void validateSocialId(String socialId) {
}
}
+ private void validateEmail(String email) {
+ if (email == null || !email.matches(EMAIL_REGEX)) {
+ throw new BusinessException(BusinessErrorCode.INVALID_EMAIL);
+ }
+ }
+
private void validateOptInMarketing(Boolean optInMarketing) {
if (optInMarketing == null) {
throw new BusinessException(BusinessErrorCode.INVALID_MARKETING_CONSENT);
@@ -85,7 +92,6 @@ private void validateOptInMarketing(Boolean optInMarketing) {
public void update(Member member) {
this.nickname = member.nickname;
this.mobilePhoneNumber = member.mobilePhoneNumber;
- this.interestArea = member.interestArea;
this.optInMarketing = member.optInMarketing;
}
@@ -111,13 +117,5 @@ public String getPhoneNumber() {
}
return mobilePhoneNumber.getValue();
}
-
- @Nullable
- public String getInterestAreaName() {
- if (interestArea == null) {
- return null;
- }
- return interestArea.getAreaName();
- }
}
diff --git a/src/main/java/timeeat/domain/member/MobilePhoneNumber.java b/src/main/java/eatda/domain/member/MobilePhoneNumber.java
similarity index 88%
rename from src/main/java/timeeat/domain/member/MobilePhoneNumber.java
rename to src/main/java/eatda/domain/member/MobilePhoneNumber.java
index f1db966f..dab42c46 100644
--- a/src/main/java/timeeat/domain/member/MobilePhoneNumber.java
+++ b/src/main/java/eatda/domain/member/MobilePhoneNumber.java
@@ -1,14 +1,14 @@
-package timeeat.domain.member;
+package eatda.domain.member;
-import java.util.regex.Pattern;
+import eatda.exception.BusinessErrorCode;
+import eatda.exception.BusinessException;
import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
+import java.util.regex.Pattern;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
-import timeeat.exception.BusinessErrorCode;
-import timeeat.exception.BusinessException;
@Getter
@Embeddable
diff --git a/src/main/java/eatda/domain/store/Cheer.java b/src/main/java/eatda/domain/store/Cheer.java
new file mode 100644
index 00000000..9339dc4d
--- /dev/null
+++ b/src/main/java/eatda/domain/store/Cheer.java
@@ -0,0 +1,74 @@
+package eatda.domain.store;
+
+import eatda.domain.AuditingEntity;
+import eatda.domain.member.Member;
+import eatda.exception.BusinessErrorCode;
+import eatda.exception.BusinessException;
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.FetchType;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import jakarta.persistence.JoinColumn;
+import jakarta.persistence.ManyToOne;
+import jakarta.persistence.Table;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@Table(name = "cheer")
+@Entity
+@Getter
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+public class Cheer extends AuditingEntity {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "member_id", nullable = false)
+ private Member member;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "store_id", nullable = false)
+ private Store store;
+
+ @Column(nullable = false, columnDefinition = "TEXT")
+ private String description;
+
+ @Column(name = "image_key", length = 511)
+ private String imageKey;
+
+ @Column(name = "is_admin", nullable = false)
+ private boolean isAdmin;
+
+ public Cheer(Member member, Store store, String description, String imageKey) {
+ validateDescription(description);
+ validateImageKey(imageKey);
+ this.member = member;
+ this.store = store;
+ this.description = description;
+ this.imageKey = imageKey;
+
+ this.isAdmin = false;
+ }
+
+ public Cheer(Member member, Store store, String description, String imageKey, boolean isAdmin) {
+ this(member, store, description, imageKey);
+ this.isAdmin = isAdmin;
+ }
+
+ private void validateDescription(String description) {
+ if (description == null || description.isBlank()) {
+ throw new BusinessException(BusinessErrorCode.INVALID_CHEER_DESCRIPTION);
+ }
+ }
+
+ private void validateImageKey(String imageKey) {
+ if (imageKey != null && imageKey.isBlank()) {
+ throw new BusinessException(BusinessErrorCode.INVALID_CHEER_IMAGE_KEY);
+ }
+ }
+}
diff --git a/src/main/java/timeeat/domain/store/Coordinates.java b/src/main/java/eatda/domain/store/Coordinates.java
similarity index 78%
rename from src/main/java/timeeat/domain/store/Coordinates.java
rename to src/main/java/eatda/domain/store/Coordinates.java
index efbfa87d..57a07c66 100644
--- a/src/main/java/timeeat/domain/store/Coordinates.java
+++ b/src/main/java/eatda/domain/store/Coordinates.java
@@ -1,13 +1,13 @@
-package timeeat.domain.store;
+package eatda.domain.store;
+import eatda.exception.BusinessErrorCode;
+import eatda.exception.BusinessException;
import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
-import timeeat.exception.BusinessErrorCode;
-import timeeat.exception.BusinessException;
@Getter
@Embeddable
@@ -19,6 +19,23 @@ public class Coordinates {
private static final double MAX_LATITUDE = 37.715133;
private static final double MIN_LONGITUDE = 126.734086;
private static final double MAX_LONGITUDE = 127.269311;
+
+ public static double getMinLatitude() {
+ return MIN_LATITUDE;
+ }
+
+ public static double getMaxLatitude() {
+ return MAX_LATITUDE;
+ }
+
+ public static double getMinLongitude() {
+ return MIN_LONGITUDE;
+ }
+
+ public static double getMaxLongitude() {
+ return MAX_LONGITUDE;
+ }
+
@Column(nullable = false)
private Double latitude;
diff --git a/src/main/java/eatda/domain/store/Store.java b/src/main/java/eatda/domain/store/Store.java
new file mode 100644
index 00000000..37e804f5
--- /dev/null
+++ b/src/main/java/eatda/domain/store/Store.java
@@ -0,0 +1,89 @@
+package eatda.domain.store;
+
+import eatda.domain.AuditingEntity;
+import jakarta.persistence.Column;
+import jakarta.persistence.Embedded;
+import jakarta.persistence.Entity;
+import jakarta.persistence.EnumType;
+import jakarta.persistence.Enumerated;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import jakarta.persistence.Table;
+import java.time.LocalDateTime;
+import lombok.AccessLevel;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@Table(name = "store")
+@Entity
+@Getter
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+public class Store extends AuditingEntity {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @Column(name = "kakao_id", unique = true, nullable = false)
+ private String kakaoId;
+
+ @Enumerated(EnumType.STRING)
+ @Column(name = "category", nullable = false)
+ private StoreCategory category;
+
+ @Column(name = "phone_number", nullable = false)
+ private String phoneNumber;
+
+ @Column(name = "name", nullable = false)
+ private String name;
+
+ @Column(name = "place_url", nullable = false)
+ private String placeUrl;
+
+ @Column(name = "road_address", nullable = false)
+ private String roadAddress;
+
+ @Column(name = "lot_number_address", nullable = false)
+ private String lotNumberAddress;
+
+ @Embedded
+ private Coordinates coordinates;
+
+ @Builder
+ private Store(String kakaoId,
+ StoreCategory category,
+ String phoneNumber,
+ String name,
+ String placeUrl,
+ String roadAddress,
+ String lotNumberAddress,
+ Double latitude,
+ Double longitude) {
+ this.kakaoId = kakaoId;
+ this.category = category;
+ this.phoneNumber = phoneNumber;
+ this.name = name;
+ this.placeUrl = placeUrl;
+ this.roadAddress = roadAddress;
+ this.lotNumberAddress = lotNumberAddress;
+ this.coordinates = new Coordinates(latitude, longitude);
+ }
+
+ public String getAddressDistrict() {
+ String[] addressParts = lotNumberAddress.split(" ");
+ if (addressParts.length < 2) {
+ return "";
+ }
+ return addressParts[1];
+ }
+
+ public String getAddressNeighborhood() {
+ String[] addressParts = lotNumberAddress.split(" ");
+ if (addressParts.length < 3) {
+ return "";
+ }
+ return addressParts[2];
+ }
+}
diff --git a/src/main/java/timeeat/enums/StoreCategory.java b/src/main/java/eatda/domain/store/StoreCategory.java
similarity index 90%
rename from src/main/java/timeeat/enums/StoreCategory.java
rename to src/main/java/eatda/domain/store/StoreCategory.java
index c5687bcc..c2cafe12 100644
--- a/src/main/java/timeeat/enums/StoreCategory.java
+++ b/src/main/java/eatda/domain/store/StoreCategory.java
@@ -1,9 +1,9 @@
-package timeeat.enums;
+package eatda.domain.store;
+import eatda.exception.BusinessErrorCode;
+import eatda.exception.BusinessException;
import java.util.Arrays;
import lombok.Getter;
-import timeeat.exception.BusinessErrorCode;
-import timeeat.exception.BusinessException;
@Getter
public enum StoreCategory {
diff --git a/src/main/java/eatda/domain/story/Story.java b/src/main/java/eatda/domain/story/Story.java
new file mode 100644
index 00000000..97817acf
--- /dev/null
+++ b/src/main/java/eatda/domain/story/Story.java
@@ -0,0 +1,129 @@
+package eatda.domain.story;
+
+import eatda.domain.AuditingEntity;
+import eatda.domain.member.Member;
+import eatda.exception.BusinessErrorCode;
+import eatda.exception.BusinessException;
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.FetchType;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import jakarta.persistence.JoinColumn;
+import jakarta.persistence.ManyToOne;
+import jakarta.persistence.Table;
+import lombok.AccessLevel;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@Table(name = "story")
+@Entity
+@Getter
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+public class Story extends AuditingEntity {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "member_id", nullable = false)
+ private Member member;
+
+ @Column(name = "store_kakao_id", nullable = false)
+ private String storeKakaoId;
+
+ @Column(name = "store_name", nullable = false)
+ private String storeName;
+
+ @Column(name = "store_address", nullable = false)
+ private String storeAddress;
+
+ @Column(name = "store_category", nullable = false)
+ private String storeCategory;
+
+ @Column(name = "description", nullable = false)
+ private String description;
+
+ @Column(name = "image_key", nullable = false)
+ private String imageKey;
+
+ @Builder
+ private Story(
+ Member member,
+ String storeKakaoId,
+ String storeName,
+ String storeAddress,
+ String storeCategory,
+ String description,
+ String imageKey
+ ) {
+ validateMember(member);
+ validateStore(storeKakaoId, storeName, storeAddress, storeCategory);
+ validateStory(description, imageKey);
+
+ this.member = member;
+ this.storeKakaoId = storeKakaoId;
+ this.storeName = storeName;
+ this.storeAddress = storeAddress;
+ this.storeCategory = storeCategory;
+ this.description = description;
+ this.imageKey = imageKey;
+ }
+
+ private void validateMember(Member member) {
+ if (member == null) {
+ throw new BusinessException(BusinessErrorCode.STORY_MEMBER_REQUIRED);
+ }
+ }
+
+ private void validateStore(String storeKakaoId, String storeName, String storeAddress, String storeCategory) {
+ validateStoreKakaoId(storeKakaoId);
+ validateStoreName(storeName);
+ validateStoreAddress(storeAddress);
+ validateStoreCategory(storeCategory);
+ }
+
+ private void validateStory(String description, String imageKey) {
+ validateDescription(description);
+ validateImage(imageKey);
+ }
+
+ private void validateStoreKakaoId(String storeKakaoId) {
+ if (storeKakaoId == null || storeKakaoId.isBlank()) {
+ throw new BusinessException(BusinessErrorCode.INVALID_STORE_KAKAO_ID);
+ }
+ }
+
+ private void validateStoreName(String storeName) {
+ if (storeName == null || storeName.isBlank()) {
+ throw new BusinessException(BusinessErrorCode.INVALID_STORE_NAME);
+ }
+ }
+
+ private void validateStoreAddress(String storeAddress) {
+ if (storeAddress == null || storeAddress.isBlank()) {
+ throw new BusinessException(BusinessErrorCode.INVALID_STORE_ADDRESS);
+ }
+ }
+
+ private void validateStoreCategory(String storeCategory) {
+ if (storeCategory == null || storeCategory.isBlank()) {
+ throw new BusinessException(BusinessErrorCode.INVALID_STORE_CATEGORY);
+ }
+ }
+
+ private void validateDescription(String description) {
+ if (description == null || description.isBlank()) {
+ throw new BusinessException(BusinessErrorCode.INVALID_STORY_DESCRIPTION);
+ }
+ }
+
+ private void validateImage(String imageKey) {
+ if (imageKey == null || imageKey.isBlank()) {
+ throw new BusinessException(BusinessErrorCode.INVALID_STORY_IMAGE_KEY);
+ }
+ }
+}
diff --git a/src/main/java/timeeat/exception/BusinessErrorCode.java b/src/main/java/eatda/exception/BusinessErrorCode.java
similarity index 52%
rename from src/main/java/timeeat/exception/BusinessErrorCode.java
rename to src/main/java/eatda/exception/BusinessErrorCode.java
index 3369eb26..fea49646 100644
--- a/src/main/java/timeeat/exception/BusinessErrorCode.java
+++ b/src/main/java/eatda/exception/BusinessErrorCode.java
@@ -1,4 +1,4 @@
-package timeeat.exception;
+package eatda.exception;
import lombok.Getter;
import org.springframework.http.HttpStatus;
@@ -14,39 +14,44 @@ public enum BusinessErrorCode {
INVALID_SOCIAL_ID("MEM005", "์์
ID๋ ํ์์
๋๋ค."),
DUPLICATE_NICKNAME("MEM006", "์ด๋ฏธ ์ฌ์ฉ ์ค์ธ ๋๋ค์์
๋๋ค."),
DUPLICATE_PHONE_NUMBER("MEM007", "์ด๋ฏธ ์ฌ์ฉ ์ค์ธ ์ ํ๋ฒํธ์
๋๋ค."),
+ INVALID_EMAIL("MEM008", "์ ํจํ์ง ์์ ์ด๋ฉ์ผ ํ์์
๋๋ค."),
// Store
INVALID_STORE_CATEGORY("STO001", "์ ํจํ์ง ์์ ๋งค์ฅ ์นดํ
๊ณ ๋ฆฌ์
๋๋ค."),
- INVALID_STORE_NAME("STO002", "๋งค์ฅ๋ช
์ ํ์์
๋๋ค."),
- INVALID_STORE_ADDRESS("STO003", "๋งค์ฅ ์ฃผ์๋ ํ์์
๋๋ค."),
- INVALID_STORE_PHONE_NUMBER("STO004", "๋งค์ฅ ์ ํ๋ฒํธ๋ 9~12์๋ฆฌ์ฌ์ผ ํฉ๋๋ค."),
- INVALID_STORE_COORDINATES("STO005", "์ ํจํ์ง ์์ ์ขํ์
๋๋ค."),
- INVALID_STORE_ID("STO006", "์ ํจํ์ง ์์ ๋งค์ฅ ID์
๋๋ค."),
INVALID_STORE_COORDINATES_NULL("STO007", "์ขํ ๊ฐ์ ํ์์
๋๋ค."),
- INVALID_STORE_TIME_NULL("STO008", "์์
์๊ฐ์ ํ์์
๋๋ค."),
- INVALID_STORE_TIME_ORDER("STO009", "์ข
๋ฃ ์๊ฐ์ ์์ ์๊ฐ๋ณด๋ค ๋ฆ์ด์ผ ํฉ๋๋ค."),
OUT_OF_SEOUL_LATITUDE_RANGE("STO010", "์๋น์ค ์ง์ญ(์์ธ)์ ๋ฒ์ด๋ ์๋ ๊ฐ์
๋๋ค."),
OUT_OF_SEOUL_LONGITUDE_RANGE("STO011", "์๋น์ค ์ง์ญ(์์ธ)์ ๋ฒ์ด๋ ๊ฒฝ๋ ๊ฐ์
๋๋ค."),
+ STORE_NOT_FOUND("ST0012", "ํด๋น ๊ฐ๊ฒ ์ ๋ณด๋ฅผ ์ฐพ์์ ์์ต๋๋ค."),
- // Menu
- INVALID_MENU_NAME("MEN001", "๋ฉ๋ด๋ช
์ ํ์์
๋๋ค."),
- INVALID_MENU_PRICE("MEN002", "๋ฉ๋ด ๊ฐ๊ฒฉ์ 0๋ณด๋ค ์ปค์ผ ํฉ๋๋ค."),
- INVALID_MENU_DISCOUNT_PRICE("MEN003", "ํ ์ธ ๊ฐ๊ฒฉ์ ์๊ฐ๋ณด๋ค ์์์ผ ํฉ๋๋ค."),
- INVALID_MENU_DISCOUNT_TIME("MEN004", "ํ ์ธ ์๊ฐ์ด ์ฌ๋ฐ๋ฅด์ง ์์ต๋๋ค."),
- INVALID_MENU_LENGTH("MEN005", "๋ฉ๋ด๋ช
๊ธธ์ด๊ฐ ์ต๋๋ฅผ ์ด๊ณผํ์ต๋๋ค."),
+ // Cheer
+ INVALID_CHEER_DESCRIPTION("CHE001", "์์ ๋ฉ์์ง๋ ํ์์
๋๋ค."),
+ INVALID_CHEER_IMAGE_KEY("CHE002", "์์ ์ด๋ฏธ์ง ํค๊ฐ ๋น์ด ์์ต๋๋ค.", HttpStatus.INTERNAL_SERVER_ERROR),
- // Bookmark
- DUPLICATE_BOOKMARK("BOK001", "์ด๋ฏธ ๋ถ๋งํฌ๋ ๋งค์ฅ์
๋๋ค."),
- BOOKMARK_NOT_FOUND("BOK002", "๋ถ๋งํฌ๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค."),
- BOOKMARK_MEMBER_REQUIRED("BOK003", "๋ถ๋งํฌ ์์ฑ ์ ํ์ ์ ๋ณด๋ ํ์์
๋๋ค."),
- BOOKMARK_STORE_REQUIRED("BOK004", "๋ถ๋งํฌ ์์ฑ ์ ๊ฐ๊ฒ ์ ๋ณด๋ ํ์์
๋๋ค."),
+ // Map
+ MAP_SERVER_ERROR("MAP001", "์ง๋ ์๋ฒ์์ ํต์ ์ค๋ฅ์
๋๋ค.", HttpStatus.INTERNAL_SERVER_ERROR),
// Auth
UNAUTHORIZED_MEMBER("AUTH001", "์ธ์ฆ๋์ง ์์ ํ์์
๋๋ค.", HttpStatus.UNAUTHORIZED),
EXPIRED_TOKEN("AUTH002", "์ด๋ฏธ ๋ง๋ฃ๋ ํ ํฐ์
๋๋ค.", HttpStatus.UNAUTHORIZED),
UNAUTHORIZED_ORIGIN("AUTH003", "ํ์ฉ๋์ง ์์ ์ค๋ฆฌ์ง์
๋๋ค."),
OAUTH_SERVER_ERROR("AUTH003", "OAuth ์๋ฒ์์ ํต์ ์ค๋ฅ์
๋๋ค.", HttpStatus.INTERNAL_SERVER_ERROR),
- ;
+
+ // image
+ INVALID_IMAGE_TYPE("CLIENT010", "์ง์ํ์ง ์๋ ์ด๋ฏธ์ง ํ์์
๋๋ค.", HttpStatus.BAD_REQUEST),
+ FILE_UPLOAD_FAILED("SERVER002", "ํ์ผ ์
๋ก๋์ ์คํจํ์ต๋๋ค.", HttpStatus.INTERNAL_SERVER_ERROR),
+ FILE_URL_GENERATION_FAILED("SERVER003", "ํ์ผ URL ์์ฑ์ ์คํจํ์ต๋๋ค.", HttpStatus.INTERNAL_SERVER_ERROR),
+ PRESIGNED_URL_GENERATION_FAILED("SERVER004", "Presigned URL ์์ฑ์ ์คํจํ์ต๋๋ค.", HttpStatus.INTERNAL_SERVER_ERROR),
+
+ //story
+ INVALID_STORY_DESCRIPTION("STY001", "์คํ ๋ฆฌ ๋ณธ๋ฌธ์ ํ์์
๋๋ค."),
+ INVALID_STORY_IMAGE_KEY("STY002", "์คํ ๋ฆฌ ์ด๋ฏธ์ง Key๋ ํ์์
๋๋ค."),
+ STORY_MEMBER_REQUIRED("STY003", "์คํ ๋ฆฌ ์์ฑ ์ ํ์ ์ ๋ณด๋ ํ์์
๋๋ค."),
+ STORY_STORE_REQUIRED("STY004", "์คํ ๋ฆฌ ์์ฑ ์ ๊ฐ๊ฒ ์ ๋ณด๋ ํ์์
๋๋ค."),
+ STORY_NOT_FOUND("STY005", "์คํ ๋ฆฌ๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค."),
+ INVALID_STORE_ID("STY006", "์ ํจํ์ง ์์ ๊ฐ๊ฒ ID์
๋๋ค."),
+ INVALID_STORE_KAKAO_ID("STY007", "์คํ ์ด Kakao ID๋ ํ์์
๋๋ค."),
+ INVALID_STORE_NAME("STY008", "์คํ ์ด ์ด๋ฆ์ ํ์์
๋๋ค."),
+ INVALID_STORE_ADDRESS("STY009", "์คํ ์ด ์ฃผ์๋ ํ์์
๋๋ค.");
private final String code;
private final String message;
diff --git a/src/main/java/timeeat/exception/BusinessException.java b/src/main/java/eatda/exception/BusinessException.java
similarity index 93%
rename from src/main/java/timeeat/exception/BusinessException.java
rename to src/main/java/eatda/exception/BusinessException.java
index bc332e04..8ecceec5 100644
--- a/src/main/java/timeeat/exception/BusinessException.java
+++ b/src/main/java/eatda/exception/BusinessException.java
@@ -1,4 +1,4 @@
-package timeeat.exception;
+package eatda.exception;
import lombok.Getter;
import org.springframework.http.HttpStatus;
diff --git a/src/main/java/timeeat/exception/ErrorResponse.java b/src/main/java/eatda/exception/ErrorResponse.java
similarity index 91%
rename from src/main/java/timeeat/exception/ErrorResponse.java
rename to src/main/java/eatda/exception/ErrorResponse.java
index 5a168651..1170cce5 100644
--- a/src/main/java/timeeat/exception/ErrorResponse.java
+++ b/src/main/java/eatda/exception/ErrorResponse.java
@@ -1,4 +1,4 @@
-package timeeat.exception;
+package eatda.exception;
public record ErrorResponse(String errorCode, String message) {
diff --git a/src/main/java/timeeat/exception/EtcErrorCode.java b/src/main/java/eatda/exception/EtcErrorCode.java
similarity index 91%
rename from src/main/java/timeeat/exception/EtcErrorCode.java
rename to src/main/java/eatda/exception/EtcErrorCode.java
index f8e45ef0..94b4a418 100644
--- a/src/main/java/timeeat/exception/EtcErrorCode.java
+++ b/src/main/java/eatda/exception/EtcErrorCode.java
@@ -1,4 +1,4 @@
-package timeeat.exception;
+package eatda.exception;
import lombok.Getter;
import org.springframework.http.HttpStatus;
@@ -15,6 +15,7 @@ public enum EtcErrorCode {
NO_COOKIE_FOUND("CLIENT007", "ํ์ ์ฟ ํค ๊ฐ์ด ์กด์ฌํ์ง ์์ต๋๋ค.", HttpStatus.BAD_REQUEST),
NO_HEADER_FOUND("CLIENT008", "ํ์ ํค๋ ๊ฐ์ด ์กด์ฌํ์ง ์์ต๋๋ค.", HttpStatus.BAD_REQUEST),
NO_PARAMETER_FOUND("CLIENT009", "ํ์ ํ๋ผ๋ฏธํฐ ๊ฐ์ด ์กด์ฌํ์ง ์์ต๋๋ค.", HttpStatus.BAD_REQUEST),
+ VALIDATION_ERROR("CLIENT010", "์์ฒญ ๋ฐ์ดํฐ ๊ฐ์ด ๋ฒ์๋ฅผ ๋ฒ์ด๋ฌ์ต๋๋ค", HttpStatus.BAD_REQUEST),
INTERNAL_SERVER_ERROR("SERVER001", "์๋ฒ ๋ด๋ถ ์๋ฌ๊ฐ ๋ฐ์ํ์ต๋๋ค.", HttpStatus.INTERNAL_SERVER_ERROR),
;
diff --git a/src/main/java/timeeat/exception/GlobalExceptionHandler.java b/src/main/java/eatda/exception/GlobalExceptionHandler.java
similarity index 87%
rename from src/main/java/timeeat/exception/GlobalExceptionHandler.java
rename to src/main/java/eatda/exception/GlobalExceptionHandler.java
index 87a86713..c1688b63 100644
--- a/src/main/java/timeeat/exception/GlobalExceptionHandler.java
+++ b/src/main/java/eatda/exception/GlobalExceptionHandler.java
@@ -1,7 +1,8 @@
-package timeeat.exception;
+package eatda.exception;
import jakarta.validation.ConstraintViolationException;
import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.connector.ClientAbortException;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindException;
@@ -12,9 +13,11 @@
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
+import org.springframework.web.method.annotation.HandlerMethodValidationException;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.resource.NoResourceFoundException;
+@Slf4j
@RestControllerAdvice
@RequiredArgsConstructor
public class GlobalExceptionHandler {
@@ -75,8 +78,15 @@ public ResponseEntity handleMissingServletRequestParameterExcepti
return toErrorResponse(EtcErrorCode.NO_PARAMETER_FOUND);
}
+ @ExceptionHandler(HandlerMethodValidationException.class)
+ public ResponseEntity handleHandlerMethodValidationException(
+ HandlerMethodValidationException exception) {
+ return toErrorResponse(EtcErrorCode.VALIDATION_ERROR);
+ }
+
@ExceptionHandler(BusinessException.class)
public ResponseEntity handleBusinessException(BusinessException exception) {
+ log.error("[BusinessException] handled: {}", exception.getErrorCode());
ErrorResponse response = new ErrorResponse(exception.getErrorCode());
return ResponseEntity.status(exception.getStatus())
.body(response);
@@ -84,6 +94,7 @@ public ResponseEntity handleBusinessException(BusinessException e
@ExceptionHandler(Exception.class)
public ResponseEntity handleException(Exception exception) {
+ log.error("[Unhandled Exception] {}: {}", exception.getClass().getSimpleName(), exception.getMessage(), exception);
return toErrorResponse(EtcErrorCode.INTERNAL_SERVER_ERROR);
}
diff --git a/src/main/java/timeeat/exception/InitializeException.java b/src/main/java/eatda/exception/InitializeException.java
similarity index 90%
rename from src/main/java/timeeat/exception/InitializeException.java
rename to src/main/java/eatda/exception/InitializeException.java
index 89bb8470..6a9a5c95 100644
--- a/src/main/java/timeeat/exception/InitializeException.java
+++ b/src/main/java/eatda/exception/InitializeException.java
@@ -1,4 +1,4 @@
-package timeeat.exception;
+package eatda.exception;
public class InitializeException extends RuntimeException {
diff --git a/src/main/java/timeeat/repository/member/MemberRepository.java b/src/main/java/eatda/repository/member/MemberRepository.java
similarity index 77%
rename from src/main/java/timeeat/repository/member/MemberRepository.java
rename to src/main/java/eatda/repository/member/MemberRepository.java
index 9e25fc02..b74afa35 100644
--- a/src/main/java/timeeat/repository/member/MemberRepository.java
+++ b/src/main/java/eatda/repository/member/MemberRepository.java
@@ -1,10 +1,10 @@
-package timeeat.repository.member;
+package eatda.repository.member;
+import eatda.domain.member.Member;
+import eatda.exception.BusinessErrorCode;
+import eatda.exception.BusinessException;
import java.util.Optional;
import org.springframework.data.repository.Repository;
-import timeeat.domain.member.Member;
-import timeeat.exception.BusinessErrorCode;
-import timeeat.exception.BusinessException;
public interface MemberRepository extends Repository {
diff --git a/src/main/java/eatda/repository/store/CheerRepository.java b/src/main/java/eatda/repository/store/CheerRepository.java
new file mode 100644
index 00000000..0ea9c490
--- /dev/null
+++ b/src/main/java/eatda/repository/store/CheerRepository.java
@@ -0,0 +1,23 @@
+package eatda.repository.store;
+
+import eatda.domain.store.Cheer;
+import eatda.domain.store.Store;
+import java.util.List;
+import java.util.Optional;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.Repository;
+
+public interface CheerRepository extends Repository {
+
+ Cheer save(Cheer cheer);
+
+ List findAllByOrderByCreatedAtDesc(Pageable pageable);
+
+ @Query("""
+ SELECT c.imageKey FROM Cheer c
+ WHERE c.store = :store AND c.imageKey IS NOT NULL
+ ORDER BY c.createdAt DESC
+ LIMIT 1""")
+ Optional findRecentImageKey(Store store);
+}
diff --git a/src/main/java/eatda/repository/store/StoreRepository.java b/src/main/java/eatda/repository/store/StoreRepository.java
new file mode 100644
index 00000000..8fc482fb
--- /dev/null
+++ b/src/main/java/eatda/repository/store/StoreRepository.java
@@ -0,0 +1,13 @@
+package eatda.repository.store;
+
+import eatda.domain.store.Store;
+import java.util.List;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.repository.Repository;
+
+public interface StoreRepository extends Repository {
+
+ Store save(Store store);
+
+ List findAllByOrderByCreatedAtDesc(Pageable pageable);
+}
diff --git a/src/main/java/eatda/repository/story/StoryRepository.java b/src/main/java/eatda/repository/story/StoryRepository.java
new file mode 100644
index 00000000..f17c9e12
--- /dev/null
+++ b/src/main/java/eatda/repository/story/StoryRepository.java
@@ -0,0 +1,19 @@
+package eatda.repository.story;
+
+import eatda.domain.story.Story;
+import eatda.exception.BusinessErrorCode;
+import eatda.exception.BusinessException;
+import java.util.Optional;
+import org.springframework.data.repository.Repository;
+
+public interface StoryRepository extends Repository {
+
+ Story save(Story story);
+
+ Optional findById(Long id);
+
+ default Story getById(Long id) {
+ return findById(id)
+ .orElseThrow(() -> new BusinessException(BusinessErrorCode.STORY_NOT_FOUND));
+ }
+}
diff --git a/src/main/java/timeeat/service/auth/AuthService.java b/src/main/java/eatda/service/auth/AuthService.java
similarity index 76%
rename from src/main/java/timeeat/service/auth/AuthService.java
rename to src/main/java/eatda/service/auth/AuthService.java
index 24032661..a71d5dc3 100644
--- a/src/main/java/timeeat/service/auth/AuthService.java
+++ b/src/main/java/eatda/service/auth/AuthService.java
@@ -1,18 +1,18 @@
-package timeeat.service.auth;
+package eatda.service.auth;
+import eatda.client.oauth.OauthClient;
+import eatda.client.oauth.OauthMemberInformation;
+import eatda.client.oauth.OauthToken;
+import eatda.controller.auth.LoginRequest;
+import eatda.controller.member.MemberResponse;
+import eatda.domain.member.Member;
+import eatda.repository.member.MemberRepository;
import java.net.URI;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
-import timeeat.client.oauth.OauthClient;
-import timeeat.client.oauth.OauthMemberInformation;
-import timeeat.client.oauth.OauthToken;
-import timeeat.controller.auth.LoginRequest;
-import timeeat.controller.member.MemberResponse;
-import timeeat.domain.member.Member;
-import timeeat.repository.member.MemberRepository;
@Slf4j
@Service
diff --git a/src/main/java/eatda/service/common/ImageDomain.java b/src/main/java/eatda/service/common/ImageDomain.java
new file mode 100644
index 00000000..5d155c0b
--- /dev/null
+++ b/src/main/java/eatda/service/common/ImageDomain.java
@@ -0,0 +1,15 @@
+package eatda.service.common;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@Getter
+@RequiredArgsConstructor
+public enum ImageDomain {
+ ARTICLE("article"),
+ STORE("store"),
+ MEMBER("member"),
+ STORY("story");
+
+ private final String name;
+}
diff --git a/src/main/java/eatda/service/common/ImageService.java b/src/main/java/eatda/service/common/ImageService.java
new file mode 100644
index 00000000..676a89d4
--- /dev/null
+++ b/src/main/java/eatda/service/common/ImageService.java
@@ -0,0 +1,100 @@
+package eatda.service.common;
+
+import eatda.exception.BusinessErrorCode;
+import eatda.exception.BusinessException;
+import java.io.IOException;
+import java.time.Duration;
+import java.util.Set;
+import java.util.UUID;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.lang.Nullable;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+import software.amazon.awssdk.core.sync.RequestBody;
+import software.amazon.awssdk.services.s3.S3Client;
+import software.amazon.awssdk.services.s3.model.GetObjectRequest;
+import software.amazon.awssdk.services.s3.model.PutObjectRequest;
+import software.amazon.awssdk.services.s3.presigner.S3Presigner;
+import software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest;
+
+@Service
+public class ImageService {
+
+ private static final Set ALLOWED_CONTENT_TYPES = Set.of("image/jpg", "image/jpeg", "image/png");
+ private static final String DEFAULT_CONTENT_TYPE = "bin";
+ private static final String PATH_DELIMITER = "/";
+ private static final String EXTENSION_DELIMITER = ".";
+ private static final Duration PRESIGNED_URL_DURATION = Duration.ofMinutes(30);
+
+ private final S3Client s3Client;
+ private final String bucket;
+ private final S3Presigner s3Presigner;
+
+ public ImageService(
+ S3Client s3Client,
+ @Value("${spring.cloud.aws.s3.bucket}") String bucket,
+ S3Presigner s3Presigner) {
+ this.s3Client = s3Client;
+ this.bucket = bucket;
+ this.s3Presigner = s3Presigner;
+ }
+
+ public String upload(MultipartFile file, ImageDomain domain) {
+ validateContentType(file);
+ String extension = getExtension(file.getOriginalFilename());
+ String uuid = UUID.randomUUID().toString();
+ String key = domain.getName() + PATH_DELIMITER + uuid + EXTENSION_DELIMITER + extension;
+
+ try {
+ PutObjectRequest request = PutObjectRequest.builder()
+ .bucket(bucket)
+ .key(key)
+ .contentType(file.getContentType())
+ .build();
+
+ s3Client.putObject(request, RequestBody.fromInputStream(file.getInputStream(), file.getSize()));
+ return key;
+ } catch (IOException exception) {
+ throw new BusinessException(BusinessErrorCode.FILE_UPLOAD_FAILED);
+ }
+ }
+
+ private void validateContentType(MultipartFile file) {
+ String contentType = file.getContentType();
+ if (!ALLOWED_CONTENT_TYPES.contains(contentType)) {
+ throw new BusinessException(BusinessErrorCode.INVALID_IMAGE_TYPE);
+ }
+ }
+
+ private String getExtension(String filename) {
+ if (filename == null
+ || filename.lastIndexOf(EXTENSION_DELIMITER) == -1
+ || filename.startsWith(EXTENSION_DELIMITER)) {
+ return DEFAULT_CONTENT_TYPE;
+ }
+ return filename.substring(filename.lastIndexOf(EXTENSION_DELIMITER) + 1);
+ }
+
+ @Nullable
+ public String getPresignedUrl(@Nullable String key) {
+ if (key == null) {
+ return null;
+ }
+
+ try {
+ GetObjectRequest getObjectRequest = GetObjectRequest.builder()
+ .bucket(bucket)
+ .key(key)
+ .build();
+
+ GetObjectPresignRequest presignRequest = GetObjectPresignRequest.builder()
+ .getObjectRequest(getObjectRequest)
+ .signatureDuration(PRESIGNED_URL_DURATION)
+ .build();
+
+ return s3Presigner.presignGetObject(presignRequest).url().toString();
+ } catch (Exception exception) {
+ throw new BusinessException(BusinessErrorCode.PRESIGNED_URL_GENERATION_FAILED);
+ }
+ }
+}
diff --git a/src/main/java/timeeat/service/service/MemberService.java b/src/main/java/eatda/service/member/MemberService.java
similarity index 78%
rename from src/main/java/timeeat/service/service/MemberService.java
rename to src/main/java/eatda/service/member/MemberService.java
index 95735437..37f33555 100644
--- a/src/main/java/timeeat/service/service/MemberService.java
+++ b/src/main/java/eatda/service/member/MemberService.java
@@ -1,14 +1,14 @@
-package timeeat.service.service;
-
+package eatda.service.member;
+
+import eatda.controller.member.MemberResponse;
+import eatda.controller.member.MemberUpdateRequest;
+import eatda.domain.member.Member;
+import eatda.exception.BusinessErrorCode;
+import eatda.exception.BusinessException;
+import eatda.repository.member.MemberRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
-import timeeat.controller.member.MemberResponse;
-import timeeat.controller.member.MemberUpdateRequest;
-import timeeat.domain.member.Member;
-import timeeat.exception.BusinessErrorCode;
-import timeeat.exception.BusinessException;
-import timeeat.repository.member.MemberRepository;
@Service
@RequiredArgsConstructor
@@ -16,6 +16,12 @@ public class MemberService {
private final MemberRepository memberRepository;
+ @Transactional(readOnly = true)
+ public MemberResponse getMember(long memberId) {
+ Member member = memberRepository.getById(memberId);
+ return new MemberResponse(member);
+ }
+
@Transactional(readOnly = true)
public void validateNickname(String nickname, long memberId) {
Member member = memberRepository.getById(memberId);
diff --git a/src/main/java/eatda/service/store/CheerService.java b/src/main/java/eatda/service/store/CheerService.java
new file mode 100644
index 00000000..e8a98a39
--- /dev/null
+++ b/src/main/java/eatda/service/store/CheerService.java
@@ -0,0 +1,33 @@
+package eatda.service.store;
+
+import eatda.controller.store.CheerPreviewResponse;
+import eatda.controller.store.CheersResponse;
+import eatda.domain.store.Cheer;
+import eatda.repository.store.CheerRepository;
+import eatda.service.common.ImageService;
+import java.util.List;
+import lombok.RequiredArgsConstructor;
+import org.springframework.data.domain.Pageable;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@RequiredArgsConstructor
+public class CheerService {
+
+ private final ImageService imageService;
+ private final CheerRepository cheerRepository;
+
+ @Transactional(readOnly = true)
+ public CheersResponse getCheers(int size) {
+ List cheers = cheerRepository.findAllByOrderByCreatedAtDesc(Pageable.ofSize(size));
+ return toCheersResponse(cheers);
+ }
+
+ private CheersResponse toCheersResponse(List cheers) {
+ return new CheersResponse(cheers.stream()
+ .map(cheer -> new CheerPreviewResponse(cheer, cheer.getStore(),
+ imageService.getPresignedUrl(cheer.getImageKey())))
+ .toList());
+ }
+}
diff --git a/src/main/java/eatda/service/store/StoreSearchFilter.java b/src/main/java/eatda/service/store/StoreSearchFilter.java
new file mode 100644
index 00000000..0604c68d
--- /dev/null
+++ b/src/main/java/eatda/service/store/StoreSearchFilter.java
@@ -0,0 +1,19 @@
+package eatda.service.store;
+
+import eatda.client.map.StoreSearchResult;
+import java.util.List;
+import org.springframework.stereotype.Component;
+
+@Component
+public class StoreSearchFilter {
+
+ public List filterSearchedStores(List searchResults) {
+ return searchResults.stream()
+ .filter(this::isValidStore)
+ .toList();
+ }
+
+ private boolean isValidStore(StoreSearchResult store) {
+ return store.isFoodStore() && store.isInSeoul();
+ }
+}
diff --git a/src/main/java/eatda/service/store/StoreService.java b/src/main/java/eatda/service/store/StoreService.java
new file mode 100644
index 00000000..ded2b722
--- /dev/null
+++ b/src/main/java/eatda/service/store/StoreService.java
@@ -0,0 +1,54 @@
+package eatda.service.store;
+
+import static java.util.stream.Collectors.collectingAndThen;
+import static java.util.stream.Collectors.toList;
+
+import eatda.client.map.MapClient;
+import eatda.client.map.StoreSearchResult;
+import eatda.controller.store.StorePreviewResponse;
+import eatda.controller.store.StoreSearchResponses;
+import eatda.controller.store.StoresResponse;
+import eatda.domain.store.Store;
+import eatda.repository.store.CheerRepository;
+import eatda.repository.store.StoreRepository;
+import eatda.service.common.ImageService;
+import java.util.List;
+import java.util.Optional;
+import lombok.RequiredArgsConstructor;
+import org.springframework.data.domain.Pageable;
+import org.springframework.stereotype.Service;
+
+@Service
+@RequiredArgsConstructor
+public class StoreService {
+
+ private final MapClient mapClient;
+ private final StoreSearchFilter storeSearchFilter;
+ private final StoreRepository storeRepository;
+ private final CheerRepository cheerRepository;
+ private final ImageService imageService;
+
+ // TODO : N+1 ๋ฌธ์ ํด๊ฒฐ
+ public StoresResponse getStores(int size) {
+ return storeRepository.findAllByOrderByCreatedAtDesc(Pageable.ofSize(size))
+ .stream()
+ .map(store -> new StorePreviewResponse(store, getStoreImageUrl(store).orElse(null)))
+ .collect(collectingAndThen(toList(), StoresResponse::new));
+ }
+
+ private Optional getStoreImageUrl(Store store) {
+ return cheerRepository.findRecentImageKey(store)
+ .map(imageService::getPresignedUrl);
+ }
+
+ public StoreSearchResponses searchStores(String query) {
+ List searchResults = mapClient.searchShops(query);
+ List filteredResults = storeSearchFilter.filterSearchedStores(searchResults);
+ return StoreSearchResponses.from(filteredResults);
+ }
+
+ public List searchStoreResults(String query) {
+ List searchResults = mapClient.searchShops(query);
+ return storeSearchFilter.filterSearchedStores(searchResults);
+ }
+}
diff --git a/src/main/java/eatda/service/story/StoryService.java b/src/main/java/eatda/service/story/StoryService.java
new file mode 100644
index 00000000..7724b1cb
--- /dev/null
+++ b/src/main/java/eatda/service/story/StoryService.java
@@ -0,0 +1,62 @@
+package eatda.service.story;
+
+import eatda.client.map.StoreSearchResult;
+import eatda.controller.story.FilteredSearchResult;
+import eatda.controller.story.StoryRegisterRequest;
+import eatda.domain.member.Member;
+import eatda.domain.story.Story;
+import eatda.exception.BusinessErrorCode;
+import eatda.exception.BusinessException;
+import eatda.repository.member.MemberRepository;
+import eatda.repository.story.StoryRepository;
+import eatda.service.common.ImageDomain;
+import eatda.service.common.ImageService;
+import eatda.service.store.StoreService;
+import java.util.List;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.multipart.MultipartFile;
+
+@Service
+@RequiredArgsConstructor
+public class StoryService {
+
+ private final StoreService storeService;
+ private final ImageService imageService;
+ private final StoryRepository storyRepository;
+ private final MemberRepository memberRepository;
+
+ @Transactional
+ public void registerStory(StoryRegisterRequest request, MultipartFile image, Long memberId) {
+ Member member = memberRepository.getById(memberId);
+ List searchResponses = storeService.searchStoreResults(request.query());
+ FilteredSearchResult matchedStore = filteredSearchResponse(searchResponses, request.storeKakaoId());
+ String imageKey = imageService.upload(image, ImageDomain.STORY);
+
+ Story story = Story.builder()
+ .member(member)
+ .storeKakaoId(matchedStore.kakaoId())
+ .storeName(matchedStore.name())
+ .storeAddress(matchedStore.address())
+ .storeCategory(matchedStore.category())
+ .description(request.description())
+ .imageKey(imageKey)
+ .build();
+
+ storyRepository.save(story);
+ }
+
+ private FilteredSearchResult filteredSearchResponse(List responses, String storeKakaoId) {
+ return responses.stream()
+ .filter(store -> store.kakaoId().equals(storeKakaoId))
+ .findFirst()
+ .map(store -> new FilteredSearchResult(
+ store.kakaoId(),
+ store.name(),
+ store.roadAddress(),
+ store.categoryName()
+ ))
+ .orElseThrow(() -> new BusinessException(BusinessErrorCode.STORE_NOT_FOUND));
+ }
+}
diff --git a/src/main/java/timeeat/domain/bookmark/Bookmark.java b/src/main/java/timeeat/domain/bookmark/Bookmark.java
deleted file mode 100644
index 2ca6a982..00000000
--- a/src/main/java/timeeat/domain/bookmark/Bookmark.java
+++ /dev/null
@@ -1,49 +0,0 @@
-package timeeat.domain.bookmark;
-
-import jakarta.persistence.*;
-import lombok.AccessLevel;
-import lombok.Getter;
-import lombok.NoArgsConstructor;
-import timeeat.domain.member.Member;
-import timeeat.domain.store.Store;
-import timeeat.exception.BusinessErrorCode;
-import timeeat.exception.BusinessException;
-
-@Table(name = "bookmark")
-@Entity
-@Getter
-@NoArgsConstructor(access = AccessLevel.PROTECTED)
-public class Bookmark {
-
- @Id
- @GeneratedValue(strategy = GenerationType.IDENTITY)
- private Long id;
-
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "member_id", nullable = false)
- private Member member;
-
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "store_id", nullable = false)
- private Store store;
-
- public Bookmark(Member member, Store store) {
- validateMember(member);
- validateStore(store);
- this.member = member;
- this.store = store;
- }
-
- private void validateMember(Member member) {
- if (member == null) {
- throw new BusinessException(BusinessErrorCode.BOOKMARK_MEMBER_REQUIRED);
- }
- }
-
- private void validateStore(Store store) {
- if (store == null) {
- throw new BusinessException(BusinessErrorCode.BOOKMARK_STORE_REQUIRED);
- }
- }
-}
-
diff --git a/src/main/java/timeeat/domain/menu/Discount.java b/src/main/java/timeeat/domain/menu/Discount.java
deleted file mode 100644
index 48983f52..00000000
--- a/src/main/java/timeeat/domain/menu/Discount.java
+++ /dev/null
@@ -1,45 +0,0 @@
-package timeeat.domain.menu;
-
-import java.time.LocalDateTime;
-import jakarta.persistence.Embeddable;
-import lombok.AccessLevel;
-import lombok.EqualsAndHashCode;
-import lombok.Getter;
-import lombok.NoArgsConstructor;
-import timeeat.exception.BusinessErrorCode;
-import timeeat.exception.BusinessException;
-
-@Getter
-@Embeddable
-@EqualsAndHashCode
-@NoArgsConstructor(access = AccessLevel.PROTECTED)
-public class Discount {
- private static final int MIN_PRICE = 1;
-
- private Integer discountPrice;
- private LocalDateTime startTime;
- private LocalDateTime endTime;
-
- public Discount(Price originalPrice, Integer discountPrice, LocalDateTime startTime, LocalDateTime endTime) {
- validatePrice(originalPrice, discountPrice);
- validateTime(startTime, endTime);
- this.discountPrice = discountPrice;
- this.startTime = startTime;
- this.endTime = endTime;
- }
-
- private void validatePrice(Price originalPrice, Integer discountPrice) {
- if (discountPrice != null) {
- if (discountPrice < MIN_PRICE || discountPrice >= originalPrice.getValue()) {
- throw new BusinessException(BusinessErrorCode.INVALID_MENU_DISCOUNT_PRICE);
- }
- }
- }
-
- private void validateTime(LocalDateTime startTime, LocalDateTime endTime) {
- if (startTime != null && endTime != null && startTime.isAfter(endTime)) {
- throw new BusinessException(BusinessErrorCode.INVALID_MENU_DISCOUNT_TIME);
- }
- }
-}
-
diff --git a/src/main/java/timeeat/domain/menu/Menu.java b/src/main/java/timeeat/domain/menu/Menu.java
deleted file mode 100644
index 36c3dc47..00000000
--- a/src/main/java/timeeat/domain/menu/Menu.java
+++ /dev/null
@@ -1,68 +0,0 @@
-package timeeat.domain.menu;
-
-import java.time.LocalDateTime;
-import jakarta.persistence.*;
-import lombok.AccessLevel;
-import lombok.Getter;
-import lombok.NoArgsConstructor;
-import timeeat.exception.BusinessErrorCode;
-import timeeat.exception.BusinessException;
-
-@Table(name = "menu")
-@Entity
-@Getter
-@NoArgsConstructor(access = AccessLevel.PROTECTED)
-public class Menu {
-
- private static final int MAX_NAME_LENGTH = 255;
-
- @Id
- @GeneratedValue(strategy = GenerationType.IDENTITY)
- private Long id;
-
- @Column(name = "name", nullable = false)
- private String name;
-
- @Column(name = "description")
- private String description;
-
- @Column(name = "image_url")
- private String imageUrl;
-
- @Embedded
- private Price price;
-
- @Embedded
- private Discount discount;
-
- public Menu(
- String name,
- String description,
- Integer price,
- String imageUrl,
- Integer discountPrice,
- LocalDateTime discountStartTime,
- LocalDateTime discountEndTime
- ) {
- validateName(name);
- validateNameLength(name);
-
- this.name = name;
- this.description = description;
- this.imageUrl = imageUrl;
- this.price = new Price(price);
- this.discount = new Discount(this.price, discountPrice, discountStartTime, discountEndTime);
- }
-
- private void validateName(String name) {
- if (name == null || name.trim().isEmpty()) {
- throw new BusinessException(BusinessErrorCode.INVALID_MENU_NAME);
- }
- }
-
- private void validateNameLength(String name) {
- if (name.length() > MAX_NAME_LENGTH) {
- throw new BusinessException(BusinessErrorCode.INVALID_MENU_LENGTH);
- }
- }
-}
diff --git a/src/main/java/timeeat/domain/menu/Price.java b/src/main/java/timeeat/domain/menu/Price.java
deleted file mode 100644
index 8f2afe95..00000000
--- a/src/main/java/timeeat/domain/menu/Price.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package timeeat.domain.menu;
-
-import jakarta.persistence.Column;
-import jakarta.persistence.Embeddable;
-import lombok.AccessLevel;
-import lombok.EqualsAndHashCode;
-import lombok.Getter;
-import lombok.NoArgsConstructor;
-import timeeat.exception.BusinessErrorCode;
-import timeeat.exception.BusinessException;
-
-@Getter
-@Embeddable
-@EqualsAndHashCode
-@NoArgsConstructor(access = AccessLevel.PROTECTED)
-public class Price {
-
- private static final int MIN_PRICE = 1;
-
- @Column(name = "price", nullable = false)
- private Integer value;
-
- public Price(Integer value) {
- validateMinPrice(value);
- this.value = value;
- }
-
- private void validateMinPrice(Integer value) {
- if (value == null || value < MIN_PRICE) {
- throw new BusinessException(BusinessErrorCode.INVALID_MENU_PRICE);
- }
- }
-}
diff --git a/src/main/java/timeeat/domain/store/Store.java b/src/main/java/timeeat/domain/store/Store.java
deleted file mode 100644
index 56df106b..00000000
--- a/src/main/java/timeeat/domain/store/Store.java
+++ /dev/null
@@ -1,94 +0,0 @@
-package timeeat.domain.store;
-
-import java.time.LocalTime;
-import jakarta.persistence.*;
-import lombok.AccessLevel;
-import lombok.Getter;
-import lombok.NoArgsConstructor;
-import timeeat.enums.InterestArea;
-import timeeat.enums.StoreCategory;
-import timeeat.exception.BusinessErrorCode;
-import timeeat.exception.BusinessException;
-
-@Table(name = "store")
-@Entity
-@Getter
-@NoArgsConstructor(access = AccessLevel.PROTECTED)
-public class Store {
-
- @Id
- @GeneratedValue(strategy = GenerationType.IDENTITY)
- private Long id;
-
- @Column(name = "name", nullable = false)
- private String name;
-
- @Enumerated(EnumType.STRING)
- @Column(name = "category", nullable = false)
- private StoreCategory category;
-
- @Embedded
- private Coordinates coordinates;
-
- @Column(name = "address", nullable = false)
- private String address;
-
- @Embedded
- private StorePhoneNumber storePhoneNumber;
-
- @Column(name = "image_url")
- private String imageUrl;
-
- @Embedded
- private StoreHours storeHours;
-
- @Column(name = "introduction")
- private String introduction;
-
- @Enumerated(EnumType.STRING)
- @Column(name = "interest_area", nullable = false)
- private InterestArea interestArea;
-
- //TODO ๋น๋ ํจํด์ผ๋ก ๋ณ๊ฒฝ ํฉ์, ๋ค์ ๋๋ฉ์ธ ์์ ์ ๋ฐ์ ํ์
-
- public Store(
- String name,
- String category,
- String address,
- Double latitude,
- Double longitude,
- String phoneNumber,
- String imageUrl,
- LocalTime openTime,
- LocalTime closeTime,
- String introduction,
- String interestArea
- ) {
-
- validateName(name);
- validateAddress(address);
-
- this.name = name;
- this.address = address;
- this.imageUrl = imageUrl;
- this.introduction = introduction;
-
- this.category = StoreCategory.from(category);
- this.interestArea = InterestArea.from(interestArea);
- this.coordinates = new Coordinates(latitude, longitude);
- this.storePhoneNumber = new StorePhoneNumber(phoneNumber);
- this.storeHours = new StoreHours(openTime, closeTime);
- }
-
- private void validateName(String name) {
- if (name == null || name.trim().isEmpty()) {
- throw new BusinessException(BusinessErrorCode.INVALID_STORE_NAME);
- }
- }
-
- private void validateAddress(String address) {
- if (address == null || address.trim().isEmpty()) {
- throw new BusinessException(BusinessErrorCode.INVALID_STORE_ADDRESS);
- }
- }
-}
diff --git a/src/main/java/timeeat/domain/store/StoreHours.java b/src/main/java/timeeat/domain/store/StoreHours.java
deleted file mode 100644
index 2cb439d4..00000000
--- a/src/main/java/timeeat/domain/store/StoreHours.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package timeeat.domain.store;
-
-import java.time.LocalTime;
-import jakarta.persistence.Embeddable;
-import lombok.AccessLevel;
-import lombok.EqualsAndHashCode;
-import lombok.Getter;
-import lombok.NoArgsConstructor;
-import timeeat.exception.BusinessErrorCode;
-import timeeat.exception.BusinessException;
-
-@Getter
-@Embeddable
-@EqualsAndHashCode
-@NoArgsConstructor(access = AccessLevel.PROTECTED)
-public class StoreHours {
-
- private LocalTime openTime;
- private LocalTime closeTime;
-
- public StoreHours(LocalTime openTime, LocalTime closeTime) {
- validate(openTime, closeTime);
- this.openTime = openTime;
- this.closeTime = closeTime;
- }
-
- private void validate(LocalTime openTime, LocalTime closeTime) {
- validateNotNull(openTime, closeTime);
- validateOrder(openTime, closeTime);
- }
-
- private void validateNotNull(LocalTime openTime, LocalTime closeTime) {
- if (openTime == null || closeTime == null) {
- throw new BusinessException(BusinessErrorCode.INVALID_STORE_TIME_NULL);
- }
- }
-
- private void validateOrder(LocalTime openTime, LocalTime closeTime) {
- if (openTime.isAfter(closeTime)) {
- throw new BusinessException(BusinessErrorCode.INVALID_STORE_TIME_ORDER);
- }
- }
-}
diff --git a/src/main/java/timeeat/domain/store/StorePhoneNumber.java b/src/main/java/timeeat/domain/store/StorePhoneNumber.java
deleted file mode 100644
index c3b84b5d..00000000
--- a/src/main/java/timeeat/domain/store/StorePhoneNumber.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package timeeat.domain.store;
-
-import java.util.regex.Pattern;
-import jakarta.persistence.Column;
-import jakarta.persistence.Embeddable;
-import lombok.AccessLevel;
-import lombok.EqualsAndHashCode;
-import lombok.Getter;
-import lombok.NoArgsConstructor;
-import timeeat.exception.BusinessErrorCode;
-import timeeat.exception.BusinessException;
-
-@Getter
-@Embeddable
-@EqualsAndHashCode
-@NoArgsConstructor(access = AccessLevel.PROTECTED)
-public class StorePhoneNumber {
-
- private static final Pattern PHONE_NUMBER_PATTERN = Pattern.compile("^\\d{8,12}$");
-
- @Column(name = "phone_number", nullable = false)
- private String value;
-
- public StorePhoneNumber(String value) {
- validateNumber(value);
- this.value = value;
- }
-
- private void validateNumber(String number) {
- if (number != null && !PHONE_NUMBER_PATTERN.matcher(number).matches()) {
- throw new BusinessException(BusinessErrorCode.INVALID_STORE_PHONE_NUMBER);
- }
- }
-}
diff --git a/src/main/java/timeeat/enums/InterestArea.java b/src/main/java/timeeat/enums/InterestArea.java
deleted file mode 100644
index 75c334a2..00000000
--- a/src/main/java/timeeat/enums/InterestArea.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package timeeat.enums;
-
-import java.util.Arrays;
-import lombok.Getter;
-import timeeat.exception.BusinessErrorCode;
-import timeeat.exception.BusinessException;
-
-@Getter
-public enum InterestArea {
-
- JONGNO("์ข
๋ก๊ตฌ"),
- JUNG("์ค๊ตฌ"),
- YONGSAN("์ฉ์ฐ๊ตฌ"),
- SEONGDONG("์ฑ๋๊ตฌ"),
- GWANGJIN("๊ด์ง๊ตฌ"),
- DONGDAEMUN("๋๋๋ฌธ๊ตฌ"),
- JUNGRANG("์ค๋๊ตฌ"),
- SEONGBUK("์ฑ๋ถ๊ตฌ"),
- GANGBUK("๊ฐ๋ถ๊ตฌ"),
- DOBONG("๋๋ด๊ตฌ"),
- NOWON("๋
ธ์๊ตฌ"),
- EUNPYEONG("์ํ๊ตฌ"),
- SEODAEMUN("์๋๋ฌธ๊ตฌ"),
- MAPO("๋งํฌ๊ตฌ"),
- YANGCHEON("์์ฒ๊ตฌ"),
- GANGSEO("๊ฐ์๊ตฌ"),
- GURO("๊ตฌ๋ก๊ตฌ"),
- GEUMCHEON("๊ธ์ฒ๊ตฌ"),
- YEONGDEUNGPO("์๋ฑํฌ๊ตฌ"),
- DONGJAK("๋์๊ตฌ"),
- GWANAK("๊ด์
๊ตฌ"),
- SEOCHO("์์ด๊ตฌ"),
- GANGNAM("๊ฐ๋จ๊ตฌ"),
- SONGPA("์กํ๊ตฌ"),
- GANGDONG("๊ฐ๋๊ตฌ");
-
- private final String areaName;
-
- InterestArea(String areaName) {
- this.areaName = areaName;
- }
-
- public static InterestArea from(String value) {
- if (value == null || value.trim().isEmpty()) {
- throw new BusinessException(BusinessErrorCode.INVALID_INTEREST_AREA);
- }
-
- return Arrays.stream(values())
- .filter(area -> area.areaName.equals(value))
- .findFirst()
- .orElseThrow(() -> new BusinessException(BusinessErrorCode.INVALID_INTEREST_AREA));
- }
-
- public static boolean isValid(String value) {
- try {
- from(value);
- return true;
- } catch (BusinessException e) {
- return false;
- }
- }
-}
diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml
index f64d06f6..cfb28b12 100644
--- a/src/main/resources/application-dev.yml
+++ b/src/main/resources/application-dev.yml
@@ -7,6 +7,14 @@ spring:
aws:
region:
static: ap-northeast-2
+ s3:
+ bucket: eatda-storage-dev
+
+ servlet:
+ multipart:
+ max-file-size: 5MB
+ max-request-size: 20MB
+
config:
import: "aws-parameterstore:/dev/"
@@ -43,3 +51,6 @@ oauth:
allowed-origins:
- "http://localhost:3000"
- "https://dev.eatda.net"
+
+kakao:
+ api-key: ${KAKAO_API_KEY}
diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml
index 82b90fea..e9b373d4 100644
--- a/src/main/resources/application-local.yml
+++ b/src/main/resources/application-local.yml
@@ -10,6 +10,18 @@ spring:
password: ${LOCAL_DB_PASSWORD}
driver-class-name: com.mysql.cj.jdbc.Driver
+ cloud:
+ aws:
+ region:
+ static: ap-northeast-2
+ s3:
+ bucket: eatda-storage-local
+
+ servlet:
+ multipart:
+ max-file-size: 5MB
+ max-request-size: 20MB
+
jpa:
hibernate:
ddl-auto: validate
@@ -34,3 +46,6 @@ oauth:
redirect-path: /login/callback
allowed-origins:
- "http://localhost:3000"
+
+kakao:
+ api-key: ${KAKAO_API_KEY}
diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml
index dffbac3d..e15c334d 100644
--- a/src/main/resources/application-prod.yml
+++ b/src/main/resources/application-prod.yml
@@ -7,6 +7,14 @@ spring:
aws:
region:
static: ap-northeast-2
+ s3:
+ bucket: eatda-storage-prod
+
+ servlet:
+ multipart:
+ max-file-size: 5MB
+ max-request-size: 10MB
+
config:
import: "aws-parameterstore:/prod/"
@@ -43,3 +51,6 @@ oauth:
allowed-origins:
- "https://www.eatda.net"
- "https://eatda.net"
+
+kakao:
+ api-key: ${KAKAO_API_KEY}
diff --git a/src/main/resources/db/migration/V1__init.sql b/src/main/resources/db/migration/V1__init.sql
index 5e9a73ca..79d9e669 100644
--- a/src/main/resources/db/migration/V1__init.sql
+++ b/src/main/resources/db/migration/V1__init.sql
@@ -1,50 +1,52 @@
-CREATE TABLE `store`
+CREATE TABLE `member`
(
- `id` BIGINT NOT NULL AUTO_INCREMENT,
- `name` VARCHAR(255) NOT NULL,
- `category` VARCHAR(255) NOT NULL,
- `introduction` TEXT NOT NULL,
- `phone_number` VARCHAR(255) NOT NULL COMMENT '(`-` ์์ด))',
- `interest_area` VARCHAR(50) NOT NULL COMMENT '(์์ธ์ 25๊ฐ ๊ตฌ, Java Enum ์ด๋ฆ์ผ๋ก ์ ์ฅ: ex, GANGNAM)',
- `address` VARCHAR(255) NOT NULL COMMENT '(์ ์ฒด์ฃผ์)',
- `latitude` DOUBLE NOT NULL,
- `longitude` DOUBLE NOT NULL,
- `open_time` TIME NOT NULL,
- `close_time` TIME NOT NULL,
- `image_url` VARCHAR(511) NULL,
+ `id` BIGINT NOT NULL AUTO_INCREMENT,
+ `email` VARCHAR(255) NOT NULL UNIQUE,
+ `social_id` VARCHAR(255) NOT NULL UNIQUE,
+ `nickname` VARCHAR(255) NULL,
+ `phone_number` VARCHAR(255) NULL,
+ `opt_in_marketing` BOOLEAN NULL,
+ `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
);
-CREATE TABLE `member`
+CREATE TABLE `store`
(
- `id` BIGINT NOT NULL AUTO_INCREMENT,
- `social_id` VARCHAR(255) NOT NULL,
- `nickname` VARCHAR(255) NULL,
- `phone_number` VARCHAR(255) NULL COMMENT '(`-` ์์ด))',
- `interest_area` VARCHAR(50) NULL COMMENT '(์์ธ์ 25๊ฐ ๊ตฌ, Java Enum ์ด๋ฆ์ผ๋ก ์ ์ฅ: ex, JONGNO)',
- `opt_in_marketing` BOOLEAN NULL DEFAULT true,
+ `id` BIGINT NOT NULL AUTO_INCREMENT,
+ `kakao_id` VARCHAR(255) NOT NULL UNIQUE,
+ `category` VARCHAR(50) NOT NULL,
+ `phone_number` VARCHAR(255) NOT NULL,
+ `name` VARCHAR(255) NOT NULL,
+ `place_url` VARCHAR(255) NOT NULL,
+ `road_address` VARCHAR(255) NOT NULL,
+ `lot_number_address` VARCHAR(255) NOT NULL,
+ `latitude` DOUBLE NOT NULL,
+ `longitude` DOUBLE NOT NULL,
+ `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
);
-CREATE TABLE `bookmark`
+CREATE TABLE `cheer`
(
- `id` BIGINT NOT NULL AUTO_INCREMENT,
- `member_id` BIGINT NOT NULL,
- `store_id` BIGINT NOT NULL,
+ `id` BIGINT NOT NULL AUTO_INCREMENT,
+ `member_id` BIGINT NOT NULL,
+ `store_id` BIGINT NOT NULL,
+ `description` TEXT NOT NULL,
+ `image_key` VARCHAR(511) NULL,
+ `is_admin` BOOLEAN NOT NULL DEFAULT FALSE,
+ `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
- UNIQUE KEY `UK_member_store` (`member_id`, `store_id`)
+ FOREIGN KEY (`member_id`) REFERENCES `member` (`id`) ON DELETE CASCADE,
+ FOREIGN KEY (`store_id`) REFERENCES `store` (`id`) ON DELETE CASCADE
);
-CREATE TABLE `menu`
+CREATE TABLE `article`
(
- `id` BIGINT NOT NULL AUTO_INCREMENT,
- `store_id` BIGINT NOT NULL,
- `name` VARCHAR(255) NOT NULL,
- `description` VARCHAR(255) NULL,
- `price` INTEGER NOT NULL,
- `discount_price` INTEGER NULL,
- `start_time` DATETIME NULL,
- `end_time` DATETIME NULL,
- `image_url` VARCHAR(511) NULL,
+ `id` BIGINT NOT NULL AUTO_INCREMENT,
+ `title` VARCHAR(255) NOT NULL,
+ `subtitle` VARCHAR(255) NOT NULL,
+ `article_url` VARCHAR(511) NOT NULL,
+ `image_key` VARCHAR(511) NOT NULL,
+ `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
);
diff --git a/src/main/resources/db/migration/V3__add_story_table.sql b/src/main/resources/db/migration/V3__add_story_table.sql
new file mode 100644
index 00000000..32c7e6ad
--- /dev/null
+++ b/src/main/resources/db/migration/V3__add_story_table.sql
@@ -0,0 +1,13 @@
+CREATE TABLE `story`
+(
+ `id` BIGINT NOT NULL AUTO_INCREMENT,
+ `member_id` BIGINT NOT NULL,
+ `store_kakao_id` VARCHAR(255) NOT NULL,
+ `store_name` VARCHAR(255) NOT NULL,
+ `store_address` VARCHAR(255) NOT NULL,
+ `store_category` VARCHAR(50) NOT NULL,
+ `description` TEXT NOT NULL,
+ `image_key` VARCHAR(511) NOT NULL,
+ `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ PRIMARY KEY (`id`)
+);
diff --git a/src/main/resources/db/seed/dev/V2__dev_init_data.sql b/src/main/resources/db/seed/dev/V2__dev_init_data.sql
index 40f48ac2..b9fa13ed 100644
--- a/src/main/resources/db/seed/dev/V2__dev_init_data.sql
+++ b/src/main/resources/db/seed/dev/V2__dev_init_data.sql
@@ -1,112 +1,39 @@
-INSERT INTO member (id, social_id, nickname, phone_number, interest_area, opt_in_marketing)
- VALUES (1, 123456789, '์ด์น๋ก', '01012345678', 'GANGNAM', true),
- (2, 987654321, '์ด์ถฉ์', '01087654321', 'SEOCHO', false),
- (3, 456789123, '์ฅ์๋น', '01045678912', 'SONGPA', true),
- (4, 789123456, '์์คํ', '01078912345', 'MAPO', true),
- (5, 321251287, '์ ๋ฏผ์ ', '01034574568', 'GANGSEO', false),
- (6, 324569987, '๋ฐํฌ์', '01043609998', 'SEODAEMUN', false),
- (7, 323487985, 'ํ์์', '01065083298', 'SEONGDONG', false) AS new
-ON DUPLICATE KEY UPDATE social_id = new.social_id,
- nickname = new.nickname,
- phone_number = new.phone_number,
- interest_area = new.interest_area,
- opt_in_marketing = new.opt_in_marketing;
+INSERT INTO member (id, social_id, email, nickname, phone_number, opt_in_marketing)
+VALUES (1, 123456789, 'default1@kakao.com', '์ด์น๋ก', '01012345678', true),
+ (2, 987654321, 'default2@kakao.com', '์ด์ถฉ์', '01087654321', false),
+ (3, 456789123, 'default3@kakao.com', '์ฅ์๋น', '01045678912', true),
+ (4, 789123456, 'default4@kakao.com', '์์คํ', '01078912345', true),
+ (5, 321251287, 'default5@kakao.com', '์ ๋ฏผ์ ', '01034574568', false),
+ (6, 324569987, 'default6@kakao.com', '๋ฐํฌ์', '01043609998', false),
+ (7, 323487985, 'default7@kakao.com', 'ํ์์', '01065083298', false);
-INSERT INTO store (id, name, category, latitude, longitude, address, phone_number, image_url, open_time, close_time,
- introduction, interest_area)
- VALUES (1, '๋ง์๋ ํ์๋น', 'ํ์', 37.5665, 126.9780, '์์ธํน๋ณ์ ๊ฐ๋จ๊ตฌ ํ
ํค๋๋ก 123', '0212345678', 'https://example.com/store1.jpg',
- '09:00:00', '22:00:00', '์ ํต ํ์์ ๋ง์ ๋๋ ์ ์๋ ๊ณณ์
๋๋ค.', 'GANGNAM'),
- (2, '์ ์ ํ ์ค์๋น', '์ค์', 37.5645, 126.9770, '์์ธํน๋ณ์ ์์ด๊ตฌ ์์ด๋๋ก 456', '0287654321', 'https://example.com/store2.jpg',
- '11:00:00', '21:00:00', '์ ํต ์ค๊ตญ ์๋ฆฌ๋ฅผ ๋ง๋ณผ ์ ์์ต๋๋ค.', 'SEOCHO'),
- (3, '์ผ๋ณธ ์ ํต ์ค์', '์ผ์', 37.5685, 126.9790, '์์ธํน๋ณ์ ์กํ๊ตฌ ์ฌ๋ฆผํฝ๋ก 789', '0245678912',
- 'https://example.com/store3.jpg',
- '11:30:00', '22:30:00', '์ ์ ํ ํ์ ์ ํต ์ค์๋ฅผ ์ฆ๊ธฐ์ธ์.', 'SONGPA'),
- (4, '์ดํ๋ฆฌ์ ํ์คํ', '์์', 37.5625, 126.9760, '์์ธํน๋ณ์ ๋งํฌ๊ตฌ ํ๋๋ก 321', '0278912345', 'https://example.com/store4.jpg',
- '12:00:00', '23:00:00', '์ ํต ์ดํ๋ฆฌ์ ํ์คํ์ ํผ์๋ฅผ ๋ง๋๋ณด์ธ์.', 'MAPO'),
- (5, '์ปคํผํฅ ๊ฐ๋ํ ์นดํ', '์นดํ', 37.5705, 126.9800, '์์ธํน๋ณ์ ์ข
๋ก๊ตฌ ์ข
๋ก 654', '0232165498',
- 'https://example.com/store5.jpg',
- '07:00:00', '23:59:59', '์๋ํ ๋ถ์๊ธฐ์์ ์ปคํผ๋ฅผ ์ฆ๊ธฐ์ธ์.', 'JONGNO'),
- (6, '๋ฌ์ฝคํ ๋์ ํธ์ต', '๋์ ํธ', 37.5640, 126.9775, '์์ธํน๋ณ์ ๊ฐ๋จ๊ตฌ ๊ฐ๋จ๋๋ก 987', '0211111111',
- 'https://example.com/store6.jpg',
- '10:00:00', '21:00:00', '๋ค์ํ ๋์ ํธ์ ์ผ์ดํฌ๋ฅผ ๋ง๋๋ณด์ธ์.', 'GANGNAM'),
- (7, '๋ถ์๊ธฐ ์ข์ ์ ์ง', '์ ์ง', 37.5660, 126.9785, '์์ธํน๋ณ์ ์์ด๊ตฌ ์์ด๋๋ก 147', '0222222222',
- 'https://example.com/store7.jpg',
- '18:00:00', '02:00:00', '๋ถ์๊ธฐ ์ข์ ์ ์ง์์ ์ฆ๊ฑฐ์ด ์๊ฐ์ ๋ณด๋ด์ธ์.', 'SEOCHO'),
- (8, '๋น ๋ฅธ ํจ์คํธํธ๋', 'ํจ์คํธํธ๋', 37.5680, 126.9795, '์์ธํน๋ณ์ ์กํ๊ตฌ ์ฌ๋ฆผํฝ๋ก 258', '0233333333',
- 'https://example.com/store8.jpg',
- '06:00:00', '23:59:59', '๋น ๋ฅด๊ณ ๋ง์๋ ํจ์คํธํธ๋๋ฅผ ์ฆ๊ธฐ์ธ์.', 'SONGPA'),
- (9, 'ํธ๋ฆฌํ ํธ์์ ', 'ํธ์์ ', 37.5620, 126.9765, '์์ธํน๋ณ์ ๋งํฌ๊ตฌ ํ๋๋ก 369', '0244444444', 'https://example.com/store9.jpg',
- '00:00:00', '23:59:59', '24์๊ฐ ํธ๋ฆฌํ ํธ์์ ์
๋๋ค.', 'MAPO'),
- (10, 'ํน๋ณํ ๊ธฐํ๋งค์ฅ', '๊ธฐํ', 37.5700, 126.9805, '์์ธํน๋ณ์ ์ข
๋ก๊ตฌ ์ข
๋ก 741', '0255555555',
- 'https://example.com/store10.jpg',
- '09:00:00', '18:00:00', 'ํน๋ณํ ์ํ๋ค์ ๋ง๋๋ณด์ธ์.', 'JONGNO') AS new
-ON DUPLICATE KEY UPDATE name = new.name,
- category = new.category,
- latitude = new.latitude,
- longitude = new.longitude,
- address = new.address,
- phone_number = new.phone_number,
- image_url = new.image_url,
- open_time = new.open_time,
- close_time = new.close_time,
- introduction = new.introduction,
- interest_area = new.interest_area;
+INSERT INTO store (id, kakao_id, category, phone_number, name, place_url, road_address, lot_number_address, latitude,
+ longitude)
+VALUES (1, '99999999999', 'KOREAN', '01012345678', '๋ง์๋ ํ์์ง', 'https://place.map.kakao.com/17163273',
+ '์์ธ์ ๊ฐ๋จ๊ตฌ ์ญ์ผ๋ 123-45', '์์ธ์ ๊ฐ๋จ๊ตฌ ์ญ์ผ๋ 123-45', 37.503708148482524, 127.05300772497776),
+ (2, '99999999998', 'WESTERN', '01087654321', '์๋ฆ๋ค์ด ์์์ง', 'https://place.map.kakao.com/17163273',
+ '์์ธ์ ๊ฐ๋จ๊ตฌ ์ญ์ผ๋ 67-89', '์์ธ์ ๊ฐ๋จ๊ตฌ ์ญ์ผ๋ 67-89', 37.4979, 127.0276),
+ (3, '99999999997', 'CHINESE', '01045678912', '์ ํต ์ค์๋น', 'https://place.map.kakao.com/17163273',
+ '์์ธ์ ๊ฐ๋จ๊ตฌ ์ญ์ผ๋ 101-112', '์์ธ์ ๊ฐ๋จ๊ตฌ ์ญ์ผ๋ 101-112', 37.56259825108099, 126.97715943361476),
+ (4, '99999999996', 'WESTERN', '01078912345', '๊ณ ๊ธ ์์ ๋ ์คํ ๋', 'https://place.map.kakao.com/17163273', '',
+ '์์ธ์ ๊ฐ๋จ๊ตฌ ์ญ์ผ๋ 131-415', 37.4979, 127.0276),
+ (5, '99999999995', 'ETC', '01034574568', '๋ฌ์ฝคํ ๋์ ํธ ์นดํ', 'https://place.map.kakao.com/17163273',
+ '์์ธ์ ๊ฐ๋จ๊ตฌ ์ญ์ผ๋ 161-718', '์์ธ์ ๊ฐ๋จ๊ตฌ ์ญ์ผ๋ 161-718', 37.49491300989233, 127.03150463098274),
+ (6, '99999999994', 'ETC', '01043609998', '์๋ํ ์ปคํผ์', 'https://place.map.kakao.com/17163273',
+ '์์ธ์ ๊ฐ๋จ๊ตฌ ์ญ์ผ๋ 192-021', '์์ธ์ ๊ฐ๋จ๊ตฌ ์ญ์ผ๋ 192-021', 37.5298343127044, 126.919484339847),
+ (7, '99999999993', 'ETC', '01065083298', '๋น ๋ฅธ ํจ์คํธํธ๋์ ', 'https://place.map.kakao.com/17163273', '',
+ '์์ธ์ ๊ฐ๋จ๊ตฌ ์ญ์ผ๋ 222-324', 37.5036675804016, 127.05305858911);
-INSERT INTO menu (id, store_id, name, description, price, discount_price, start_time, end_time, image_url)
- VALUES (1, 1, '๋ถ๊ณ ๊ธฐ', '๋ง์๋ ๋ถ๊ณ ๊ธฐ', 15000, 12000, '2025-06-28 14:00:00', '2025-06-28 16:00:00',
- 'https://example.com/menu1.jpg'),
- (2, 1, '๋น๋น๋ฐฅ', '์ ์ ํ ์ฑ์๊ฐ ๋ค์ด๊ฐ ๋น๋น๋ฐฅ', 12000, NULL, NULL, NULL, 'https://example.com/menu2.jpg'),
- (3, 2, '์ง์ฅ๋ฉด', '์ ํต ์ค๊ตญ ์ง์ฅ๋ฉด', 8000, 6000, '2025-06-28 12:00:00', '2025-06-28 14:00:00',
- 'https://example.com/menu3.jpg'),
- (4, 2, 'ํ์์ก', '๋ฐ์ญํ ํ์์ก', 18000, NULL, NULL, NULL, 'https://example.com/menu4.jpg'),
- (5, 3, '์ด๋ฐฅ ์ธํธ', '์ ์ ํ ํ๋ก ๋ง๋ ์ด๋ฐฅ ์ธํธ', 25000, 20000, '2025-06-28 15:00:00', '2025-06-28 17:00:00',
- 'https://example.com/menu5.jpg'),
- (6, 3, '์ฐ๋', '๋ฐ๋ปํ ์ฐ๋', 12000, NULL, NULL, NULL, 'https://example.com/menu6.jpg'),
- (7, 4, '๊น๋ฅด๋ณด๋๋ผ', '์ ํต ์ดํ๋ฆฌ์ ๊น๋ฅด๋ณด๋๋ผ', 18000, 15000, '2025-06-28 13:00:00', '2025-06-28 15:00:00',
- 'https://example.com/menu7.jpg'),
- (8, 4, '๋ง๋ฅด๊ฒ๋ฆฌํ ํผ์', '์ ์ ํ ๋ชจ์ง๋ ๋ผ ์น์ฆ ํผ์', 22000, NULL, NULL, NULL, 'https://example.com/menu8.jpg'),
- (9, 5, '์๋ฉ๋ฆฌ์นด๋
ธ', '๊น์ ๋ง์ ์๋ฉ๋ฆฌ์นด๋
ธ', 4500, 3500, '2025-06-28 10:00:00', '2025-06-28 12:00:00',
- 'https://example.com/menu9.jpg'),
- (10, 5, '์นดํ๋ผ๋ผ', '๋ถ๋๋ฌ์ด ์นดํ๋ผ๋ผ', 5500, NULL, NULL, NULL, 'https://example.com/menu10.jpg'),
- (11, 6, 'ํฐ๋ผ๋ฏธ์', '์งํ ์ปคํผํฅ์ ํฐ๋ผ๋ฏธ์', 8000, 6000, '2025-06-28 14:00:00', '2025-06-28 16:00:00',
- 'https://example.com/menu11.jpg'),
- (12, 6, '์ด์ฝ์ผ์ดํฌ', '๋ฌ์ฝคํ ์ด์ฝ์ผ์ดํฌ', 7000, NULL, NULL, NULL, 'https://example.com/menu12.jpg'),
- (13, 7, '์์ฃผ', '๊น๋ํ ์์ฃผ', 4000, 3000, '2025-06-28 20:00:00', '2025-06-28 22:00:00',
- 'https://example.com/menu13.jpg'),
- (14, 7, '๋งฅ์ฃผ', '์์ํ ๋งฅ์ฃผ', 6000, NULL, NULL, NULL, 'https://example.com/menu14.jpg'),
- (15, 8, 'ํ๋ฒ๊ฑฐ ์ธํธ', '๋ง์๋ ํ๋ฒ๊ฑฐ ์ธํธ', 12000, 9000, '2025-06-28 11:00:00', '2025-06-28 13:00:00',
- 'https://example.com/menu15.jpg'),
- (16, 8, '์นํจ', '๋ฐ์ญํ ์นํจ', 18000, NULL, NULL, NULL, 'https://example.com/menu16.jpg'),
- (17, 9, '์ผ๊ฐ๊น๋ฐฅ', '๋ง์๋ ์ผ๊ฐ๊น๋ฐฅ', 1500, 1200, '2025-06-28 22:00:00', '2025-06-29 06:00:00',
- 'https://example.com/menu17.jpg'),
- (18, 9, '์ปคํผ', 'ํธ์์ ์ปคํผ', 1500, NULL, NULL, NULL, 'https://example.com/menu18.jpg'),
- (19, 10, 'ํน๋ณ์ํA', 'ํน๋ณํ ์ํ A', 50000, 40000, '2025-06-28 10:00:00', '2025-06-28 12:00:00',
- 'https://example.com/menu19.jpg'),
- (20, 10, 'ํน๋ณ์ํB', 'ํน๋ณํ ์ํ B', 30000, NULL, NULL, NULL, 'https://example.com/menu20.jpg') AS new
-ON DUPLICATE KEY UPDATE store_id = new.store_id,
- name = new.name,
- description = new.description,
- price = new.price,
- discount_price = new.discount_price,
- start_time = new.start_time,
- end_time = new.end_time,
- image_url = new.image_url;
+INSERT INTO cheer (id, member_id, store_id, description, image_key, is_admin)
+VALUES (1, 1, 1, '์ ๋ง ๋ง์์ด์! ๊ฐ์ถํฉ๋๋ค!', 'default.jpg', true),
+ (2, 2, 2, '์๋น์ค๊ฐ ํ๋ฅญํด์!', 'default.jpg', true),
+ (3, 3, 3, '์ฌ๊ธฐ ์์์ด ์ ๋ง ๋ง์์ด์!', 'default.jpg', true),
+ (4, 4, 4, '๋ถ์๊ธฐ๊ฐ ๋๋ฌด ์ข์์!', 'default.jpg', true),
+ (5, 5, 5, '๋์ ํธ๊ฐ ์ ๋ง ๋ง์์ด์!', 'default.jpg', true),
+ (6, 6, 6, '์ปคํผ๊ฐ ์ ๋ง ๋ง์์ด์!', 'default.jpg', false),
+ (7, 7, 7, 'ํจ์คํธํธ๋๊ฐ ๋น ๋ฅด๊ณ ๋ง์์ด์!', 'default.jpg', false);
-INSERT INTO bookmark (id, member_id, store_id)
- VALUES (1, 1, 1),
- (2, 1, 3),
- (3, 2, 2),
- (4, 2, 5),
- (5, 3, 4),
- (6, 3, 6),
- (7, 4, 7),
- (8, 4, 9),
- (9, 5, 8),
- (10, 5, 10),
- (11, 1, 5),
- (12, 2, 1),
- (13, 3, 7),
- (14, 4, 2),
- (15, 5, 4) AS new
-ON DUPLICATE KEY UPDATE member_id = new.member_id,
- store_id = new.store_id;
+INSERT INTO article (id, title, subtitle, article_url, image_key)
+VALUES (1, '์ฒซ ๋ฒ์งธ ๊ธฐ์ฌ', '์๋ธํ์ดํ 1', 'https://example.com/article1', 'default.jpg'),
+ (2, '๋ ๋ฒ์งธ ๊ธฐ์ฌ', '์๋ธํ์ดํ 2', 'https://example.com/article2', 'default.jpg'),
+ (3, '์ธ ๋ฒ์งธ ๊ธฐ์ฌ', '์๋ธํ์ดํ 3', 'https://example.com/article3', 'default.jpg');
diff --git a/src/main/resources/db/seed/local/R__local_init_data.sql b/src/main/resources/db/seed/local/R__local_init_data.sql
deleted file mode 100644
index f96abd5e..00000000
--- a/src/main/resources/db/seed/local/R__local_init_data.sql
+++ /dev/null
@@ -1,56 +0,0 @@
-INSERT INTO member (id, social_id, nickname, phone_number, interest_area, opt_in_marketing)
- VALUES (1, 123456789, '๋ก์ปฌ๊ฐ๋ฐ์', '01012345678', 'GANGNAM', true),
- (2, 987654321, '๋จ์ํ
์คํฐ', '01087654321', 'SEOCHO', false) AS new
-ON DUPLICATE KEY UPDATE social_id = new.social_id,
- nickname = new.nickname,
- phone_number = new.phone_number,
- interest_area = new.interest_area,
- opt_in_marketing = new.opt_in_marketing;
-
-
-INSERT INTO store (id, name, category, latitude, longitude, address, phone_number, image_url, open_time, close_time,
- introduction, interest_area)
- VALUES (1, '๋ก์ปฌ ํ์๋น', 'ํ์', 37.5665, 126.9780, '์์ธํน๋ณ์ ๊ฐ๋จ๊ตฌ ํ
ํค๋๋ก 123', '0212345678', 'https://example.com/store1.jpg',
- '09:00:00', '22:00:00', '๋ก์ปฌ ํ
์คํธ์ฉ ํ์๋น์
๋๋ค.', 'GANGNAM'),
- (2, '๋ก์ปฌ ์ค์๋น', '์ค์', 37.5645, 126.9770, '์์ธํน๋ณ์ ์์ด๊ตฌ ์์ด๋๋ก 456', '0287654321', 'https://example.com/store2.jpg',
- '11:00:00', '21:00:00', '๋ก์ปฌ ํ
์คํธ์ฉ ์ค์๋น์
๋๋ค.', 'SEOCHO'),
- (3, '๋ก์ปฌ ์ผ์๋น', '์ผ์', 37.5685, 126.9790, '์์ธํน๋ณ์ ์กํ๊ตฌ ์ฌ๋ฆผํฝ๋ก 789', '0245678912', 'https://example.com/store3.jpg',
- '11:30:00', '22:30:00', '๋ก์ปฌ ํ
์คํธ์ฉ ์ผ์๋น์
๋๋ค.', 'SONGPA') AS new
-ON DUPLICATE KEY UPDATE name = new.name,
- category = new.category,
- latitude = new.latitude,
- longitude = new.longitude,
- address = new.address,
- phone_number = new.phone_number,
- image_url = new.image_url,
- open_time = new.open_time,
- close_time = new.close_time,
- introduction = new.introduction,
- interest_area = new.interest_area;
-
-
-INSERT INTO menu (id, store_id, name, description, price, discount_price, start_time, end_time,
- image_url)
- VALUES (1, 1, '๋ถ๊ณ ๊ธฐ', '๋ง์๋ ๋ถ๊ณ ๊ธฐ', 15000, 12000, '2025-06-28 14:00:00', '2025-06-28 16:00:00',
- 'https://example.com/menu1.jpg'),
- (2, 1, '๋น๋น๋ฐฅ', '์ ์ ํ ์ฑ์๊ฐ ๋ค์ด๊ฐ ๋น๋น๋ฐฅ', 12000, NULL, NULL, NULL, 'https://example.com/menu2.jpg'),
- (3, 2, '์ง์ฅ๋ฉด', '์ ํต ์ค๊ตญ ์ง์ฅ๋ฉด', 8000, 6000, '2025-06-28 12:00:00', '2025-06-28 14:00:00',
- 'https://example.com/menu3.jpg'),
- (4, 3, '์ด๋ฐฅ ์ธํธ', '์ ์ ํ ํ๋ก ๋ง๋ ์ด๋ฐฅ ์ธํธ', 25000, 20000, '2025-06-28 15:00:00', '2025-06-28 17:00:00',
- 'https://example.com/menu5.jpg') AS new
-ON DUPLICATE KEY UPDATE store_id = new.store_id,
- name = new.name,
- description = new.description,
- price = new.price,
- discount_price = new.discount_price,
- start_time = new.start_time,
- end_time = new.end_time,
- image_url = new.image_url;
-
-
-INSERT INTO bookmark (id, member_id, store_id)
- VALUES (1, 1, 1),
- (2, 1, 3),
- (3, 2, 2) AS new
-ON DUPLICATE KEY UPDATE member_id = new.member_id,
- store_id = new.store_id;
diff --git a/src/main/resources/db/seed/local/V2__local_init_data.sql b/src/main/resources/db/seed/local/V2__local_init_data.sql
new file mode 100644
index 00000000..b9fa13ed
--- /dev/null
+++ b/src/main/resources/db/seed/local/V2__local_init_data.sql
@@ -0,0 +1,39 @@
+INSERT INTO member (id, social_id, email, nickname, phone_number, opt_in_marketing)
+VALUES (1, 123456789, 'default1@kakao.com', '์ด์น๋ก', '01012345678', true),
+ (2, 987654321, 'default2@kakao.com', '์ด์ถฉ์', '01087654321', false),
+ (3, 456789123, 'default3@kakao.com', '์ฅ์๋น', '01045678912', true),
+ (4, 789123456, 'default4@kakao.com', '์์คํ', '01078912345', true),
+ (5, 321251287, 'default5@kakao.com', '์ ๋ฏผ์ ', '01034574568', false),
+ (6, 324569987, 'default6@kakao.com', '๋ฐํฌ์', '01043609998', false),
+ (7, 323487985, 'default7@kakao.com', 'ํ์์', '01065083298', false);
+
+INSERT INTO store (id, kakao_id, category, phone_number, name, place_url, road_address, lot_number_address, latitude,
+ longitude)
+VALUES (1, '99999999999', 'KOREAN', '01012345678', '๋ง์๋ ํ์์ง', 'https://place.map.kakao.com/17163273',
+ '์์ธ์ ๊ฐ๋จ๊ตฌ ์ญ์ผ๋ 123-45', '์์ธ์ ๊ฐ๋จ๊ตฌ ์ญ์ผ๋ 123-45', 37.503708148482524, 127.05300772497776),
+ (2, '99999999998', 'WESTERN', '01087654321', '์๋ฆ๋ค์ด ์์์ง', 'https://place.map.kakao.com/17163273',
+ '์์ธ์ ๊ฐ๋จ๊ตฌ ์ญ์ผ๋ 67-89', '์์ธ์ ๊ฐ๋จ๊ตฌ ์ญ์ผ๋ 67-89', 37.4979, 127.0276),
+ (3, '99999999997', 'CHINESE', '01045678912', '์ ํต ์ค์๋น', 'https://place.map.kakao.com/17163273',
+ '์์ธ์ ๊ฐ๋จ๊ตฌ ์ญ์ผ๋ 101-112', '์์ธ์ ๊ฐ๋จ๊ตฌ ์ญ์ผ๋ 101-112', 37.56259825108099, 126.97715943361476),
+ (4, '99999999996', 'WESTERN', '01078912345', '๊ณ ๊ธ ์์ ๋ ์คํ ๋', 'https://place.map.kakao.com/17163273', '',
+ '์์ธ์ ๊ฐ๋จ๊ตฌ ์ญ์ผ๋ 131-415', 37.4979, 127.0276),
+ (5, '99999999995', 'ETC', '01034574568', '๋ฌ์ฝคํ ๋์ ํธ ์นดํ', 'https://place.map.kakao.com/17163273',
+ '์์ธ์ ๊ฐ๋จ๊ตฌ ์ญ์ผ๋ 161-718', '์์ธ์ ๊ฐ๋จ๊ตฌ ์ญ์ผ๋ 161-718', 37.49491300989233, 127.03150463098274),
+ (6, '99999999994', 'ETC', '01043609998', '์๋ํ ์ปคํผ์', 'https://place.map.kakao.com/17163273',
+ '์์ธ์ ๊ฐ๋จ๊ตฌ ์ญ์ผ๋ 192-021', '์์ธ์ ๊ฐ๋จ๊ตฌ ์ญ์ผ๋ 192-021', 37.5298343127044, 126.919484339847),
+ (7, '99999999993', 'ETC', '01065083298', '๋น ๋ฅธ ํจ์คํธํธ๋์ ', 'https://place.map.kakao.com/17163273', '',
+ '์์ธ์ ๊ฐ๋จ๊ตฌ ์ญ์ผ๋ 222-324', 37.5036675804016, 127.05305858911);
+
+INSERT INTO cheer (id, member_id, store_id, description, image_key, is_admin)
+VALUES (1, 1, 1, '์ ๋ง ๋ง์์ด์! ๊ฐ์ถํฉ๋๋ค!', 'default.jpg', true),
+ (2, 2, 2, '์๋น์ค๊ฐ ํ๋ฅญํด์!', 'default.jpg', true),
+ (3, 3, 3, '์ฌ๊ธฐ ์์์ด ์ ๋ง ๋ง์์ด์!', 'default.jpg', true),
+ (4, 4, 4, '๋ถ์๊ธฐ๊ฐ ๋๋ฌด ์ข์์!', 'default.jpg', true),
+ (5, 5, 5, '๋์ ํธ๊ฐ ์ ๋ง ๋ง์์ด์!', 'default.jpg', true),
+ (6, 6, 6, '์ปคํผ๊ฐ ์ ๋ง ๋ง์์ด์!', 'default.jpg', false),
+ (7, 7, 7, 'ํจ์คํธํธ๋๊ฐ ๋น ๋ฅด๊ณ ๋ง์์ด์!', 'default.jpg', false);
+
+INSERT INTO article (id, title, subtitle, article_url, image_key)
+VALUES (1, '์ฒซ ๋ฒ์งธ ๊ธฐ์ฌ', '์๋ธํ์ดํ 1', 'https://example.com/article1', 'default.jpg'),
+ (2, '๋ ๋ฒ์งธ ๊ธฐ์ฌ', '์๋ธํ์ดํ 2', 'https://example.com/article2', 'default.jpg'),
+ (3, '์ธ ๋ฒ์งธ ๊ธฐ์ฌ', '์๋ธํ์ดํ 3', 'https://example.com/article3', 'default.jpg');
diff --git a/src/test/java/timeeat/DatabaseCleaner.java b/src/test/java/eatda/DatabaseCleaner.java
similarity index 99%
rename from src/test/java/timeeat/DatabaseCleaner.java
rename to src/test/java/eatda/DatabaseCleaner.java
index d44f1455..d4d321cd 100644
--- a/src/test/java/timeeat/DatabaseCleaner.java
+++ b/src/test/java/eatda/DatabaseCleaner.java
@@ -1,12 +1,12 @@
-package timeeat;
+package eatda;
+import jakarta.persistence.EntityManager;
import java.util.List;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.transaction.support.TransactionTemplate;
-import jakarta.persistence.EntityManager;
public class DatabaseCleaner implements BeforeEachCallback {
diff --git a/src/test/java/timeeat/TimeEatApplicationTests.java b/src/test/java/eatda/EatdaApplicationTests.java
similarity index 76%
rename from src/test/java/timeeat/TimeEatApplicationTests.java
rename to src/test/java/eatda/EatdaApplicationTests.java
index 57e257ae..6af92919 100644
--- a/src/test/java/timeeat/TimeEatApplicationTests.java
+++ b/src/test/java/eatda/EatdaApplicationTests.java
@@ -1,10 +1,10 @@
-package timeeat;
+package eatda;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
-class TimeEatApplicationTests {
+class EatdaApplicationTests {
@Test
void contextLoads() {
diff --git a/src/test/java/eatda/client/map/KakaoPropertiesTest.java b/src/test/java/eatda/client/map/KakaoPropertiesTest.java
new file mode 100644
index 00000000..54b4f5de
--- /dev/null
+++ b/src/test/java/eatda/client/map/KakaoPropertiesTest.java
@@ -0,0 +1,35 @@
+package eatda.client.map;
+
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.NullAndEmptySource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+class KakaoPropertiesTest {
+
+ @Nested
+ class ValidateApiKey {
+
+ @ValueSource(strings = {"\n", " "})
+ @ParameterizedTest
+ @NullAndEmptySource
+ void null_๋๋_๋น_๋ฌธ์์ด์ธ_API_ํค๋_์์ธ๋ฅผ_๋์ง๋ค(String apiKey) {
+ assertThatThrownBy(() -> new KakaoProperties(apiKey))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("API key must not be null or blank");
+
+ }
+
+ @Test
+ void ์ ํจํ_API_ํค๋_์ ์์ ์ผ๋ก_์์ฑ๋๋ค() {
+ assertThatCode(() -> new KakaoProperties("z116bf75dgh76c253hg7c4b123ab3609"))
+ .doesNotThrowAnyException();
+ }
+ }
+
+}
diff --git a/src/test/java/eatda/client/map/MapClientTest.java b/src/test/java/eatda/client/map/MapClientTest.java
new file mode 100644
index 00000000..c9294bda
--- /dev/null
+++ b/src/test/java/eatda/client/map/MapClientTest.java
@@ -0,0 +1,113 @@
+package eatda.client.map;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.hamcrest.Matchers.startsWith;
+import static org.junit.jupiter.api.Assertions.assertAll;
+import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
+import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
+
+import java.util.List;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.client.RestClientTest;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.client.MockRestServiceServer;
+import org.springframework.test.web.client.response.MockRestResponseCreators;
+
+@RestClientTest(MapClient.class)
+class MapClientTest {
+
+ @Autowired
+ private MockRestServiceServer mockServer;
+
+ @Autowired
+ private MapClient mapClient;
+
+ private void setMockServer(HttpMethod method, String url, String responseBody) {
+ mockServer.expect(requestTo(startsWith(url)))
+ .andExpect(method(method))
+ .andRespond(MockRestResponseCreators.withSuccess(responseBody, MediaType.APPLICATION_JSON));
+ }
+
+ @Nested
+ class SearchShops {
+
+ @Test
+ void ๊ฐ๊ฒ_๊ฒ์์_ํ _์_์๋ค() {
+ String url = "https://dapi.kakao.com/v2/local/search/keyword.json";
+ String responseBody = """
+ {
+ "documents": [
+ {
+ "address_name": "์์ธ ๊ฐ๋จ๊ตฌ ๋์น๋ 896-33",
+ "category_group_code": "FD6",
+ "category_group_name": "์์์ ",
+ "category_name": "์์์ > ํ์ > ๊ตญ๋ฐฅ",
+ "distance": "",
+ "id": "17163273",
+ "phone": "02-555-9603",
+ "place_name": "๋๋ฏผ๋ฐฑ์์๋ ๋ณธ์ ",
+ "place_url": "http://place.map.kakao.com/17163273",
+ "road_address_name": "์์ธ ๊ฐ๋จ๊ตฌ ์ ๋ฆ๋ก86๊ธธ 40-4",
+ "x": "127.05300772497776",
+ "y": "37.503708148482524"
+ }
+ ],
+ "meta": {
+ "is_end": true,
+ "pageable_count": 1,
+ "same_name": {
+ "keyword": "๋๋ฏผ๋ฐฑ์์๋",
+ "region": [],
+ "selected_region": ""
+ },
+ "total_count": 1
+ }
+ }""";
+ setMockServer(HttpMethod.GET, url, responseBody);
+
+ List results = mapClient.searchShops("๋๋ฏผ๋ฐฑ์์๋");
+
+ StoreSearchResult result = results.getFirst();
+ assertAll(
+ () -> assertThat(results).hasSize(1),
+ () -> assertThat(result.kakaoId()).isEqualTo("17163273"),
+ () -> assertThat(result.categoryGroupCode()).isEqualTo("FD6"),
+ () -> assertThat(result.categoryName()).isEqualTo("์์์ > ํ์ > ๊ตญ๋ฐฅ"),
+ () -> assertThat(result.phoneNumber()).isEqualTo("02-555-9603"),
+ () -> assertThat(result.name()).isEqualTo("๋๋ฏผ๋ฐฑ์์๋ ๋ณธ์ "),
+ () -> assertThat(result.placeUrl()).isEqualTo("http://place.map.kakao.com/17163273"),
+ () -> assertThat(result.lotNumberAddress()).isEqualTo("์์ธ ๊ฐ๋จ๊ตฌ ๋์น๋ 896-33"),
+ () -> assertThat(result.roadAddress()).isEqualTo("์์ธ ๊ฐ๋จ๊ตฌ ์ ๋ฆ๋ก86๊ธธ 40-4"),
+ () -> assertThat(result.latitude()).isEqualTo(37.503708148482524),
+ () -> assertThat(result.longitude()).isEqualTo(127.05300772497776)
+ );
+ }
+
+ @Test
+ void ๊ฒ์_๊ฒฐ๊ณผ๊ฐ_์์_๊ฒฝ์ฐ_๋น_๋ฆฌ์คํธ๋ฅผ_๋ฐํํ๋ค() {
+ String url = "https://dapi.kakao.com/v2/local/search/keyword.json";
+ String responseBody = """
+ {
+ "documents": [],
+ "meta": {
+ "is_end": true,
+ "pageable_count": 0,
+ "same_name": {
+ "keyword": "์กด์ฌํ์ง ์๋ ๊ฐ๊ฒ",
+ "region": [],
+ "selected_region": ""
+ },
+ "total_count": 0
+ }
+ }""";
+ setMockServer(HttpMethod.GET, url, responseBody);
+
+ List results = mapClient.searchShops("์กด์ฌํ์ง ์๋ ๊ฐ๊ฒ");
+
+ assertThat(results).isEmpty();
+ }
+ }
+}
diff --git a/src/test/java/timeeat/client/oauth/OauthClientTest.java b/src/test/java/eatda/client/oauth/OauthClientTest.java
similarity index 85%
rename from src/test/java/timeeat/client/oauth/OauthClientTest.java
rename to src/test/java/eatda/client/oauth/OauthClientTest.java
index 2543f0a2..9530db2f 100644
--- a/src/test/java/timeeat/client/oauth/OauthClientTest.java
+++ b/src/test/java/eatda/client/oauth/OauthClientTest.java
@@ -1,4 +1,4 @@
-package timeeat.client.oauth;
+package eatda.client.oauth;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertAll;
@@ -6,6 +6,8 @@
import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
+import eatda.exception.BusinessErrorCode;
+import eatda.exception.BusinessException;
import java.net.URI;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
@@ -15,8 +17,6 @@
import org.springframework.http.MediaType;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.test.web.client.response.MockRestResponseCreators;
-import timeeat.exception.BusinessErrorCode;
-import timeeat.exception.BusinessException;
@RestClientTest(OauthClient.class)
class OauthClientTest {
@@ -30,7 +30,7 @@ class OauthClientTest {
@Autowired
private OauthProperties properties;
- public void setMockServer(HttpMethod method, String uri, String responseBody) {
+ private void setMockServer(HttpMethod method, String uri, String responseBody) {
mockServer.expect(requestTo(uri))
.andExpect(method(method))
.andRespond(MockRestResponseCreators.withSuccess(responseBody, MediaType.APPLICATION_JSON));
@@ -117,15 +117,22 @@ class RequestMemberInformation {
void Oauth_ํ์์ ๋ณด๋ฅผ_์์ฒญํ _์_์๋ค() {
setMockServer(HttpMethod.GET, "https://kapi.kakao.com/v2/user/me", """
{
- "id":123456789,
- "connected_at": "2022-04-11T01:45:28Z",
+ "id": 123456789,
+ "connected_at": "2025-07-08T13:31:28Z",
+ "properties": {
+ "nickname": "์ด์ถฉ์"
+ },
"kakao_account": {
"profile_nickname_needs_agreement": false,
- "profile_image_needs_agreement": false,
"profile": {
- "nickname": "ํ๊ธธ๋",
+ "nickname": "์ด์ถฉ์",
"is_default_nickname": false
- }
+ },
+ "has_email": true,
+ "email_needs_agreement": false,
+ "is_email_valid": true,
+ "is_email_verified": true,
+ "email": "cnddkscndgus@naver.com"
}
}""");
OauthToken token = new OauthToken("test-access-token");
@@ -134,7 +141,8 @@ class RequestMemberInformation {
assertAll(
() -> assertThat(memberInfo.socialId()).isEqualTo(123456789L),
- () -> assertThat(memberInfo.nickname()).isEqualTo("ํ๊ธธ๋")
+ () -> assertThat(memberInfo.email()).isEqualTo("cnddkscndgus@naver.com"),
+ () -> assertThat(memberInfo.nickname()).isEqualTo("์ด์ถฉ์")
);
}
}
diff --git a/src/test/java/timeeat/client/oauth/OauthPropertiesTest.java b/src/test/java/eatda/client/oauth/OauthPropertiesTest.java
similarity index 97%
rename from src/test/java/timeeat/client/oauth/OauthPropertiesTest.java
rename to src/test/java/eatda/client/oauth/OauthPropertiesTest.java
index 7b317841..568d07c0 100644
--- a/src/test/java/timeeat/client/oauth/OauthPropertiesTest.java
+++ b/src/test/java/eatda/client/oauth/OauthPropertiesTest.java
@@ -1,14 +1,14 @@
-package timeeat.client.oauth;
+package eatda.client.oauth;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
+import eatda.exception.InitializeException;
import java.util.List;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.NullAndEmptySource;
import org.junit.jupiter.params.provider.ValueSource;
-import timeeat.exception.InitializeException;
class OauthPropertiesTest {
diff --git a/src/test/java/eatda/controller/BaseControllerTest.java b/src/test/java/eatda/controller/BaseControllerTest.java
new file mode 100644
index 00000000..3827893d
--- /dev/null
+++ b/src/test/java/eatda/controller/BaseControllerTest.java
@@ -0,0 +1,136 @@
+package eatda.controller;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doReturn;
+
+import eatda.DatabaseCleaner;
+import eatda.client.map.MapClient;
+import eatda.client.map.StoreSearchResult;
+import eatda.client.oauth.OauthClient;
+import eatda.client.oauth.OauthMemberInformation;
+import eatda.client.oauth.OauthToken;
+import eatda.controller.web.jwt.JwtManager;
+import eatda.domain.member.Member;
+import eatda.fixture.CheerGenerator;
+import eatda.fixture.MemberGenerator;
+import eatda.fixture.StoreGenerator;
+import eatda.repository.member.MemberRepository;
+import eatda.repository.store.CheerRepository;
+import eatda.repository.store.StoreRepository;
+import eatda.service.common.ImageService;
+import eatda.service.common.ImageService;
+import eatda.service.story.StoryService;
+import io.restassured.RestAssured;
+import io.restassured.builder.RequestSpecBuilder;
+import io.restassured.filter.Filter;
+import io.restassured.filter.log.RequestLoggingFilter;
+import io.restassured.filter.log.ResponseLoggingFilter;
+import io.restassured.specification.RequestSpecification;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.List;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.web.server.LocalServerPort;
+import org.springframework.test.context.bean.override.mockito.MockitoBean;
+
+@ExtendWith(DatabaseCleaner.class)
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+public class BaseControllerTest {
+
+ private static final List SPEC_FILTERS = List.of(new RequestLoggingFilter(), new ResponseLoggingFilter());
+
+ private static final OauthToken DEFAULT_OAUTH_TOKEN = new OauthToken("oauth-access-token");
+ private static final OauthMemberInformation DEFAULT_OAUTH_MEMBER_INFO =
+ new OauthMemberInformation(314159248183772L, "constant@kakao.com", "nickname");
+ private static final String MOCKED_IMAGE_KEY = "mocked-image-path";
+ private static final String MOCKED_IMAGE_URL = "https://example.com/image.jpg";
+
+
+ @Autowired
+ protected MemberGenerator memberGenerator;
+
+ @Autowired
+ protected StoreGenerator storeGenerator;
+
+ @Autowired
+ protected CheerGenerator cheerGenerator;
+
+ @Autowired
+ protected MemberRepository memberRepository;
+
+ @Autowired
+ protected StoreRepository storeRepository;
+
+ @Autowired
+ protected CheerRepository cheerRepository;
+
+ @Autowired
+ protected JwtManager jwtManager;
+
+ @MockitoBean
+ private OauthClient oauthClient;
+
+ @MockitoBean
+ private MapClient mapClient;
+
+ @MockitoBean
+ private ImageService imageService;
+
+ @MockitoBean
+ protected StoryService storyService; // TODO ์ค ๊ฐ์ฒด๋ก ๋ณํ
+
+ @LocalServerPort
+ private int port;
+
+ private RequestSpecification spec;
+
+ @BeforeEach
+ final void setEnvironment() {
+ RestAssured.port = port;
+ spec = new RequestSpecBuilder()
+ .addFilters(SPEC_FILTERS)
+ .build();
+ }
+
+ @BeforeEach
+ final void mockingClient() throws URISyntaxException {
+ doReturn(new URI("http://localhost:8080/login/callback")).when(oauthClient).getOauthLoginUrl(anyString());
+ doReturn(DEFAULT_OAUTH_TOKEN).when(oauthClient).requestOauthToken(anyString(), anyString());
+ doReturn(DEFAULT_OAUTH_MEMBER_INFO).when(oauthClient).requestMemberInformation(DEFAULT_OAUTH_TOKEN);
+
+ List searchResults = List.of(
+ new StoreSearchResult("123", "FD6", "์์์ > ํ์ > ๊ตญ๋ฐฅ", "010-1234-1234", "๋๋ฏผ๋ฐฑ์์๋ ๋ณธ์ ", "https://yapp.co.kr",
+ "์์ธ ๊ฐ๋จ๊ตฌ ๋์น๋ 896-33", "์์ธ ๊ฐ๋จ๊ตฌ ์ ๋ฆ๋ก86๊ธธ 40-4", 37.0d, 128.0d),
+ new StoreSearchResult("456", "FD6", "์์์ > ํ์ > ๊ตญ๋ฐฅ", "010-1234-1234", "๋๋ฏผ๋ฐฑ์์๋ ์์ฒญ์ ", "http://yapp.kr",
+ "์์ธ ์ค๊ตฌ ๋ถ์ฐฝ๋ 19-4", null, 37.0d, 128.0d)
+ );
+ doReturn(searchResults).when(mapClient).searchShops(anyString());
+
+ doReturn(MOCKED_IMAGE_URL).when(imageService).getPresignedUrl(anyString());
+ doReturn(MOCKED_IMAGE_KEY).when(imageService).upload(any(), any());
+ }
+
+ protected final RequestSpecification given() {
+ return RestAssured.given(spec);
+ }
+
+ protected final String accessToken() {
+ Member member = memberGenerator.generateByEmail(Long.toString(DEFAULT_OAUTH_MEMBER_INFO.socialId()),
+ "authAccessToken@example.com");
+ return jwtManager.issueAccessToken(member.getId());
+ }
+
+ protected final String refreshToken() {
+ Member member = memberGenerator.generateByEmail(Long.toString(DEFAULT_OAUTH_MEMBER_INFO.socialId()),
+ "authRefreshToken@example.com");
+ return jwtManager.issueRefreshToken(member.getId());
+ }
+
+ protected final String oauthLoginSocialId() {
+ return Long.toString(DEFAULT_OAUTH_MEMBER_INFO.socialId());
+ }
+}
diff --git a/src/test/java/timeeat/controller/CorsTest.java b/src/test/java/eatda/controller/CorsTest.java
similarity index 98%
rename from src/test/java/timeeat/controller/CorsTest.java
rename to src/test/java/eatda/controller/CorsTest.java
index a5da4acf..14ab18f1 100644
--- a/src/test/java/timeeat/controller/CorsTest.java
+++ b/src/test/java/eatda/controller/CorsTest.java
@@ -1,4 +1,4 @@
-package timeeat.controller;
+package eatda.controller;
import static org.hamcrest.Matchers.containsString;
diff --git a/src/test/java/timeeat/controller/auth/AuthControllerTest.java b/src/test/java/eatda/controller/auth/AuthControllerTest.java
similarity index 97%
rename from src/test/java/timeeat/controller/auth/AuthControllerTest.java
rename to src/test/java/eatda/controller/auth/AuthControllerTest.java
index a6f77b5e..02c8709e 100644
--- a/src/test/java/timeeat/controller/auth/AuthControllerTest.java
+++ b/src/test/java/eatda/controller/auth/AuthControllerTest.java
@@ -1,14 +1,14 @@
-package timeeat.controller.auth;
+package eatda.controller.auth;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertAll;
+import eatda.controller.BaseControllerTest;
import io.restassured.http.ContentType;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
-import timeeat.controller.BaseControllerTest;
class AuthControllerTest extends BaseControllerTest {
diff --git a/src/test/java/timeeat/controller/member/MemberControllerTest.java b/src/test/java/eatda/controller/member/MemberControllerTest.java
similarity index 68%
rename from src/test/java/timeeat/controller/member/MemberControllerTest.java
rename to src/test/java/eatda/controller/member/MemberControllerTest.java
index c776de29..7c6f5532 100644
--- a/src/test/java/timeeat/controller/member/MemberControllerTest.java
+++ b/src/test/java/eatda/controller/member/MemberControllerTest.java
@@ -1,16 +1,42 @@
-package timeeat.controller.member;
+package eatda.controller.member;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertAll;
+import eatda.controller.BaseControllerTest;
+import eatda.domain.member.Member;
import io.restassured.http.ContentType;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;
-import timeeat.controller.BaseControllerTest;
class MemberControllerTest extends BaseControllerTest {
+ @Nested
+ class GetMember {
+
+ @Test
+ void ํ์_์ ๋ณด๋ฅผ_์กฐํํ _์_์๋ค() {
+ Member member = memberGenerator.generateRegisteredMember(
+ "123", "abc@kakao.com", "test-nickname", "01012345678");
+ String accessToken = jwtManager.issueAccessToken(member.getId());
+
+ MemberResponse response = given()
+ .header(HttpHeaders.AUTHORIZATION, accessToken)
+ .when().get("/api/member")
+ .then()
+ .statusCode(200)
+ .extract().as(MemberResponse.class);
+
+ assertAll(
+ () -> assertThat(response.id()).isEqualTo(member.getId()),
+ () -> assertThat(response.nickname()).isEqualTo(member.getNickname()),
+ () -> assertThat(response.phoneNumber()).isEqualTo(member.getPhoneNumber()),
+ () -> assertThat(response.isSignUp()).isFalse()
+ );
+ }
+ }
+
@Nested
class CheckNickname {
@@ -27,7 +53,7 @@ class CheckNickname {
@Test
void ์ค๋ณต๋_๋๋ค์์_ํ์ธํ _์_์๋ค() {
String existingNickname = "existing-nickname";
- memberGenerator.generateRegisteredMember("123", existingNickname, "01012345678");
+ memberGenerator.generateRegisteredMember(existingNickname, "hij@kakao.com", "123", "01012345678");
given()
.header(HttpHeaders.AUTHORIZATION, accessToken())
@@ -54,7 +80,7 @@ class CheckPhoneNumber {
@Test
void ์ค๋ณต๋_์ ํ๋ฒํธ๋ฅผ_ํ์ธํ _์_์๋ค() {
String existingPhoneNumber = "01012345678";
- memberGenerator.generateRegisteredMember("123", "nickname", existingPhoneNumber);
+ memberGenerator.generateRegisteredMember("nickname", "hij@kakao.com", "123", existingPhoneNumber);
given()
.header(HttpHeaders.AUTHORIZATION, accessToken())
.queryParam("phoneNumber", existingPhoneNumber)
@@ -69,7 +95,7 @@ class UpdateMember {
@Test
void ํ์_์ ๋ณด๋ฅผ_์์ ํ _์_์๋ค() {
- MemberUpdateRequest request = new MemberUpdateRequest("update-nickname", "01012345678", "์ฑ๋ถ๊ตฌ", true);
+ MemberUpdateRequest request = new MemberUpdateRequest("update-nickname", "01012345678", true);
MemberResponse response = given()
.contentType(ContentType.JSON)
@@ -84,7 +110,6 @@ class UpdateMember {
() -> assertThat(response.isSignUp()).isFalse(),
() -> assertThat(response.nickname()).isEqualTo("update-nickname"),
() -> assertThat(response.phoneNumber()).isEqualTo("01012345678"),
- () -> assertThat(response.interestArea()).isEqualTo("์ฑ๋ถ๊ตฌ"),
() -> assertThat(response.optInMarketing()).isTrue()
);
}
diff --git a/src/test/java/eatda/controller/store/CheerControllerTest.java b/src/test/java/eatda/controller/store/CheerControllerTest.java
new file mode 100644
index 00000000..c0750000
--- /dev/null
+++ b/src/test/java/eatda/controller/store/CheerControllerTest.java
@@ -0,0 +1,45 @@
+package eatda.controller.store;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertAll;
+
+import eatda.controller.BaseControllerTest;
+import eatda.domain.member.Member;
+import eatda.domain.store.Cheer;
+import eatda.domain.store.Store;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+class CheerControllerTest extends BaseControllerTest {
+
+ @Nested
+ class GetCheers {
+
+ @Test
+ void ์์ฒญํ_์์_์ค_์ต์ _์์_N๊ฐ๋ฅผ_์กฐํํ๋ค() {
+ Member member = memberGenerator.generateRegisteredMember("nickname", "ac@kakao.com", "123", "01011111111");
+ Store store1 = storeGenerator.generate("111", "์์ธ์ ๋
ธ์๊ตฌ ์๊ณ3๋ 123-45");
+ Store store2 = storeGenerator.generate("222", "์์ธ์ ์ฑ๋ถ๊ตฌ ์๊ด๋ 123-45");
+ Cheer cheer1 = cheerGenerator.generateAdmin(member, store1);
+ Cheer cheer2 = cheerGenerator.generateAdmin(member, store1);
+ Cheer cheer3 = cheerGenerator.generateAdmin(member, store2);
+
+ CheersResponse response = given()
+ .when()
+ .queryParam("size", 2)
+ .get("/api/cheer")
+ .then()
+ .statusCode(200)
+ .extract().as(CheersResponse.class);
+
+ CheerPreviewResponse firstResponse = response.cheers().get(0);
+ assertAll(
+ () -> assertThat(response.cheers()).hasSize(2),
+ () -> assertThat(firstResponse.storeId()).isEqualTo(store2.getId()),
+ () -> assertThat(firstResponse.storeDistrict()).isEqualTo("์ฑ๋ถ๊ตฌ"),
+ () -> assertThat(firstResponse.storeNeighborhood()).isEqualTo("์๊ด๋"),
+ () -> assertThat(firstResponse.cheerId()).isEqualTo(cheer3.getId())
+ );
+ }
+ }
+}
diff --git a/src/test/java/eatda/controller/store/StoreControllerTest.java b/src/test/java/eatda/controller/store/StoreControllerTest.java
new file mode 100644
index 00000000..eac387c2
--- /dev/null
+++ b/src/test/java/eatda/controller/store/StoreControllerTest.java
@@ -0,0 +1,66 @@
+package eatda.controller.store;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertAll;
+
+import eatda.controller.BaseControllerTest;
+import eatda.domain.member.Member;
+import eatda.domain.store.Store;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.springframework.http.HttpHeaders;
+
+class StoreControllerTest extends BaseControllerTest {
+
+ @Nested
+ class GetStores {
+
+ @Test
+ void ์์์ _๋ชฉ๋ก์_์ต์ ์์ผ๋ก_์กฐํํ๋ค() {
+ Member member = memberGenerator.generate("111");
+ Store store1 = storeGenerator.generate("111", "์์ธ ๊ฐ๋จ๊ตฌ ๋์น๋ 896-33");
+ Store store2 = storeGenerator.generate("222", "์์ธ ๊ฐ๋จ๊ตฌ ๋์น๋ 896-34");
+ Store store3 = storeGenerator.generate("333", "์์ธ ๊ฐ๋จ๊ตฌ ๋์น๋ 896-35");
+ cheerGenerator.generateCommon(member, store1, "image-key-1");
+ cheerGenerator.generateCommon(member, store2, "image-key-2");
+ cheerGenerator.generateCommon(member, store3, "image-key-3");
+
+ int size = 2;
+
+ StoresResponse response = given()
+ .queryParam("size", size)
+ .when()
+ .get("/api/shops")
+ .then()
+ .statusCode(200)
+ .extract().as(StoresResponse.class);
+
+ assertAll(
+ () -> assertThat(response.stores()).hasSize(size),
+ () -> assertThat(response.stores().get(0).id()).isEqualTo(store3.getId()),
+ () -> assertThat(response.stores().get(1).id()).isEqualTo(store2.getId())
+ );
+ }
+ }
+
+ @Nested
+ class SearchStores {
+
+ @Test
+ void ์์์ _๊ฒ์_๊ฒฐ๊ณผ๋ฅผ_๋ฐํํ๋ค() {
+ String query = "๋๋ฏผ๋ฐฑ์์๋";
+
+ StoreSearchResponses responses = given()
+ .header(HttpHeaders.AUTHORIZATION, accessToken())
+ .queryParam("query", query)
+ .when()
+ .get("/api/shop/search")
+ .then()
+ .statusCode(200)
+ .extract().as(StoreSearchResponses.class);
+
+ assertThat(responses.stores()).isNotEmpty();
+ }
+ }
+
+}
diff --git a/src/test/java/eatda/controller/story/StoryControllerTest.java b/src/test/java/eatda/controller/story/StoryControllerTest.java
new file mode 100644
index 00000000..15af0b4f
--- /dev/null
+++ b/src/test/java/eatda/controller/story/StoryControllerTest.java
@@ -0,0 +1,51 @@
+package eatda.controller.story;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+
+import eatda.controller.BaseControllerTest;
+import eatda.service.common.ImageDomain;
+import io.restassured.response.Response;
+import java.nio.charset.StandardCharsets;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+public class StoryControllerTest extends BaseControllerTest {
+
+ @BeforeEach
+ void setUpMock() {
+ doNothing()
+ .when(storyService)
+ .registerStory(any(), any(), any());
+ }
+
+ @Nested
+ class SearchStores {
+
+ @Test
+ void ์คํ ๋ฆฌ๋ฅผ_๋ฑ๋กํ _์_์๋ค() {
+ String requestJson = """
+ {
+ "query": "๋๋ฏผ๋ฐฑ์์๋",
+ "storeKakaoId": "123",
+ "description": "์ฌ๊ธฐ ์ง์ง ๋ง์์ด์!"
+ }
+ """;
+
+ byte[] imageBytes = "dummy image content".getBytes(StandardCharsets.UTF_8);
+
+ Response response = given()
+ .contentType("multipart/form-data")
+ .header("Authorization", accessToken())
+ .multiPart("request", "request.json", requestJson.getBytes(StandardCharsets.UTF_8), "application/json")
+ .multiPart("image", "image.png", imageBytes, "image/png")
+ .when()
+ .post("/api/stories");
+
+ response.then().statusCode(201);
+ }
+ }
+}
diff --git a/src/test/java/timeeat/controller/web/jwt/JwtManagerTest.java b/src/test/java/eatda/controller/web/jwt/JwtManagerTest.java
similarity index 97%
rename from src/test/java/timeeat/controller/web/jwt/JwtManagerTest.java
rename to src/test/java/eatda/controller/web/jwt/JwtManagerTest.java
index c3b6be6f..95a1ef39 100644
--- a/src/test/java/timeeat/controller/web/jwt/JwtManagerTest.java
+++ b/src/test/java/eatda/controller/web/jwt/JwtManagerTest.java
@@ -1,14 +1,14 @@
-package timeeat.controller.web.jwt;
+package eatda.controller.web.jwt;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.junit.jupiter.api.Assertions.assertThrows;
+import eatda.exception.BusinessErrorCode;
+import eatda.exception.BusinessException;
import java.time.Duration;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
-import timeeat.exception.BusinessErrorCode;
-import timeeat.exception.BusinessException;
class JwtManagerTest {
diff --git a/src/test/java/timeeat/controller/web/jwt/JwtPropertiesTest.java b/src/test/java/eatda/controller/web/jwt/JwtPropertiesTest.java
similarity index 96%
rename from src/test/java/timeeat/controller/web/jwt/JwtPropertiesTest.java
rename to src/test/java/eatda/controller/web/jwt/JwtPropertiesTest.java
index 5eca74bb..18b1438f 100644
--- a/src/test/java/timeeat/controller/web/jwt/JwtPropertiesTest.java
+++ b/src/test/java/eatda/controller/web/jwt/JwtPropertiesTest.java
@@ -1,14 +1,14 @@
-package timeeat.controller.web.jwt;
+package eatda.controller.web.jwt;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import eatda.exception.InitializeException;
import java.time.Duration;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.NullAndEmptySource;
-import timeeat.exception.InitializeException;
class JwtPropertiesTest {
diff --git a/src/test/java/timeeat/document/BaseDocumentTest.java b/src/test/java/eatda/document/BaseDocumentTest.java
similarity index 51%
rename from src/test/java/timeeat/document/BaseDocumentTest.java
rename to src/test/java/eatda/document/BaseDocumentTest.java
index dc3c51bc..3bb276a1 100644
--- a/src/test/java/timeeat/document/BaseDocumentTest.java
+++ b/src/test/java/eatda/document/BaseDocumentTest.java
@@ -1,12 +1,24 @@
-package timeeat.document;
+package eatda.document;
+import static org.mockito.Mockito.doReturn;
+import static org.springframework.restdocs.payload.JsonFieldType.STRING;
+import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
+
+import eatda.controller.web.jwt.JwtManager;
+import eatda.exception.BusinessErrorCode;
+import eatda.exception.EtcErrorCode;
+import eatda.service.auth.AuthService;
+import eatda.service.common.ImageService;
+import eatda.service.member.MemberService;
+import eatda.service.store.CheerService;
+import eatda.service.store.StoreService;
+import eatda.service.story.StoryService;
import io.restassured.RestAssured;
import io.restassured.builder.RequestSpecBuilder;
import io.restassured.specification.RequestSpecification;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
-import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.restdocs.RestDocumentationContextProvider;
@@ -15,16 +27,18 @@
import org.springframework.restdocs.restassured.RestAssuredRestDocumentationConfigurer;
import org.springframework.restdocs.restassured.RestDocumentationFilter;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
-import timeeat.controller.web.jwt.JwtManager;
-import timeeat.service.auth.AuthService;
-import timeeat.service.service.MemberService;
@ExtendWith({RestDocumentationExtension.class, MockitoExtension.class})
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public abstract class BaseDocumentTest {
- @Autowired
- private JwtManager jwtManager;
+ protected static final RestDocsResponse ERROR_RESPONSE = new RestDocsResponse()
+ .responseBodyField(
+ fieldWithPath("errorCode").type(STRING).description("์๋ฌ ์ฝ๋"),
+ fieldWithPath("message").type(STRING).description("์๋ฌ ๋ฉ์์ง")
+ );
+ private static final String MOCKED_ACCESS_TOKEN = "access-token";
+ private static final String MOCKED_REFRESH_TOKEN = "refresh-token";
@MockitoBean
protected AuthService authService;
@@ -32,6 +46,20 @@ public abstract class BaseDocumentTest {
@MockitoBean
protected MemberService memberService;
+ @MockitoBean
+ protected StoreService storeService;
+
+ @MockitoBean
+ protected StoryService storyService;
+
+ @MockitoBean
+ protected ImageService imageService;
+
+ @MockitoBean
+ protected CheerService cheerService;
+ @MockitoBean
+ protected JwtManager jwtManager;
+
@LocalServerPort
private int port;
@@ -47,6 +75,14 @@ void setEnvironment(RestDocumentationContextProvider restDocumentation) {
.build();
}
+ @BeforeEach
+ void mockingJwtManager() {
+ doReturn(MOCKED_ACCESS_TOKEN).when(jwtManager).issueAccessToken(1L);
+ doReturn(MOCKED_REFRESH_TOKEN).when(jwtManager).issueRefreshToken(1L);
+ doReturn(1L).when(jwtManager).resolveAccessToken(MOCKED_ACCESS_TOKEN);
+ doReturn(1L).when(jwtManager).resolveRefreshToken(MOCKED_REFRESH_TOKEN);
+ }
+
protected final RestDocsRequest request() {
return new RestDocsRequest();
}
@@ -59,16 +95,24 @@ protected final RestDocsFilterBuilder document(String identifierPrefix, int stat
return new RestDocsFilterBuilder(identifierPrefix, Integer.toString(statusCode));
}
+ protected final RestDocsFilterBuilder document(String identifierPrefix, BusinessErrorCode errorCode) {
+ return new RestDocsFilterBuilder(identifierPrefix, errorCode.name());
+ }
+
protected final RequestSpecification given(RestDocumentationFilter documentationFilter) {
return RestAssured.given(spec)
.filter(documentationFilter);
}
+ protected final RestDocsFilterBuilder document(String identifierPrefix, EtcErrorCode errorCode) {
+ return new RestDocsFilterBuilder(identifierPrefix, errorCode.name());
+ }
+
protected final String accessToken() {
- return jwtManager.issueAccessToken(1L);
+ return MOCKED_ACCESS_TOKEN;
}
protected final String refreshToken() {
- return jwtManager.issueRefreshToken(1L);
+ return MOCKED_REFRESH_TOKEN;
}
}
diff --git a/src/test/java/timeeat/document/RestDocsFilterBuilder.java b/src/test/java/eatda/document/RestDocsFilterBuilder.java
similarity index 99%
rename from src/test/java/timeeat/document/RestDocsFilterBuilder.java
rename to src/test/java/eatda/document/RestDocsFilterBuilder.java
index 89f61e1d..f6620688 100644
--- a/src/test/java/timeeat/document/RestDocsFilterBuilder.java
+++ b/src/test/java/eatda/document/RestDocsFilterBuilder.java
@@ -1,7 +1,8 @@
-package timeeat.document;
+package eatda.document;
import static com.epages.restdocs.apispec.RestAssuredRestDocumentationWrapper.document;
+import com.epages.restdocs.apispec.ResourceSnippetParametersBuilder;
import java.util.ArrayList;
import java.util.List;
import org.springframework.http.HttpHeaders;
@@ -10,7 +11,6 @@
import org.springframework.restdocs.operation.preprocess.Preprocessors;
import org.springframework.restdocs.restassured.RestDocumentationFilter;
import org.springframework.restdocs.snippet.Snippet;
-import com.epages.restdocs.apispec.ResourceSnippetParametersBuilder;
public class RestDocsFilterBuilder {
diff --git a/src/test/java/timeeat/document/RestDocsRequest.java b/src/test/java/eatda/document/RestDocsRequest.java
similarity index 98%
rename from src/test/java/timeeat/document/RestDocsRequest.java
rename to src/test/java/eatda/document/RestDocsRequest.java
index 107219aa..45594238 100644
--- a/src/test/java/timeeat/document/RestDocsRequest.java
+++ b/src/test/java/eatda/document/RestDocsRequest.java
@@ -1,4 +1,4 @@
-package timeeat.document;
+package eatda.document;
import static org.springframework.restdocs.cookies.CookieDocumentation.requestCookies;
import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders;
@@ -6,6 +6,7 @@
import static org.springframework.restdocs.request.RequestDocumentation.pathParameters;
import static org.springframework.restdocs.request.RequestDocumentation.queryParameters;
+import com.epages.restdocs.apispec.ResourceSnippetParametersBuilder;
import java.util.LinkedList;
import java.util.List;
import org.springframework.restdocs.cookies.CookieDescriptor;
@@ -13,7 +14,6 @@
import org.springframework.restdocs.payload.FieldDescriptor;
import org.springframework.restdocs.request.ParameterDescriptor;
import org.springframework.restdocs.snippet.Snippet;
-import com.epages.restdocs.apispec.ResourceSnippetParametersBuilder;
public class RestDocsRequest {
diff --git a/src/test/java/timeeat/document/RestDocsResponse.java b/src/test/java/eatda/document/RestDocsResponse.java
similarity index 98%
rename from src/test/java/timeeat/document/RestDocsResponse.java
rename to src/test/java/eatda/document/RestDocsResponse.java
index ff74930f..ef142274 100644
--- a/src/test/java/timeeat/document/RestDocsResponse.java
+++ b/src/test/java/eatda/document/RestDocsResponse.java
@@ -1,4 +1,4 @@
-package timeeat.document;
+package eatda.document;
import static org.springframework.restdocs.cookies.CookieDocumentation.responseCookies;
import static org.springframework.restdocs.headers.HeaderDocumentation.responseHeaders;
diff --git a/src/test/java/timeeat/document/Tag.java b/src/test/java/eatda/document/Tag.java
similarity index 71%
rename from src/test/java/timeeat/document/Tag.java
rename to src/test/java/eatda/document/Tag.java
index c821cd8e..61a9c130 100644
--- a/src/test/java/timeeat/document/Tag.java
+++ b/src/test/java/eatda/document/Tag.java
@@ -1,9 +1,12 @@
-package timeeat.document;
+package eatda.document;
public enum Tag {
AUTH_API("Auth API"),
MEMBER_API("Member API"),
+ STORE_API("Store API"),
+ STORY_API("Story API"),
+ CHEER_API("Cheer API"),
;
private final String displayName;
diff --git a/src/test/java/timeeat/document/auth/AuthDocumentTest.java b/src/test/java/eatda/document/auth/AuthDocumentTest.java
similarity index 62%
rename from src/test/java/timeeat/document/auth/AuthDocumentTest.java
rename to src/test/java/eatda/document/auth/AuthDocumentTest.java
index 1815e26b..3477a724 100644
--- a/src/test/java/timeeat/document/auth/AuthDocumentTest.java
+++ b/src/test/java/eatda/document/auth/AuthDocumentTest.java
@@ -1,7 +1,8 @@
-package timeeat.document.auth;
+package eatda.document.auth;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName;
import static org.springframework.restdocs.payload.JsonFieldType.BOOLEAN;
import static org.springframework.restdocs.payload.JsonFieldType.NUMBER;
@@ -9,20 +10,24 @@
import static org.springframework.restdocs.payload.JsonFieldType.STRING;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
+import eatda.controller.auth.LoginRequest;
+import eatda.controller.auth.ReissueRequest;
+import eatda.controller.member.MemberResponse;
+import eatda.document.BaseDocumentTest;
+import eatda.document.RestDocsRequest;
+import eatda.document.RestDocsResponse;
+import eatda.document.Tag;
+import eatda.exception.BusinessErrorCode;
+import eatda.exception.BusinessException;
import io.restassured.http.ContentType;
import java.net.URI;
import java.net.URISyntaxException;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
-import timeeat.controller.auth.LoginRequest;
-import timeeat.controller.auth.ReissueRequest;
-import timeeat.controller.member.MemberResponse;
-import timeeat.document.BaseDocumentTest;
-import timeeat.document.RestDocsRequest;
-import timeeat.document.RestDocsResponse;
-import timeeat.document.Tag;
public class AuthDocumentTest extends BaseDocumentTest {
@@ -45,7 +50,7 @@ class RedirectOauthLoginPage {
);
@Test
- void Oauth_๋ก๊ทธ์ธ_ํ์ด์ง๋ก_๋ฆฌ๋ค์ด๋ ํธ_ํ _์_์๋ค() throws URISyntaxException {
+ void Oauth_๋ก๊ทธ์ธ_ํ์ด์ง_๋ฆฌ๋ค์ด๋ ํธ_์ฑ๊ณต() throws URISyntaxException {
doReturn(new URI("http://localhost:8080")).when(authService).getOauthLoginUrl(anyString());
var document = document("auth/oauth-redirect", 302)
@@ -61,6 +66,22 @@ class RedirectOauthLoginPage {
.then()
.statusCode(302);
}
+
+ @EnumSource(value = BusinessErrorCode.class, names = {"UNAUTHORIZED_ORIGIN"})
+ @ParameterizedTest
+ void Oauth_๋ก๊ทธ์ธ_ํ์ด์ง_๋ฆฌ๋ค์ด๋ ํธ_์คํจ(BusinessErrorCode errorCode) {
+ doThrow(new BusinessException(errorCode)).when(authService).getOauthLoginUrl(anyString());
+
+ var document = document("auth/oauth-redirect", errorCode)
+ .request(requestDocument)
+ .response(ERROR_RESPONSE)
+ .build();
+
+ given(document)
+ .header(HttpHeaders.REFERER, origin)
+ .when().get("/api/auth/login/oauth")
+ .then().statusCode(errorCode.getStatus().value());
+ }
}
@Nested
@@ -81,17 +102,17 @@ class Login {
fieldWithPath("token.refreshToken").type(STRING).description("๋ฆฌํ๋ ์ ํ ํฐ"),
fieldWithPath("information").type(OBJECT).description("์ ์ ์ ๋ณด"),
fieldWithPath("information.id").type(NUMBER).description("์ ์ ์๋ณ์"),
+ fieldWithPath("information.email").type(STRING).description("์ ์ ์ด๋ฉ์ผ"),
fieldWithPath("information.isSignUp").type(BOOLEAN).description("ํ์ ๊ฐ์
์ฌ๋ถ"),
fieldWithPath("information.nickname").type(STRING).description("์ ์ ๋๋ค์"),
fieldWithPath("information.phoneNumber").type(STRING).description("ํธ๋ํฐ ์ ํ๋ฒํธ").optional(),
- fieldWithPath("information.interestArea").type(STRING).description("์ ์ ๊ด์ฌ ์ง์ญ").optional(),
fieldWithPath("information.optInMarketing").type(BOOLEAN).description("๋ง์ผํ
๋์ ์ฌ๋ถ").optional()
);
@Test
void ๋ก๊ทธ์ธ_์ฑ๊ณต() {
LoginRequest request = new LoginRequest("code", "http://localhost:3000");
- MemberResponse response = new MemberResponse(1L, true, "๋๋ค์", null, null, null);
+ MemberResponse response = new MemberResponse(1L, "abc@kakao.com", true, "๋๋ค์", null, null);
doReturn(response).when(authService).login(request);
var document = document("auth/login", 201)
@@ -101,10 +122,29 @@ class Login {
given(document)
.contentType(ContentType.JSON)
+ .header(HttpHeaders.ORIGIN, origin)
.body(request)
.when().post("/api/auth/login")
.then().statusCode(201);
}
+
+ @EnumSource(value = BusinessErrorCode.class, names = {"UNAUTHORIZED_ORIGIN", "OAUTH_SERVER_ERROR"})
+ @ParameterizedTest
+ void ๋ก๊ทธ์ธ_์คํจ(BusinessErrorCode errorCode) {
+ LoginRequest request = new LoginRequest("code", "http://localhost:3000");
+ doThrow(new BusinessException(errorCode)).when(authService).login(request);
+
+ var document = document("auth/login", errorCode)
+ .request(requestDocument)
+ .response(ERROR_RESPONSE)
+ .build();
+
+ given(document)
+ .contentType(ContentType.JSON)
+ .body(request)
+ .when().post("/api/auth/login")
+ .then().statusCode(errorCode.getStatus().value());
+ }
}
@Nested
@@ -138,5 +178,23 @@ class ReissueToken {
.when().post("/api/auth/reissue")
.then().statusCode(201);
}
+
+ @EnumSource(value = BusinessErrorCode.class, names = {"EXPIRED_TOKEN", "UNAUTHORIZED_MEMBER"})
+ @ParameterizedTest
+ void ํ ํฐ_์ฌ๋ฐ๊ธ_์คํจ(BusinessErrorCode errorCode) {
+ ReissueRequest request = new ReissueRequest(refreshToken());
+ doThrow(new BusinessException(errorCode)).when(jwtManager).resolveRefreshToken(request.refreshToken());
+
+ var document = document("auth/reissue", errorCode)
+ .request(requestDocument)
+ .response(ERROR_RESPONSE)
+ .build();
+
+ given(document)
+ .contentType(ContentType.JSON)
+ .body(request)
+ .when().post("/api/auth/reissue")
+ .then().statusCode(errorCode.getStatus().value());
+ }
}
}
diff --git a/src/test/java/eatda/document/member/MemberDocumentTest.java b/src/test/java/eatda/document/member/MemberDocumentTest.java
new file mode 100644
index 00000000..ef3203aa
--- /dev/null
+++ b/src/test/java/eatda/document/member/MemberDocumentTest.java
@@ -0,0 +1,251 @@
+package eatda.document.member;
+
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName;
+import static org.springframework.restdocs.payload.JsonFieldType.BOOLEAN;
+import static org.springframework.restdocs.payload.JsonFieldType.NUMBER;
+import static org.springframework.restdocs.payload.JsonFieldType.STRING;
+import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
+import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
+
+import eatda.controller.member.MemberResponse;
+import eatda.controller.member.MemberUpdateRequest;
+import eatda.document.BaseDocumentTest;
+import eatda.document.RestDocsRequest;
+import eatda.document.RestDocsResponse;
+import eatda.document.Tag;
+import eatda.exception.BusinessErrorCode;
+import eatda.exception.BusinessException;
+import io.restassured.http.ContentType;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+import org.springframework.http.HttpHeaders;
+
+public class MemberDocumentTest extends BaseDocumentTest {
+
+ @Nested
+ class GetMember {
+
+ RestDocsRequest requestDocument = request()
+ .tag(Tag.MEMBER_API)
+ .summary("ํ์ ์ ๋ณด ์กฐํ")
+ .requestHeader(
+ headerWithName(HttpHeaders.AUTHORIZATION).description("์ก์ธ์ค ํ ํฐ")
+ );
+
+ RestDocsResponse responseDocument = response()
+ .responseBodyField(
+ fieldWithPath("id").type(NUMBER).description("ํ์ ์๋ณ์"),
+ fieldWithPath("email").type(STRING).description("ํ์ ์ด๋ฉ์ผ"),
+ fieldWithPath("isSignUp").type(BOOLEAN).description("ํ์ ๊ฐ์
์์ฒญ ์ฌ๋ถ (false ๊ณ ์ )"),
+ fieldWithPath("nickname").type(STRING).description("ํ์ ๋๋ค์").optional(),
+ fieldWithPath("phoneNumber").type(STRING).description("ํ์ ์ ํ๋ฒํธ ex) 01012345678").optional(),
+ fieldWithPath("optInMarketing").type(BOOLEAN).description("๋ง์ผํ
๋์ ์ฌ๋ถ").optional()
+ );
+
+ @Test
+ void ํ์_์ ๋ณด_์กฐํ_์ฑ๊ณต() {
+ MemberResponse response = new MemberResponse(1L, "abc@kakao.com", false, "test-nickname", "01012345678", true);
+ doReturn(response).when(memberService).getMember(anyLong());
+
+ var document = document("member/get", 200)
+ .request(requestDocument)
+ .response(responseDocument)
+ .build();
+
+ given(document)
+ .contentType(ContentType.JSON)
+ .header(HttpHeaders.AUTHORIZATION, accessToken())
+ .when().get("/api/member")
+ .then().statusCode(200);
+ }
+
+ @EnumSource(value = BusinessErrorCode.class,
+ names = {"UNAUTHORIZED_MEMBER", "EXPIRED_TOKEN", "INVALID_MEMBER_ID"})
+ @ParameterizedTest
+ void ํ์_์ ๋ณด_์กฐํ_์คํจ(BusinessErrorCode errorCode) {
+ doThrow(new BusinessException(errorCode)).when(memberService).getMember(anyLong());
+
+ var document = document("member/get", errorCode)
+ .request(requestDocument)
+ .response(ERROR_RESPONSE)
+ .build();
+
+ given(document)
+ .contentType(ContentType.JSON)
+ .header(HttpHeaders.AUTHORIZATION, accessToken())
+ .when().get("/api/member")
+ .then().statusCode(errorCode.getStatus().value());
+ }
+ }
+
+ @Nested
+ class CheckNickname {
+
+ RestDocsRequest requestDocument = request()
+ .tag(Tag.MEMBER_API)
+ .summary("๋๋ค์ ์ค๋ณต ๊ฒ์ฌ")
+ .requestHeader(
+ headerWithName(HttpHeaders.AUTHORIZATION).description("์ก์ธ์ค ํ ํฐ")
+ ).queryParameter(
+ parameterWithName("nickname").description("๊ฒ์ฌํ ๋๋ค์")
+ );
+
+ @Test
+ void ์ค๋ณต_๋๋ค์_ํ์ธ_์ฑ๊ณต() {
+ doNothing().when(memberService).validateNickname(anyString(), anyLong());
+
+ var document = document("member/nickname-check", 204)
+ .request(requestDocument)
+ .build();
+
+ given(document)
+ .contentType(ContentType.JSON)
+ .header(HttpHeaders.AUTHORIZATION, accessToken())
+ .queryParam("nickname", "new-nickname")
+ .when().get("/api/member/nickname/check")
+ .then().statusCode(204);
+ }
+
+ @EnumSource(value = BusinessErrorCode.class,
+ names = {"UNAUTHORIZED_MEMBER", "EXPIRED_TOKEN", "DUPLICATE_NICKNAME"})
+ @ParameterizedTest
+ void ์ค๋ณต_๋๋ค์_ํ์ธ_์คํจ(BusinessErrorCode errorCode) {
+ doThrow(new BusinessException(errorCode))
+ .when(memberService).validateNickname(anyString(), anyLong());
+
+ var document = document("member/nickname-check", errorCode)
+ .request(requestDocument)
+ .response(ERROR_RESPONSE)
+ .build();
+
+ given(document)
+ .contentType(ContentType.JSON)
+ .header(HttpHeaders.AUTHORIZATION, accessToken())
+ .queryParam("nickname", "existing-nickname")
+ .when().get("/api/member/nickname/check")
+ .then().statusCode(errorCode.getStatus().value());
+ }
+ }
+
+ @Nested
+ class CheckPhoneNumber {
+
+ RestDocsRequest requestDocument = request()
+ .tag(Tag.MEMBER_API)
+ .summary("์ ํ๋ฒํธ ์ค๋ณต ๊ฒ์ฌ")
+ .requestHeader(
+ headerWithName(HttpHeaders.AUTHORIZATION).description("์ก์ธ์ค ํ ํฐ")
+ ).queryParameter(
+ parameterWithName("phoneNumber").description("๊ฒ์ฌํ ์ ํ๋ฒํธ ex) 01012345678")
+ );
+
+ @Test
+ void ์ค๋ณต_์ ํ๋ฒํธ_ํ์ธ_์ฑ๊ณต() {
+ doNothing().when(memberService).validatePhoneNumber(anyString(), anyLong());
+
+ var document = document("member/phone-number-check", 204)
+ .request(requestDocument)
+ .build();
+
+ given(document)
+ .contentType(ContentType.JSON)
+ .header(HttpHeaders.AUTHORIZATION, accessToken())
+ .queryParam("phoneNumber", "01098765432")
+ .when().get("/api/member/phone-number/check")
+ .then().statusCode(204);
+ }
+
+ @EnumSource(value = BusinessErrorCode.class,
+ names = {"UNAUTHORIZED_MEMBER", "EXPIRED_TOKEN", "DUPLICATE_PHONE_NUMBER"})
+ @ParameterizedTest
+ void ์ค๋ณต_์ ํ๋ฒํธ_ํ์ธ_์คํจ(BusinessErrorCode errorCode) {
+ doThrow(new BusinessException(errorCode))
+ .when(memberService).validatePhoneNumber(anyString(), anyLong());
+
+ var document = document("member/phone-number-check", errorCode)
+ .request(requestDocument)
+ .response(ERROR_RESPONSE)
+ .build();
+
+ given(document)
+ .contentType(ContentType.JSON)
+ .header(HttpHeaders.AUTHORIZATION, accessToken())
+ .queryParam("phoneNumber", "01012345678")
+ .when().get("/api/member/phone-number/check")
+ .then().statusCode(errorCode.getStatus().value());
+ }
+ }
+
+ @Nested
+ class UpdateMember {
+
+ RestDocsRequest requestDocument = request()
+ .tag(Tag.MEMBER_API)
+ .summary("ํ์ ์ ๋ณด ์์ ")
+ .requestHeader(
+ headerWithName(HttpHeaders.AUTHORIZATION).description("์ก์ธ์ค ํ ํฐ")
+ ).requestBodyField(
+ fieldWithPath("nickname").type(STRING).description("ํ์ ๋๋ค์"),
+ fieldWithPath("phoneNumber").type(STRING).description("ํ์ ์ ํ๋ฒํธ ex) 01012345678"),
+ fieldWithPath("optInMarketing").type(BOOLEAN).description("๋ง์ผํ
๋์ ์ฌ๋ถ")
+ );
+
+ RestDocsResponse responseDocument = response()
+ .responseBodyField(
+ fieldWithPath("id").type(NUMBER).description("ํ์ ์๋ณ์"),
+ fieldWithPath("email").type(STRING).description("ํ์ ์ด๋ฉ์ผ"),
+ fieldWithPath("isSignUp").type(BOOLEAN).description("ํ์ ๊ฐ์
์์ฒญ ์ฌ๋ถ (false ๊ณ ์ )"),
+ fieldWithPath("nickname").type(STRING).description("ํ์ ๋๋ค์").optional(),
+ fieldWithPath("phoneNumber").type(STRING).description("ํ์ ์ ํ๋ฒํธ ex) 01012345678").optional(),
+ fieldWithPath("optInMarketing").type(BOOLEAN).description("๋ง์ผํ
๋์ ์ฌ๋ถ").optional()
+ );
+
+ @Test
+ void ํ์_์ ๋ณด_์์ _์ฑ๊ณต() {
+ MemberUpdateRequest request = new MemberUpdateRequest("update-nickname", "01012345678", true);
+ MemberResponse response = new MemberResponse(1L, "abc@kakao.com", false, "update-nickname", "01012345678", true);
+ doReturn(response).when(memberService).update(anyLong(), eq(request));
+
+ var document = document("member/update", 200)
+ .request(requestDocument)
+ .response(responseDocument)
+ .build();
+
+ given(document)
+ .contentType(ContentType.JSON)
+ .header(HttpHeaders.AUTHORIZATION, accessToken())
+ .body(request)
+ .when().put("/api/member")
+ .then().statusCode(200);
+ }
+
+ @EnumSource(value = BusinessErrorCode.class,
+ names = {"UNAUTHORIZED_MEMBER", "EXPIRED_TOKEN", "DUPLICATE_NICKNAME", "DUPLICATE_PHONE_NUMBER",
+ "INVALID_MOBILE_PHONE_NUMBER", "INVALID_MARKETING_CONSENT"})
+ @ParameterizedTest
+ void ํ์_์ ๋ณด_์์ _์คํจ(BusinessErrorCode errorCode) {
+ MemberUpdateRequest request = new MemberUpdateRequest("update-nickname", "01012345678", true);
+ doThrow(new BusinessException(errorCode)).when(memberService).update(anyLong(), eq(request));
+
+ var document = document("member/update", errorCode)
+ .request(requestDocument)
+ .response(ERROR_RESPONSE)
+ .build();
+
+ given(document)
+ .contentType(ContentType.JSON)
+ .header(HttpHeaders.AUTHORIZATION, accessToken())
+ .body(request)
+ .when().put("/api/member")
+ .then().statusCode(errorCode.getStatus().value());
+ }
+ }
+}
diff --git a/src/test/java/eatda/document/store/CheerDocumentTest.java b/src/test/java/eatda/document/store/CheerDocumentTest.java
new file mode 100644
index 00000000..e8dccd8e
--- /dev/null
+++ b/src/test/java/eatda/document/store/CheerDocumentTest.java
@@ -0,0 +1,93 @@
+package eatda.document.store;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.springframework.restdocs.payload.JsonFieldType.ARRAY;
+import static org.springframework.restdocs.payload.JsonFieldType.NUMBER;
+import static org.springframework.restdocs.payload.JsonFieldType.STRING;
+import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
+import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
+
+import eatda.controller.store.CheerPreviewResponse;
+import eatda.controller.store.CheersResponse;
+import eatda.document.BaseDocumentTest;
+import eatda.document.RestDocsRequest;
+import eatda.document.RestDocsResponse;
+import eatda.document.Tag;
+import eatda.exception.BusinessErrorCode;
+import eatda.exception.BusinessException;
+import io.restassured.http.ContentType;
+import java.util.List;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+
+public class CheerDocumentTest extends BaseDocumentTest {
+
+ @Nested
+ class Get {
+
+ RestDocsRequest requestDocument = request()
+ .tag(Tag.STORE_API)
+ .summary("์ต์ ์์ ๊ฒ์")
+ .queryParameter(
+ parameterWithName("size").description("์กฐํ ๊ฐ์ (์ต์ 1, ์ต๋ 50)")
+ );
+
+ RestDocsResponse responseDocument = response()
+ .responseBodyField(
+ fieldWithPath("cheers").type(ARRAY).description("์์ ๊ฒ์ ๊ฒฐ๊ณผ"),
+ fieldWithPath("cheers[].storeId").type(NUMBER).description("๊ฐ๊ฒ ID"),
+ fieldWithPath("cheers[].imageUrl").type(STRING).description("์ด๋ฏธ์ง URL").optional(),
+ fieldWithPath("cheers[].storeName").type(STRING).description("๊ฐ๊ฒ ์ด๋ฆ"),
+ fieldWithPath("cheers[].storeDistrict").type(STRING).description("๊ฐ๊ฒ ์ฃผ์ (๊ตฌ)"),
+ fieldWithPath("cheers[].storeNeighborhood").type(STRING).description("๊ฐ๊ฒ ์ฃผ์ (๋)"),
+ fieldWithPath("cheers[].storeCategory").type(STRING).description("๊ฐ๊ฒ ์นดํ
๊ณ ๋ฆฌ"),
+ fieldWithPath("cheers[].cheerId").type(NUMBER).description("์์ ID"),
+ fieldWithPath("cheers[].cheerDescription").type(STRING).description("์์ ๋ด์ฉ")
+ );
+
+ @Test
+ void ์์์ _๊ฒ์_์ฑ๊ณต() {
+ int size = 2;
+ CheersResponse responses = new CheersResponse(List.of(
+ new CheerPreviewResponse(2L, "https://example.image", "๋๋ฏผ๋ฐฑ์์๋ ๋ณธ์ ", "๊ฐ๋จ๊ตฌ", "์ ๋ฆ๊ตฌ", "ํ์", 2L,
+ "๋๋ฌด ๋ง์์ด์!"),
+ new CheerPreviewResponse(1L, null, "์๊ด๋๋ก๋ณถ์ด", "์ฑ๋ถ๊ตฌ", "์๊ด๋", "๊ธฐํ", 1L,
+ "๋๋ฌด ๋งค์์! ํ์ง๋ง ๋ง์์ด์!")
+ ));
+ doReturn(responses).when(cheerService).getCheers(anyInt());
+
+ var document = document("cheer/get-many", 200)
+ .request(requestDocument)
+ .response(responseDocument)
+ .build();
+
+ given(document)
+ .contentType(ContentType.JSON)
+ .queryParam("size", size)
+ .when().get("/api/cheer")
+ .then().statusCode(200);
+ }
+
+ @EnumSource(value = BusinessErrorCode.class, names = {"PRESIGNED_URL_GENERATION_FAILED"})
+ @ParameterizedTest
+ void ์์์ _๊ฒ์_์คํจ(BusinessErrorCode errorCode) {
+ int size = 2;
+ doThrow(new BusinessException(errorCode)).when(cheerService).getCheers(anyInt());
+
+ var document = document("cheer/get-many", errorCode)
+ .request(requestDocument)
+ .response(ERROR_RESPONSE)
+ .build();
+
+ given(document)
+ .contentType(ContentType.JSON)
+ .queryParam("size", size)
+ .when().get("/api/cheer")
+ .then().statusCode(errorCode.getStatus().value());
+ }
+ }
+}
diff --git a/src/test/java/eatda/document/store/StoreDocumentTest.java b/src/test/java/eatda/document/store/StoreDocumentTest.java
new file mode 100644
index 00000000..31210808
--- /dev/null
+++ b/src/test/java/eatda/document/store/StoreDocumentTest.java
@@ -0,0 +1,158 @@
+package eatda.document.store;
+
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName;
+import static org.springframework.restdocs.payload.JsonFieldType.ARRAY;
+import static org.springframework.restdocs.payload.JsonFieldType.NUMBER;
+import static org.springframework.restdocs.payload.JsonFieldType.STRING;
+import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
+import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
+
+import eatda.controller.store.StorePreviewResponse;
+import eatda.controller.store.StoreSearchResponse;
+import eatda.controller.store.StoreSearchResponses;
+import eatda.controller.store.StoresResponse;
+import eatda.document.BaseDocumentTest;
+import eatda.document.RestDocsRequest;
+import eatda.document.RestDocsResponse;
+import eatda.document.Tag;
+import eatda.exception.BusinessErrorCode;
+import eatda.exception.BusinessException;
+import io.restassured.http.ContentType;
+import java.util.List;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+import org.springframework.http.HttpHeaders;
+
+public class StoreDocumentTest extends BaseDocumentTest {
+
+ @Nested
+ class GetStores {
+
+ RestDocsRequest requestDocument = request()
+ .tag(Tag.STORE_API)
+ .summary("์์์ ๋ชฉ๋ก ์กฐํ")
+ .queryParameter(
+ parameterWithName("size").description("์กฐํํ ์์์ ๊ฐ์ (์ต์ 1, ์ต๋ 50)")
+ );
+
+ RestDocsResponse responseDocument = response()
+ .responseBodyField(
+ fieldWithPath("stores").type(ARRAY).description("์์์ ๋ชฉ๋ก"),
+ fieldWithPath("stores[].id").type(NUMBER).description("์์์ ID"),
+ fieldWithPath("stores[].imageUrl").type(STRING).description("์์์ ๋ํ ์ด๋ฏธ์ง URL"),
+ fieldWithPath("stores[].name").type(STRING).description("์์์ ์ด๋ฆ"),
+ fieldWithPath("stores[].district").type(STRING).description("์์์ ์ฃผ์ (๊ตฌ)"),
+ fieldWithPath("stores[].neighborhood").type(STRING).description("์์์ ์ฃผ์ (๋)"),
+ fieldWithPath("stores[].category").type(STRING).description("์์์ ์นดํ
๊ณ ๋ฆฌ")
+ );
+
+ @Test
+ void ์์์ _๋ชฉ๋ก_์ต์ ์์ผ๋ก_์กฐํ() {
+ StoresResponse response = new StoresResponse(List.of(
+ new StorePreviewResponse(2L, "https://example.image", "๋๋ฏผ๋ฐฑ์์๋", "๊ฐ๋จ๊ตฌ", "๋์น๋", "ํ์"),
+ new StorePreviewResponse(1L, "https://example.image", "์๊ด๋๋ก๋ณถ์ด", "์ฑ๋ถ๊ตฌ", "์๊ด๋", "ํ์")
+ ));
+ doReturn(response).when(storeService).getStores(anyInt());
+
+ int size = 2;
+ var document = document("store/get", 200)
+ .request(requestDocument)
+ .response(responseDocument)
+ .build();
+
+ given(document)
+ .contentType(ContentType.JSON)
+ .queryParam("size", size)
+ .when().get("/api/shops")
+ .then().statusCode(200);
+ }
+
+ @EnumSource(value = BusinessErrorCode.class, names = {"PRESIGNED_URL_GENERATION_FAILED"})
+ @ParameterizedTest
+ void ์์์ _๋ชฉ๋ก_์กฐํ_์คํจ(BusinessErrorCode errorCode) {
+ doThrow(new BusinessException(errorCode)).when(storeService).getStores(anyInt());
+
+ int size = 2;
+ var document = document("store/get", errorCode)
+ .request(requestDocument)
+ .response(ERROR_RESPONSE)
+ .build();
+
+ given(document)
+ .contentType(ContentType.JSON)
+ .queryParam("size", size)
+ .when().get("/api/shops")
+ .then().statusCode(errorCode.getStatus().value());
+ }
+ }
+
+ @Nested
+ class SearchStores {
+
+ RestDocsRequest requestDocument = request()
+ .tag(Tag.STORE_API)
+ .summary("์์์ ๊ฒ์")
+ .requestHeader(
+ headerWithName(HttpHeaders.AUTHORIZATION).description("์ก์ธ์ค ํ ํฐ")
+ ).queryParameter(
+ parameterWithName("query").description("๊ฒ์์ด")
+ );
+
+ RestDocsResponse responseDocument = response()
+ .responseBodyField(
+ fieldWithPath("stores").type(ARRAY).description("์์์ ๊ฒ์ ๊ฒฐ๊ณผ"),
+ fieldWithPath("stores[].kakaoId").type(STRING).description("์นด์นด์ค ์์์ ID"),
+ fieldWithPath("stores[].name").type(STRING).description("์์์ ์ด๋ฆ"),
+ fieldWithPath("stores[].address").type(STRING).description("์์์ ์ฃผ์ (์ง๋ฒ ์ฃผ์)")
+ );
+
+ @Test
+ void ์์์ _๊ฒ์_์ฑ๊ณต() {
+ String query = "๋๋ฏผ๋ฐฑ์์๋";
+ StoreSearchResponses responses = new StoreSearchResponses(List.of(
+ new StoreSearchResponse("17163273", "๋๋ฏผ๋ฐฑ์์๋ ๋ณธ์ ", "์์ธ ๊ฐ๋จ๊ตฌ ๋์น๋ 896-33"),
+ new StoreSearchResponse("1062153333", "๋๋ฏผ๋ฐฑ์์๋ ์์ฒญ์ง์์ ", "์์ธ ์ค๊ตฌ ๋ถ์ฐฝ๋ 19-4")
+ ));
+ doReturn(responses).when(storeService).searchStores(anyString());
+
+ var document = document("store/search", 200)
+ .request(requestDocument)
+ .response(responseDocument)
+ .build();
+
+ given(document)
+ .contentType(ContentType.JSON)
+ .header(HttpHeaders.AUTHORIZATION, accessToken())
+ .queryParam("query", query)
+ .when().get("/api/shop/search")
+ .then().statusCode(200);
+ }
+
+ @EnumSource(value = BusinessErrorCode.class,
+ names = {"UNAUTHORIZED_MEMBER", "EXPIRED_TOKEN", "MAP_SERVER_ERROR"})
+ @ParameterizedTest
+ void ์์์ _๊ฒ์_์คํจ(BusinessErrorCode errorCode) {
+ String query = "๋๋ฏผ๋ฐฑ์์๋";
+ doThrow(new BusinessException(errorCode)).when(storeService).searchStores(anyString());
+
+ var document = document("store/search", errorCode)
+ .request(requestDocument)
+ .response(ERROR_RESPONSE)
+ .build();
+
+ given(document)
+ .contentType(ContentType.JSON)
+ .header(HttpHeaders.AUTHORIZATION, accessToken())
+ .queryParam("query", query)
+ .when().get("/api/shop/search")
+ .then().statusCode(errorCode.getStatus().value());
+ }
+ }
+}
diff --git a/src/test/java/eatda/document/story/StoryDocumentTest.java b/src/test/java/eatda/document/story/StoryDocumentTest.java
new file mode 100644
index 00000000..e88838b7
--- /dev/null
+++ b/src/test/java/eatda/document/story/StoryDocumentTest.java
@@ -0,0 +1,136 @@
+package eatda.document.story;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName;
+
+import eatda.document.BaseDocumentTest;
+import eatda.document.RestDocsRequest;
+import eatda.document.RestDocsResponse;
+import eatda.document.Tag;
+import eatda.exception.BusinessErrorCode;
+import eatda.exception.BusinessException;
+import eatda.exception.EtcErrorCode;
+import eatda.service.common.ImageDomain;
+import io.restassured.response.Response;
+import java.nio.charset.StandardCharsets;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.springframework.http.HttpHeaders;
+import org.springframework.restdocs.restassured.RestDocumentationFilter;
+
+public class StoryDocumentTest extends BaseDocumentTest {
+
+ @Nested
+ class RegisterStory {
+
+ RestDocsRequest requestDocument = request()
+ .tag(Tag.STORY_API)
+ .summary("์คํ ๋ฆฌ ๋ฑ๋ก")
+ .description("์คํ ๋ฆฌ์ ์ด๋ฏธ์ง๋ฅผ multipart/form-data๋ก ๋ฑ๋กํฉ๋๋ค.")
+ .requestHeader(
+ headerWithName(HttpHeaders.AUTHORIZATION).description("์ก์ธ์ค ํ ํฐ")
+ );
+
+ RestDocsResponse responseDocument = response();
+
+ @Test
+ void ์คํ ๋ฆฌ_๋ฑ๋ก_์ฑ๊ณต() {
+ doReturn("https://dummy-s3.com/story.png")
+ .when(imageService)
+ .upload(any(), org.mockito.ArgumentMatchers.eq(ImageDomain.STORY));
+
+ doNothing().when(storyService)
+ .registerStory(any(), any(), any());
+
+ String requestJson = """
+ {
+ "query": "๋๋ฏผ๋ฐฑ์์๋",
+ "storeKakaoId": "123",
+ "description": "์ฌ๊ธฐ ์ง์ง ๋ง์์ด์!"
+ }
+ """;
+
+ byte[] imageBytes = "dummy image content".getBytes(StandardCharsets.UTF_8);
+
+ RestDocumentationFilter document = document("story/register", 201)
+ .request(requestDocument)
+ .response(responseDocument)
+ .build();
+
+ Response response = given(document)
+ .contentType("multipart/form-data")
+ .header(HttpHeaders.AUTHORIZATION, accessToken())
+ .multiPart("request", "request.json", requestJson.getBytes(StandardCharsets.UTF_8), "application/json")
+ .multiPart("image", "image.png", imageBytes, "image/png")
+ .when().post("/api/stories");
+
+ response.then().statusCode(201);
+ }
+
+ @Test
+ void ์คํ ๋ฆฌ_๋ฑ๋ก_์คํจ_ํ์๊ฐ_๋๋ฝ() {
+ String invalidJson = """
+ {
+ "query": "๋๋ฏผ๋ฐฑ์์๋",
+ "storeKakaoId": "123"
+ }
+ """;
+
+ byte[] imageBytes = "dummy image content".getBytes(StandardCharsets.UTF_8);
+
+ doThrow(new BusinessException(BusinessErrorCode.INVALID_STORY_DESCRIPTION))
+ .when(storyService)
+ .registerStory(any(), any(), any());
+
+ var document = document("story/register", EtcErrorCode.CLIENT_REQUEST_ERROR)
+ .request(requestDocument)
+ .response(ERROR_RESPONSE)
+ .build();
+
+ given(document)
+ .contentType("multipart/form-data")
+ .header(HttpHeaders.AUTHORIZATION, accessToken())
+ .multiPart("request", "request.json", invalidJson.getBytes(StandardCharsets.UTF_8), "application/json")
+ .multiPart("image", "image.png", imageBytes, "image/png")
+ .when().post("/api/stories")
+ .then().statusCode(EtcErrorCode.CLIENT_REQUEST_ERROR.getStatus().value());
+ }
+
+ @Test
+ void ์คํ ๋ฆฌ_๋ฑ๋ก_์คํจ_์ด๋ฏธ์ง_ํ์_์ค๋ฅ() {
+ String requestJson = """
+ {
+ "query": "๋๋ฏผ๋ฐฑ์์๋",
+ "storeKakaoId": "123",
+ "description": "์ฌ๊ธฐ ์ง์ง ๋ง์์ด์!"
+ }
+ """;
+
+ byte[] invalidImage = "not an image".getBytes(StandardCharsets.UTF_8);
+
+ doThrow(new BusinessException(BusinessErrorCode.INVALID_IMAGE_TYPE))
+ .when(storyService)
+ .registerStory(any(), any(), any());
+
+ var document = document("story/register", BusinessErrorCode.INVALID_IMAGE_TYPE)
+ .request(requestDocument)
+ .response(ERROR_RESPONSE)
+ .build();
+
+ Response response = given(document)
+ .contentType("multipart/form-data")
+ .header(HttpHeaders.AUTHORIZATION, accessToken())
+ .multiPart("request", "request.json", requestJson.getBytes(StandardCharsets.UTF_8), "application/json")
+ .multiPart("image", "image.txt", invalidImage, "text/plain")
+ .when().post("/api/stories");
+
+ System.out.println("์๋ต ์ํ์ฝ๋ >>> " + response.statusCode());
+ System.out.println("์๋ต ๋ฐ๋ >>> " + response.asString());
+
+ response.then().statusCode(BusinessErrorCode.INVALID_IMAGE_TYPE.getStatus().value());
+ }
+ }
+}
diff --git a/src/test/java/timeeat/domain/member/MemberTest.java b/src/test/java/eatda/domain/member/MemberTest.java
similarity index 68%
rename from src/test/java/timeeat/domain/member/MemberTest.java
rename to src/test/java/eatda/domain/member/MemberTest.java
index f7268066..aedd5ca9 100644
--- a/src/test/java/timeeat/domain/member/MemberTest.java
+++ b/src/test/java/eatda/domain/member/MemberTest.java
@@ -1,15 +1,17 @@
-package timeeat.domain.member;
+package eatda.domain.member;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertThrows;
+import eatda.exception.BusinessErrorCode;
+import eatda.exception.BusinessException;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
-import timeeat.enums.InterestArea;
-import timeeat.exception.BusinessErrorCode;
-import timeeat.exception.BusinessException;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.NullAndEmptySource;
+import org.junit.jupiter.params.provider.ValueSource;
class MemberTest {
@@ -18,16 +20,17 @@ class MemberTest {
class CreateOauthMember {
@Test
- void socialId์_nickname๋ง์ผ๋ก_์์ฑ_์_๋๋จธ์ง_ํ๋๋_null_์ํ์ด๋ค() {
+ void socialId_email_nickname๋ง์ผ๋ก_์์ฑ_์_๋๋จธ์ง_ํ๋๋_null_์ํ์ด๋ค() {
String socialId = "oauth-user-id";
+ String email = "test@example.com";
- Member member = new Member(socialId, "nickname");
+ Member member = new Member(socialId, email, "nickname");
assertAll(
() -> assertThat(member.getSocialId()).isEqualTo(socialId),
+ () -> assertThat(member.getEmail()).isEqualTo(email),
() -> assertThat(member.getNickname()).isNotNull(),
() -> assertThat(member.getMobilePhoneNumber()).isNull(),
- () -> assertThat(member.getInterestArea()).isNull(),
() -> assertThat(member.getOptInMarketing()).isNull()
);
}
@@ -35,11 +38,25 @@ class CreateOauthMember {
@Test
void socialId๊ฐ_null์ด๋ฉด_์์ธ๊ฐ_๋ฐ์ํ๋ค() {
String socialId = null;
+ String email = "test@example.com";
- BusinessException exception = assertThrows(BusinessException.class, () -> new Member(socialId, "nickname"));
+ BusinessException exception = assertThrows(BusinessException.class,
+ () -> new Member(socialId, email, "nickname"));
assertThat(exception.getErrorCode()).isEqualTo(BusinessErrorCode.INVALID_SOCIAL_ID);
}
+
+ @ParameterizedTest
+ @NullAndEmptySource
+ @ValueSource(strings = {"abc.com", "invalid-email", "test@.com", "@example.com"})
+ void email์ด_null์ด๊ฑฐ๋_์ ํจํ์ง_์์ผ๋ฉด_์์ธ๊ฐ_๋ฐ์ํ๋ค(String invalidEmail) {
+ String socialId = "oauth-user-id";
+
+ BusinessException exception = assertThrows(BusinessException.class,
+ () -> new Member(socialId, invalidEmail, "nickname"));
+
+ assertThat(exception.getErrorCode()).isEqualTo(BusinessErrorCode.INVALID_EMAIL);
+ }
}
@Nested
@@ -49,18 +66,18 @@ class CreateMemberTest {
@Test
void ๋ชจ๋ _์ ๋ณด๊ฐ_์ ์์ ์ผ_๋_ํ์์_์์ฑํ๋ค() {
String socialId = "test-social-id-123";
+ String email = "test@example.com";
String nickname = "๋ง์๋๋
์๋ค-32";
String mobilePhoneNumber = "01012345678";
- String interestArea = "๊ฐ๋จ๊ตฌ";
Boolean optInMarketing = true;
- Member member = new Member(socialId, nickname, mobilePhoneNumber, interestArea, optInMarketing);
+ Member member = new Member(socialId, email, nickname, mobilePhoneNumber, optInMarketing);
assertAll(
() -> assertThat(member.getSocialId()).isEqualTo(socialId),
+ () -> assertThat(member.getEmail()).isEqualTo(email),
() -> assertThat(member.getNickname()).isEqualTo(nickname),
() -> assertThat(member.getMobilePhoneNumber().getValue()).isEqualTo(mobilePhoneNumber),
- () -> assertThat(member.getInterestArea()).isEqualTo(InterestArea.GANGNAM),
() -> assertThat(member.isOptInMarketing()).isTrue()
);
}
@@ -68,12 +85,12 @@ class CreateMemberTest {
@Test
void ์ ํ์ _ํ๋๊ฐ_null์ด์ด๋_๋ฉค๋ฒ๋ฅผ_์์ฑํ๋ค() {
String socialId = "test-social-id-123";
+ String email = "test@example.com";
String nickname = null;
String mobilePhoneNumber = null;
- String interestArea = "๊ฐ๋จ๊ตฌ";
Boolean optInMarketing = false;
- Member member = new Member(socialId, nickname, mobilePhoneNumber, interestArea, optInMarketing);
+ Member member = new Member(socialId, email, nickname, mobilePhoneNumber, optInMarketing);
assertAll(
() -> assertThat(member.getNickname()).isNull(),
@@ -85,30 +102,16 @@ class CreateMemberTest {
@Test
void ๊ฐ์
_์๋ฃ_์_๋ง์ผํ
_๋์_์ฌ๋ถ๊ฐ_null์ด๋ฉด_์์ธ๋ฅผ_๋์ง๋ค() {
String socialId = "test-social-id-123";
+ String email = "test@example.com";
String nickname = "๋ง์๋๋
์๋ค-32";
String mobilePhoneNumber = "01012345678";
- String interestArea = "๊ฐ๋จ๊ตฌ";
Boolean optInMarketing = null;
BusinessException exception = assertThrows(BusinessException.class,
- () -> new Member(socialId, nickname, mobilePhoneNumber, interestArea, optInMarketing));
+ () -> new Member(socialId, email, nickname, mobilePhoneNumber, optInMarketing));
assertThat(exception.getErrorCode()).isEqualTo(BusinessErrorCode.INVALID_MARKETING_CONSENT);
}
-
- @Test
- void ์ ํจํ์ง_์์_๊ด์ฌ_์ง์ญ์ผ๋ก_์์ฑํ๋ฉด_์์ธ๋ฅผ_๋์ง๋ค() {
- String socialId = "test-social-id-123";
- String nickname = "๋ง์๋๋
์๋ค-32";
- String mobilePhoneNumber = "01012345678";
- String interestArea = "๋ถ์ฐ";
- Boolean optInMarketing = true;
-
- BusinessException exception = assertThrows(BusinessException.class,
- () -> new Member(socialId, nickname, mobilePhoneNumber, interestArea, optInMarketing));
-
- assertThat(exception.getErrorCode()).isEqualTo(BusinessErrorCode.INVALID_INTEREST_AREA);
- }
}
@Nested
@@ -116,15 +119,14 @@ class UpdateMember {
@Test
void ํ์_์ ๋ณด๋ฅผ_์ ์์ ์ผ๋ก_์์ ํ๋ค() {
- Member member = new Member("social-id", "nickname");
- Member updatedMember = new Member("new-nickname", "01012345678", "๊ฐ๋จ๊ตฌ", true);
+ Member member = new Member("social-id", "abc@example.com", "nickname");
+ Member updatedMember = new Member("new-nickname", "01012345678", true);
member.update(updatedMember);
assertAll(
() -> assertThat(member.getNickname()).isEqualTo("new-nickname"),
() -> assertThat(member.getPhoneNumber()).isEqualTo("01012345678"),
- () -> assertThat(member.getInterestArea()).isEqualTo(InterestArea.GANGNAM),
() -> assertThat(member.isOptInMarketing()).isTrue()
);
}
@@ -135,7 +137,7 @@ class IsSameNicknameTest {
@Test
void ๋์ผํ_๋๋ค์์_๋น๊ตํ๋ฉด_true๋ฅผ_๋ฐํํ๋ค() {
- Member member = new Member("social-id", "nickname");
+ Member member = new Member("social-id", "abc@example.com", "nickname");
boolean result = member.isSameNickname("nickname");
@@ -144,7 +146,7 @@ class IsSameNicknameTest {
@Test
void ๋ค๋ฅธ_๋๋ค์์_๋น๊ตํ๋ฉด_false๋ฅผ_๋ฐํํ๋ค() {
- Member member = new Member("social-id", "nickname");
+ Member member = new Member("social-id", "abc@example.com", "nickname");
boolean result = member.isSameNickname("different-nickname");
@@ -157,7 +159,7 @@ class IsSameMobilePhoneNumberTest {
@Test
void ๋์ผํ_์ ํ๋ฒํธ๋ฅผ_๋น๊ตํ๋ฉด_true๋ฅผ_๋ฐํํ๋ค() {
- Member member = new Member("social-id", "nickname", "01012345678", "๊ฐ๋จ๊ตฌ", true);
+ Member member = new Member("social-id", "abc@example.com", "nickname", "01012345678", true);
boolean result = member.isSameMobilePhoneNumber("01012345678");
@@ -166,7 +168,7 @@ class IsSameMobilePhoneNumberTest {
@Test
void ๋ค๋ฅธ_์ ํ๋ฒํธ๋ฅผ_๋น๊ตํ๋ฉด_false๋ฅผ_๋ฐํํ๋ค() {
- Member member = new Member("social-id", "nickname", "01012345678", "๊ฐ๋จ๊ตฌ", true);
+ Member member = new Member("social-id", "abc@example.com", "nickname", "01012345678", true);
boolean result = member.isSameMobilePhoneNumber("01087654321");
diff --git a/src/test/java/timeeat/domain/member/MobilePhoneNumberTest.java b/src/test/java/eatda/domain/member/MobilePhoneNumberTest.java
similarity index 94%
rename from src/test/java/timeeat/domain/member/MobilePhoneNumberTest.java
rename to src/test/java/eatda/domain/member/MobilePhoneNumberTest.java
index aa5fbf7f..01525ca4 100644
--- a/src/test/java/timeeat/domain/member/MobilePhoneNumberTest.java
+++ b/src/test/java/eatda/domain/member/MobilePhoneNumberTest.java
@@ -1,15 +1,15 @@
-package timeeat.domain.member;
+package eatda.domain.member;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
+import eatda.exception.BusinessErrorCode;
+import eatda.exception.BusinessException;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
-import timeeat.exception.BusinessErrorCode;
-import timeeat.exception.BusinessException;
class MobilePhoneNumberTest {
diff --git a/src/test/java/eatda/domain/store/CheerTest.java b/src/test/java/eatda/domain/store/CheerTest.java
new file mode 100644
index 00000000..261ffd54
--- /dev/null
+++ b/src/test/java/eatda/domain/store/CheerTest.java
@@ -0,0 +1,60 @@
+package eatda.domain.store;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import eatda.domain.member.Member;
+import eatda.exception.BusinessErrorCode;
+import eatda.exception.BusinessException;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.NullAndEmptySource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+class CheerTest {
+
+ private static final Member DEFAULT_MEMBER = new Member("socialId", "email@kakao.com", "nickname");
+ private static final Store DEFAULT_STORE = Store.builder()
+ .kakaoId("1234567890")
+ .category(StoreCategory.CAFE)
+ .phoneNumber("02-1234-5678")
+ .name("Test Store")
+ .placeUrl("https://place.kakao.com/1234567890")
+ .roadAddress("์์ธ์ ์ฑ๋ถ๊ตฌ ๋ํ๋ก 1๊ธธ 1")
+ .lotNumberAddress("์์ธ์ ์ฑ๋ถ๊ตฌ ๋์ ๋ 1-1")
+ .latitude(37.5665)
+ .longitude(126.978)
+ .build();
+
+ @Nested
+ class Validate {
+
+ @ParameterizedTest
+ @NullAndEmptySource
+ void ์ค๋ช
์ด_๋น์ด์์ผ๋ฉด_์๋๋ค(String description) {
+ BusinessException exception = assertThrows(BusinessException.class, () -> {
+ new Cheer(DEFAULT_MEMBER, DEFAULT_STORE, description, "imageKey");
+ });
+
+ assertThat(exception.getErrorCode()).isEqualTo(BusinessErrorCode.INVALID_CHEER_DESCRIPTION);
+ }
+
+ @Test
+ void ์ด๋ฏธ์ง_ํค๋_null์ด_๊ฐ๋ฅํ๋ค() {
+ assertThatCode(() -> new Cheer(DEFAULT_MEMBER, DEFAULT_STORE, "Great store!", null))
+ .doesNotThrowAnyException();
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"", " ", "\t\n"})
+ void ์ด๋ฏธ์ง_ํค๋_๋น์ด์์ผ๋ฉด_์๋๋ค(String imageKey) {
+ BusinessException exception = assertThrows(BusinessException.class, () -> {
+ new Cheer(DEFAULT_MEMBER, DEFAULT_STORE, "Great store!", imageKey);
+ });
+
+ assertThat(exception.getErrorCode()).isEqualTo(BusinessErrorCode.INVALID_CHEER_IMAGE_KEY);
+ }
+ }
+}
diff --git a/src/test/java/timeeat/domain/store/CoordinatesTest.java b/src/test/java/eatda/domain/store/CoordinatesTest.java
similarity index 95%
rename from src/test/java/timeeat/domain/store/CoordinatesTest.java
rename to src/test/java/eatda/domain/store/CoordinatesTest.java
index 092dd42b..8e7f84eb 100644
--- a/src/test/java/timeeat/domain/store/CoordinatesTest.java
+++ b/src/test/java/eatda/domain/store/CoordinatesTest.java
@@ -1,13 +1,13 @@
-package timeeat.domain.store;
+package eatda.domain.store;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
+import eatda.exception.BusinessErrorCode;
+import eatda.exception.BusinessException;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
-import timeeat.exception.BusinessErrorCode;
-import timeeat.exception.BusinessException;
class CoordinatesTest {
diff --git a/src/test/java/timeeat/enums/StoreCategoryTest.java b/src/test/java/eatda/domain/store/StoreCategoryTest.java
similarity index 89%
rename from src/test/java/timeeat/enums/StoreCategoryTest.java
rename to src/test/java/eatda/domain/store/StoreCategoryTest.java
index ba6bbc4d..35cff0b2 100644
--- a/src/test/java/timeeat/enums/StoreCategoryTest.java
+++ b/src/test/java/eatda/domain/store/StoreCategoryTest.java
@@ -1,13 +1,13 @@
-package timeeat.enums;
+package eatda.domain.store;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
+import eatda.exception.BusinessErrorCode;
+import eatda.exception.BusinessException;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
-import timeeat.exception.BusinessErrorCode;
-import timeeat.exception.BusinessException;
class StoreCategoryTest {
diff --git a/src/test/java/eatda/domain/store/StoreTest.java b/src/test/java/eatda/domain/store/StoreTest.java
new file mode 100644
index 00000000..1fe2d6ae
--- /dev/null
+++ b/src/test/java/eatda/domain/store/StoreTest.java
@@ -0,0 +1,50 @@
+package eatda.domain.store;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+class StoreTest {
+
+ private static final Store.StoreBuilder DEFAULT_BUILDER = Store.builder()
+ .kakaoId("123456789")
+ .category(StoreCategory.OTHER)
+ .phoneNumber("010-1234-5678")
+ .name("๊ฐ๊ฒ ์ด๋ฆ")
+ .placeUrl("https://place.kakao.com/123456789")
+ .roadAddress("")
+ .lotNumberAddress("์์ธํน๋ณ์ ๊ฐ๋จ๊ตฌ ์ญ์ผ๋ 123-45")
+ .latitude(37.5665)
+ .longitude(126.978);
+
+ @Nested
+ class GetAddressDistrict {
+
+ @Test
+ void ์ฃผ์_๊ตฌ_์ ๋ณด๋ฅผ_์ง๋ฒ_์ฃผ์์์_๋ฐํํ๋ค() {
+ Store store = DEFAULT_BUILDER
+ .lotNumberAddress("์์ธํน๋ณ์ ์ฑ๋ถ๊ตฌ ์๊ด๋ 123-45")
+ .build();
+
+ String actual = store.getAddressDistrict();
+
+ assertThat(actual).isEqualTo("์ฑ๋ถ๊ตฌ");
+ }
+ }
+
+ @Nested
+ class GetAddressNeighborhood {
+
+ @Test
+ void ์ฃผ์_๋_์ ๋ณด๋ฅผ_์ง๋ฒ_์ฃผ์์์_๋ฐํํ๋ค() {
+ Store store = DEFAULT_BUILDER
+ .lotNumberAddress("์์ธํน๋ณ์ ์ฑ๋ถ๊ตฌ ์๊ด๋ 123-45")
+ .build();
+
+ String actual = store.getAddressNeighborhood();
+
+ assertThat(actual).isEqualTo("์๊ด๋");
+ }
+ }
+}
diff --git a/src/test/java/eatda/domain/story/StoryTest.java b/src/test/java/eatda/domain/story/StoryTest.java
new file mode 100644
index 00000000..572f0601
--- /dev/null
+++ b/src/test/java/eatda/domain/story/StoryTest.java
@@ -0,0 +1,160 @@
+package eatda.domain.story;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import eatda.domain.member.Member;
+import eatda.exception.BusinessErrorCode;
+import eatda.exception.BusinessException;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+class StoryTest {
+
+ private static final Member MEMBER = Mockito.mock(Member.class);
+
+ @Nested
+ class RegisterStory {
+
+ @Test
+ void ์คํ ๋ฆฌ๋ฅผ_์ ์์ ์ผ๋ก_์์ฑํ๋ค() {
+ Story story = Story.builder()
+ .member(MEMBER)
+ .storeKakaoId("123")
+ .storeName("๊ณฑ์ฐฝ์ง")
+ .storeAddress("์์ธ์ ์ฑ๋๊ตฌ")
+ .storeCategory("ํ์")
+ .description("์ ๋ง ๋ง์์ด์")
+ .imageKey("story/image.jpg")
+ .build();
+
+ assertThat(story.getStoreName()).isEqualTo("๊ณฑ์ฐฝ์ง");
+ assertThat(story.getDescription()).isEqualTo("์ ๋ง ๋ง์์ด์");
+ }
+ }
+
+ @Nested
+ class ValidateMember {
+
+ @Test
+ void ํ์์ด_null์ด๋ฉด_์์ธ๊ฐ_๋ฐ์ํ๋ค() {
+ assertThatThrownBy(() ->
+ Story.builder()
+ .member(null)
+ .storeKakaoId("123")
+ .storeName("๊ณฑ์ฐฝ์ง")
+ .storeAddress("์์ธ์ ์ฑ๋๊ตฌ")
+ .storeCategory("ํ์")
+ .description("์ ๋ง ๋ง์์ด์")
+ .imageKey("story/image.jpg")
+ .build()
+ ).isInstanceOf(BusinessException.class)
+ .hasMessage(BusinessErrorCode.STORY_MEMBER_REQUIRED.getMessage());
+ }
+ }
+
+ @Nested
+ class ValidateStore {
+
+ @Test
+ void ๊ฐ๊ฒ_ID๊ฐ_๋น์ด์์ผ๋ฉด_์์ธ๊ฐ_๋ฐ์ํ๋ค() {
+ assertThatThrownBy(() ->
+ Story.builder()
+ .member(MEMBER)
+ .storeKakaoId(" ")
+ .storeName("๊ณฑ์ฐฝ์ง")
+ .storeAddress("์์ธ์")
+ .storeCategory("ํ์")
+ .description("๋ง์์")
+ .imageKey("story/image.jpg")
+ .build()
+ ).isInstanceOf(BusinessException.class)
+ .hasMessage(BusinessErrorCode.INVALID_STORE_KAKAO_ID.getMessage());
+ }
+
+ @Test
+ void ๊ฐ๊ฒ_์ด๋ฆ์ด_๋น์ด์์ผ๋ฉด_์์ธ๊ฐ_๋ฐ์ํ๋ค() {
+ assertThatThrownBy(() ->
+ Story.builder()
+ .member(MEMBER)
+ .storeKakaoId("123")
+ .storeName(" ")
+ .storeAddress("์์ธ์")
+ .storeCategory("ํ์")
+ .description("๋ง์์")
+ .imageKey("story/image.jpg")
+ .build()
+ ).isInstanceOf(BusinessException.class)
+ .hasMessage(BusinessErrorCode.INVALID_STORE_NAME.getMessage());
+ }
+
+ @Test
+ void ๊ฐ๊ฒ_์ฃผ์๊ฐ_๋น์ด์์ผ๋ฉด_์์ธ๊ฐ_๋ฐ์ํ๋ค() {
+ assertThatThrownBy(() ->
+ Story.builder()
+ .member(MEMBER)
+ .storeKakaoId("123")
+ .storeName("๊ณฑ์ฐฝ์ง")
+ .storeAddress(" ")
+ .storeCategory("ํ์")
+ .description("๋ง์์")
+ .imageKey("story/image.jpg")
+ .build()
+ ).isInstanceOf(BusinessException.class)
+ .hasMessage(BusinessErrorCode.INVALID_STORE_ADDRESS.getMessage());
+ }
+
+ @Test
+ void ๊ฐ๊ฒ_์นดํ
๊ณ ๋ฆฌ๊ฐ_๋น์ด์์ผ๋ฉด_์์ธ๊ฐ_๋ฐ์ํ๋ค() {
+ assertThatThrownBy(() ->
+ Story.builder()
+ .member(MEMBER)
+ .storeKakaoId("123")
+ .storeName("๊ณฑ์ฐฝ์ง")
+ .storeAddress("์์ธ์")
+ .storeCategory(" ")
+ .description("๋ง์์")
+ .imageKey("story/image.jpg")
+ .build()
+ ).isInstanceOf(BusinessException.class)
+ .hasMessage(BusinessErrorCode.INVALID_STORE_CATEGORY.getMessage());
+ }
+ }
+
+ @Nested
+ class ValidateStory {
+
+ @Test
+ void ์ค๋ช
์ด_๋น์ด์์ผ๋ฉด_์์ธ๊ฐ_๋ฐ์ํ๋ค() {
+ assertThatThrownBy(() ->
+ Story.builder()
+ .member(MEMBER)
+ .storeKakaoId("123")
+ .storeName("๊ณฑ์ฐฝ์ง")
+ .storeAddress("์์ธ์")
+ .storeCategory("ํ์")
+ .description(" ")
+ .imageKey("story/image.jpg")
+ .build()
+ ).isInstanceOf(BusinessException.class)
+ .hasMessage(BusinessErrorCode.INVALID_STORY_DESCRIPTION.getMessage());
+ }
+
+ @Test
+ void ์ด๋ฏธ์ง๊ฐ_๋น์ด์์ผ๋ฉด_์์ธ๊ฐ_๋ฐ์ํ๋ค() {
+ assertThatThrownBy(() ->
+ Story.builder()
+ .member(MEMBER)
+ .storeKakaoId("123")
+ .storeName("๊ณฑ์ฐฝ์ง")
+ .storeAddress("์์ธ์")
+ .storeCategory("ํ์")
+ .description("๋ง์์")
+ .imageKey(" ")
+ .build()
+ ).isInstanceOf(BusinessException.class)
+ .hasMessage(BusinessErrorCode.INVALID_STORY_IMAGE_KEY.getMessage());
+ }
+ }
+}
diff --git a/src/test/java/eatda/fixture/CheerGenerator.java b/src/test/java/eatda/fixture/CheerGenerator.java
new file mode 100644
index 00000000..1a3af6a7
--- /dev/null
+++ b/src/test/java/eatda/fixture/CheerGenerator.java
@@ -0,0 +1,34 @@
+package eatda.fixture;
+
+import eatda.domain.member.Member;
+import eatda.domain.store.Cheer;
+import eatda.domain.store.Store;
+import eatda.repository.store.CheerRepository;
+import org.springframework.stereotype.Component;
+
+@Component
+public class CheerGenerator {
+
+ private static final String DEFAULT_IMAGE_KEY = "default-image-key";
+ private static final String DEFAULT_DESCRIPTION = "์์ํฉ๋๋ค!";
+
+ private final CheerRepository cheerRepository;
+
+ public CheerGenerator(CheerRepository cheerRepository) {
+ this.cheerRepository = cheerRepository;
+ }
+
+ public Cheer generateAdmin(Member member, Store store) {
+ Cheer cheer = new Cheer(member, store, DEFAULT_DESCRIPTION, DEFAULT_IMAGE_KEY, true);
+ return cheerRepository.save(cheer);
+ }
+
+ public Cheer generateCommon(Member member, Store store) {
+ return generateCommon(member, store, DEFAULT_IMAGE_KEY);
+ }
+
+ public Cheer generateCommon(Member member, Store store, String imageKey) {
+ Cheer cheer = new Cheer(member, store, DEFAULT_DESCRIPTION, imageKey, false);
+ return cheerRepository.save(cheer);
+ }
+}
diff --git a/src/test/java/eatda/fixture/MemberGenerator.java b/src/test/java/eatda/fixture/MemberGenerator.java
new file mode 100644
index 00000000..be21f321
--- /dev/null
+++ b/src/test/java/eatda/fixture/MemberGenerator.java
@@ -0,0 +1,38 @@
+package eatda.fixture;
+
+import eatda.domain.member.Member;
+import eatda.repository.member.MemberRepository;
+import org.springframework.stereotype.Component;
+
+@Component
+public class MemberGenerator {
+
+ private static final String DEFAULT_NICKNAME = "nickname";
+ private static final String DEFAULT_EMAIL = "generatorEmail@example.com";
+
+ private final MemberRepository memberRepository;
+
+ public MemberGenerator(MemberRepository memberRepository) {
+ this.memberRepository = memberRepository;
+ }
+
+ public Member generate(String socialId) {
+ return memberRepository.save(new Member(socialId, DEFAULT_EMAIL, DEFAULT_NICKNAME));
+ }
+
+ public Member generateByEmail(String socialId, String email) {
+ return memberRepository.save(new Member(socialId, email, DEFAULT_NICKNAME));
+ }
+
+ public Member generateByNickname(String socialId, String nickname) {
+ return memberRepository.save(new Member(socialId, DEFAULT_EMAIL, nickname));
+ }
+
+ public Member generate(String socialId, String email, String nickname) {
+ return memberRepository.save(new Member(socialId, email, nickname));
+ }
+
+ public Member generateRegisteredMember(String nickname, String email, String socialId, String phoneNumber) {
+ return memberRepository.save(new Member(socialId, email, nickname, phoneNumber, true));
+ }
+}
diff --git a/src/test/java/eatda/fixture/StoreGenerator.java b/src/test/java/eatda/fixture/StoreGenerator.java
new file mode 100644
index 00000000..405d8c8a
--- /dev/null
+++ b/src/test/java/eatda/fixture/StoreGenerator.java
@@ -0,0 +1,40 @@
+package eatda.fixture;
+
+import eatda.domain.store.Store;
+import eatda.domain.store.StoreCategory;
+import eatda.repository.store.StoreRepository;
+import org.springframework.stereotype.Component;
+
+@Component
+public class StoreGenerator {
+
+ private static final StoreCategory DEFAULT_CATEGORY = StoreCategory.OTHER;
+ private static final String DEFAULT_PHONE_NUMBER = "010-1234-5678";
+ private static final String DEFAULT_NAME = "๊ฐ๊ฒ ์ด๋ฆ";
+ private static final String DEFAULT_PLACE_URL = "https://place.kakao.com/123456789";
+ private static final String DEFAULT_ROAD_ADDRESS = "";
+
+ private static final double DEFAULT_LATITUDE = 37.5665; // Default latitude for Seoul
+ private static final double DEFAULT_LONGITUDE = 126.978; // Default longitude for Seoul
+
+ private final StoreRepository storeRepository;
+
+ public StoreGenerator(StoreRepository storeRepository) {
+ this.storeRepository = storeRepository;
+ }
+
+ public Store generate(String kakaoId, String lotNumberAddress) {
+ Store store = Store.builder()
+ .kakaoId(kakaoId)
+ .category(DEFAULT_CATEGORY)
+ .phoneNumber(DEFAULT_PHONE_NUMBER)
+ .name(DEFAULT_NAME)
+ .placeUrl(DEFAULT_PLACE_URL)
+ .roadAddress(DEFAULT_ROAD_ADDRESS)
+ .lotNumberAddress(lotNumberAddress)
+ .latitude(DEFAULT_LATITUDE)
+ .longitude(DEFAULT_LONGITUDE)
+ .build();
+ return storeRepository.save(store);
+ }
+}
diff --git a/src/test/java/eatda/repository/BaseRepositoryTest.java b/src/test/java/eatda/repository/BaseRepositoryTest.java
new file mode 100644
index 00000000..2fd33200
--- /dev/null
+++ b/src/test/java/eatda/repository/BaseRepositoryTest.java
@@ -0,0 +1,38 @@
+package eatda.repository;
+
+import eatda.fixture.CheerGenerator;
+import eatda.fixture.MemberGenerator;
+import eatda.fixture.StoreGenerator;
+import eatda.repository.member.MemberRepository;
+import eatda.repository.store.CheerRepository;
+import eatda.repository.store.StoreRepository;
+import eatda.repository.story.StoryRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+import org.springframework.context.annotation.Import;
+
+@Import({MemberGenerator.class, StoreGenerator.class, CheerGenerator.class})
+@DataJpaTest
+public abstract class BaseRepositoryTest {
+
+ @Autowired
+ protected MemberGenerator memberGenerator;
+
+ @Autowired
+ protected StoreGenerator storeGenerator;
+
+ @Autowired
+ protected CheerGenerator cheerGenerator;
+
+ @Autowired
+ protected MemberRepository memberRepository;
+
+ @Autowired
+ protected StoreRepository storeRepository;
+
+ @Autowired
+ protected CheerRepository cheerRepository;
+
+ @Autowired
+ protected StoryRepository storyRepository;
+}
diff --git a/src/test/java/eatda/repository/DatabaseSchemaTest.java b/src/test/java/eatda/repository/DatabaseSchemaTest.java
new file mode 100644
index 00000000..f6cee556
--- /dev/null
+++ b/src/test/java/eatda/repository/DatabaseSchemaTest.java
@@ -0,0 +1,67 @@
+package eatda.repository;
+
+import static org.assertj.core.api.Assertions.assertThatCode;
+
+import org.flywaydb.core.Flyway;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.TestPropertySource;
+
+class DatabaseSchemaTest {
+
+ @Nested
+ @ActiveProfiles({"test", "flyway"})
+ @SpringBootTest(webEnvironment = WebEnvironment.NONE)
+ @TestPropertySource(properties = {
+ "spring.datasource.url=jdbc:h2:mem:flyway:local;MODE=MySQL",
+ "spring.flyway.locations=classpath:db/migration,classpath:db/seed/local"})
+ class LocalDatabaseSchemaTest {
+
+ @Autowired
+ private Flyway flyway;
+
+ @Test
+ void ๋ก์ปฌ_๋ฐ์ดํฐ๋ฒ ์ด์ค_์คํค๋ง๊ฐ_์ ์์ ์ผ๋ก_๋์ํ๋ค() {
+ assertThatCode(() -> flyway.migrate()).doesNotThrowAnyException();
+ }
+ }
+
+ @Nested
+ @ActiveProfiles({"test", "flyway"})
+ @SpringBootTest(webEnvironment = WebEnvironment.NONE)
+ @TestPropertySource(properties = {
+ "spring.datasource.url=jdbc:h2:mem:flyway:dev;MODE=MySQL",
+ "spring.flyway.locations=classpath:db/migration,classpath:db/seed/dev"})
+ class DevelopDatabaseSchemaTest {
+
+
+ @Autowired
+ private Flyway flyway;
+
+ @Test
+ void ๊ฐ๋ฐ_๋ฐ์ดํฐ๋ฒ ์ด์ค_์คํค๋ง๊ฐ_์ ์์ ์ผ๋ก_๋์ํ๋ค() {
+ assertThatCode(() -> flyway.migrate()).doesNotThrowAnyException();
+ }
+ }
+
+ @Nested
+ @ActiveProfiles({"test", "flyway"})
+ @SpringBootTest(webEnvironment = WebEnvironment.NONE)
+ @TestPropertySource(properties = {
+ "spring.datasource.url=jdbc:h2:mem:flyway:prod;MODE=MySQL",
+ "spring.flyway.locations=classpath:db/migration,classpath:db/seed/prod"})
+ class ProductionDatabaseSchemaTest {
+
+ @Autowired
+ private Flyway flyway;
+
+ @Test
+ void ์ด์_๋ฐ์ดํฐ๋ฒ ์ด์ค_์คํค๋ง๊ฐ_์ ์์ ์ผ๋ก_๋์ํ๋ค() {
+ assertThatCode(() -> flyway.migrate()).doesNotThrowAnyException();
+ }
+ }
+}
diff --git a/src/test/java/eatda/repository/store/CheerRepositoryTest.java b/src/test/java/eatda/repository/store/CheerRepositoryTest.java
new file mode 100644
index 00000000..88b8ef31
--- /dev/null
+++ b/src/test/java/eatda/repository/store/CheerRepositoryTest.java
@@ -0,0 +1,44 @@
+package eatda.repository.store;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import eatda.domain.member.Member;
+import eatda.domain.store.Store;
+import eatda.repository.BaseRepositoryTest;
+import java.util.Optional;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+class CheerRepositoryTest extends BaseRepositoryTest {
+
+ @Nested
+ class FindRecentImageKey {
+
+ @Test
+ void ์์๋ค_์ค_์ต๊ทผ_null์ด_์๋_์ด๋ฏธ์ง_ํค๋ฅผ_์กฐํํ๋ค() throws InterruptedException {
+ Member member = memberGenerator.generate("111");
+ Store store = storeGenerator.generate("๋๋ฏผ๋ฐฑ์์๋", "์์ธ ๊ฐ๋จ๊ตฌ ๋์น๋ 896-33");
+ cheerGenerator.generateCommon(member, store, "image-key-1");
+ Thread.sleep(5);
+ cheerGenerator.generateCommon(member, store, "image-key-2");
+ cheerGenerator.generateCommon(member, store, null);
+
+ Optional imageKey = cheerRepository.findRecentImageKey(store);
+
+ assertThat(imageKey).contains("image-key-2");
+ }
+
+ @Test
+ void ์์๋ค์_์ด๋ฏธ์ง๊ฐ_๋ชจ๋_๋น์ด์๋ค๋ฉด_ํด๋น_๊ฐ์ด_์๋ค() {
+ Member member = memberGenerator.generate("111");
+ Store store = storeGenerator.generate("๋๋ฏผ๋ฐฑ์์๋", "์์ธ ๊ฐ๋จ๊ตฌ ๋์น๋ 896-33");
+ cheerGenerator.generateCommon(member, store, null);
+ cheerGenerator.generateCommon(member, store, null);
+ cheerGenerator.generateCommon(member, store, null);
+
+ Optional imageKey = cheerRepository.findRecentImageKey(store);
+
+ assertThat(imageKey).isEmpty();
+ }
+ }
+}
diff --git a/src/test/java/eatda/service/BaseServiceTest.java b/src/test/java/eatda/service/BaseServiceTest.java
new file mode 100644
index 00000000..2eadb322
--- /dev/null
+++ b/src/test/java/eatda/service/BaseServiceTest.java
@@ -0,0 +1,65 @@
+package eatda.service;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doReturn;
+
+import eatda.DatabaseCleaner;
+import eatda.client.map.MapClient;
+import eatda.client.oauth.OauthClient;
+import eatda.fixture.CheerGenerator;
+import eatda.fixture.MemberGenerator;
+import eatda.fixture.StoreGenerator;
+import eatda.repository.member.MemberRepository;
+import eatda.repository.store.CheerRepository;
+import eatda.repository.store.StoreRepository;
+import eatda.service.common.ImageService;
+import org.junit.jupiter.api.BeforeEach;
+import eatda.repository.story.StoryRepository;
+import eatda.service.common.ImageService;
+import eatda.service.store.StoreService;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.bean.override.mockito.MockitoBean;
+
+@ExtendWith(DatabaseCleaner.class)
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
+public abstract class BaseServiceTest {
+
+ private static final String MOCKED_IMAGE_KEY = "mocked-image-path";
+ private static final String MOCKED_IMAGE_URL = "https://example.com/image.jpg";
+
+ @MockitoBean
+ protected OauthClient oauthClient;
+
+ @MockitoBean
+ protected MapClient mapClient;
+
+ @MockitoBean
+ protected ImageService imageService;
+
+ @Autowired
+ protected MemberGenerator memberGenerator;
+
+ @Autowired
+ protected StoreGenerator storeGenerator;
+
+ @Autowired
+ protected CheerGenerator cheerGenerator;
+
+ @Autowired
+ protected MemberRepository memberRepository;
+
+ @Autowired
+ protected StoreRepository storeRepository;
+
+ @Autowired
+ protected CheerRepository cheerRepository;
+
+ @BeforeEach
+ void mockingImageService() {
+ doReturn(MOCKED_IMAGE_URL).when(imageService).getPresignedUrl(anyString());
+ doReturn(MOCKED_IMAGE_KEY).when(imageService).upload(any(), any());
+ }
+}
diff --git a/src/test/java/timeeat/service/auth/AuthServiceTest.java b/src/test/java/eatda/service/auth/AuthServiceTest.java
similarity index 54%
rename from src/test/java/timeeat/service/auth/AuthServiceTest.java
rename to src/test/java/eatda/service/auth/AuthServiceTest.java
index c2d132bf..68089a84 100644
--- a/src/test/java/timeeat/service/auth/AuthServiceTest.java
+++ b/src/test/java/eatda/service/auth/AuthServiceTest.java
@@ -1,20 +1,38 @@
-package timeeat.service.auth;
+package eatda.service.auth;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertAll;
-
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doReturn;
+
+import eatda.client.oauth.OauthMemberInformation;
+import eatda.client.oauth.OauthToken;
+import eatda.controller.auth.LoginRequest;
+import eatda.controller.member.MemberResponse;
+import eatda.service.BaseServiceTest;
+import java.net.URI;
+import java.net.URISyntaxException;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
-import timeeat.controller.auth.LoginRequest;
-import timeeat.controller.member.MemberResponse;
-import timeeat.service.BaseServiceTest;
class AuthServiceTest extends BaseServiceTest {
+ private static final OauthToken DEFAULT_OAUTH_TOKEN = new OauthToken("oauth-access-token");
+ private static final OauthMemberInformation DEFAULT_OAUTH_MEMBER_INFO =
+ new OauthMemberInformation(123L, "authService@kakao.com", "nickname");
+
@Autowired
private AuthService authService;
+ @BeforeEach
+ protected final void mockingClient() throws URISyntaxException {
+ doReturn(new URI("http://localhost:8080/login/callback")).when(oauthClient).getOauthLoginUrl(anyString());
+ doReturn(DEFAULT_OAUTH_TOKEN).when(oauthClient).requestOauthToken(anyString(), anyString());
+ doReturn(DEFAULT_OAUTH_MEMBER_INFO).when(oauthClient).requestMemberInformation(DEFAULT_OAUTH_TOKEN);
+ }
+
@Nested
class Login {
@@ -28,7 +46,6 @@ class Login {
() -> assertThat(response.isSignUp()).isTrue(),
() -> assertThat(response.nickname()).isNotNull(),
() -> assertThat(response.phoneNumber()).isNull(),
- () -> assertThat(response.interestArea()).isNull(),
() -> assertThat(response.optInMarketing()).isNull()
);
}
diff --git a/src/test/java/eatda/service/common/ImageServiceTest.java b/src/test/java/eatda/service/common/ImageServiceTest.java
new file mode 100644
index 00000000..ec551d27
--- /dev/null
+++ b/src/test/java/eatda/service/common/ImageServiceTest.java
@@ -0,0 +1,135 @@
+package eatda.service.common;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertAll;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import eatda.exception.BusinessErrorCode;
+import eatda.exception.BusinessException;
+import java.net.URL;
+import java.time.Duration;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.mock.web.MockMultipartFile;
+import software.amazon.awssdk.core.exception.SdkClientException;
+import software.amazon.awssdk.core.sync.RequestBody;
+import software.amazon.awssdk.services.s3.S3Client;
+import software.amazon.awssdk.services.s3.model.PutObjectRequest;
+import software.amazon.awssdk.services.s3.presigner.S3Presigner;
+import software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest;
+import software.amazon.awssdk.services.s3.presigner.model.PresignedGetObjectRequest;
+
+@ExtendWith(MockitoExtension.class)
+class ImageServiceTest {
+
+ private static final String TEST_BUCKET = "test-bucket";
+
+ @Mock
+ private S3Client s3Client;
+
+ @Mock
+ private S3Presigner s3Presigner;
+ private ImageService imageService;
+
+ @BeforeEach
+ void setUp() {
+ imageService = new ImageService(s3Client, TEST_BUCKET, s3Presigner);
+ }
+
+ @Nested
+ class FileUpload {
+
+ @ParameterizedTest
+ @EnumSource(ImageDomain.class)
+ void ํ์ฉ๋_์ด๋ฏธ์ง_ํ์
์ด๋ฉด_์ ์์ ์ผ๋ก_์
๋ก๋๋๊ณ _์์ฑ๋_Key๋ฅผ_๋ฐํํ๋ค(ImageDomain imageDomain) {
+ String originalFilename = "test-image.jpg";
+ String contentType = "image/jpeg";
+
+ MockMultipartFile file = new MockMultipartFile(
+ "image", originalFilename, contentType, "image-content".getBytes()
+ );
+
+ String key = imageService.upload(file, imageDomain);
+
+ ArgumentCaptor putObjectRequestCaptor = ArgumentCaptor.forClass(PutObjectRequest.class);
+ verify(s3Client).putObject(putObjectRequestCaptor.capture(), any(RequestBody.class));
+ PutObjectRequest capturedRequest = putObjectRequestCaptor.getValue();
+
+ String expectedPattern = imageDomain.getName() + "/[a-f0-9\\-]{36}\\.jpg";
+
+ assertAll(
+ () -> assertThat(key).matches(expectedPattern),
+ () -> assertThat(capturedRequest.key()).isEqualTo(key),
+ () -> assertThat(capturedRequest.bucket()).isEqualTo(TEST_BUCKET),
+ () -> assertThat(capturedRequest.contentType()).isEqualTo(contentType)
+ );
+ }
+
+ @Test
+ void ํ์ฉ๋์ง_์์_ํ์ผ_ํ์
์ด๋ฉด_BusinessException์_๋์ง๋ค() {
+ MockMultipartFile file = new MockMultipartFile(
+ "file", "test.txt", "text/plain", "file-content".getBytes()
+ );
+
+ BusinessException exception = assertThrows(BusinessException.class,
+ () -> imageService.upload(file, ImageDomain.STORY));
+
+ assertThat(exception.getErrorCode()).isEqualTo(BusinessErrorCode.INVALID_IMAGE_TYPE);
+ }
+ }
+
+ @Nested
+ class GeneratePresignedUrl {
+
+ @Test
+ void ์ ํจํ_key๋ก_์์ฒญ_์_Presigned_URL์_์ฑ๊ณต์ ์ผ๋ก_๋ฐํํ๋ค() throws Exception {
+ String key = "stores/image.jpg";
+ String expectedUrlString = "https://example.com/presigned-url-for-image.jpg";
+ URL expectedUrl = new URL(expectedUrlString);
+
+ PresignedGetObjectRequest presignedRequestResult = mock(PresignedGetObjectRequest.class);
+
+ when(presignedRequestResult.url()).thenReturn(expectedUrl);
+ when(s3Presigner.presignGetObject(any(GetObjectPresignRequest.class)))
+ .thenReturn(presignedRequestResult);
+
+ String presignedUrl = imageService.getPresignedUrl(key);
+
+ ArgumentCaptor presignRequestCaptor =
+ ArgumentCaptor.forClass(GetObjectPresignRequest.class);
+ verify(s3Presigner).presignGetObject(presignRequestCaptor.capture());
+ GetObjectPresignRequest capturedPresignRequest = presignRequestCaptor.getValue();
+
+ assertAll(
+ () -> assertThat(presignedUrl).isEqualTo(expectedUrlString),
+ () -> assertThat(capturedPresignRequest.getObjectRequest().key()).isEqualTo(key),
+ () -> assertThat(capturedPresignRequest.getObjectRequest().bucket()).isEqualTo(TEST_BUCKET),
+ () -> assertThat(capturedPresignRequest.signatureDuration()).isEqualTo(Duration.ofMinutes(30))
+ );
+ }
+
+ @Test
+ void Presigner๊ฐ_์์ธ๋ฅผ_๋์ง๋ฉด_BusinessException์ผ๋ก_์ ํํ์ฌ_๋์ง๋ค() {
+ String key = "stores/image.jpg";
+
+ when(s3Presigner.presignGetObject(any(GetObjectPresignRequest.class)))
+ .thenThrow(SdkClientException.create("AWS SDK ํต์ ์คํจ"));
+
+ BusinessException exception = assertThrows(BusinessException.class,
+ () -> imageService.getPresignedUrl(key));
+
+ assertThat(exception.getErrorCode()).isEqualTo(BusinessErrorCode.PRESIGNED_URL_GENERATION_FAILED);
+ }
+ }
+}
diff --git a/src/test/java/timeeat/service/service/MemberServiceTest.java b/src/test/java/eatda/service/member/MemberServiceTest.java
similarity index 66%
rename from src/test/java/timeeat/service/service/MemberServiceTest.java
rename to src/test/java/eatda/service/member/MemberServiceTest.java
index 1c01be9e..f94da0c9 100644
--- a/src/test/java/timeeat/service/service/MemberServiceTest.java
+++ b/src/test/java/eatda/service/member/MemberServiceTest.java
@@ -1,32 +1,61 @@
-package timeeat.service.service;
+package eatda.service.member;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertThrows;
+import eatda.controller.member.MemberResponse;
+import eatda.controller.member.MemberUpdateRequest;
+import eatda.domain.member.Member;
+import eatda.exception.BusinessErrorCode;
+import eatda.exception.BusinessException;
+import eatda.service.BaseServiceTest;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
-import timeeat.controller.member.MemberResponse;
-import timeeat.controller.member.MemberUpdateRequest;
-import timeeat.domain.member.Member;
-import timeeat.exception.BusinessErrorCode;
-import timeeat.exception.BusinessException;
-import timeeat.service.BaseServiceTest;
class MemberServiceTest extends BaseServiceTest {
@Autowired
private MemberService memberService;
+ @Nested
+ class GetMember {
+
+ @Test
+ void ํ์_์ ๋ณด๋ฅผ_์กฐํํ _์_์๋ค() {
+ Member member = memberGenerator.generateRegisteredMember("123", "abc@kakao.com", "nickname", "01012345678");
+
+ MemberResponse response = memberService.getMember(member.getId());
+
+ assertAll(
+ () -> assertThat(response.id()).isEqualTo(member.getId()),
+ () -> assertThat(response.nickname()).isEqualTo(member.getNickname()),
+ () -> assertThat(response.phoneNumber()).isEqualTo(member.getPhoneNumber()),
+ () -> assertThat(response.optInMarketing()).isEqualTo(member.isOptInMarketing()),
+ () -> assertThat(response.isSignUp()).isFalse()
+ );
+ }
+
+ @Test
+ void ์กด์ฌํ์ง_์๋_ํ์์_์ ๋ณด๋ฅผ_์กฐํํ๋ฉด_์์ธ๊ฐ_๋ฐ์ํ๋ค() {
+ long nonExistentMemberId = 999L;
+
+ BusinessException exception = assertThrows(BusinessException.class,
+ () -> memberService.getMember(nonExistentMemberId));
+
+ assertThat(exception.getErrorCode()).isEqualTo(BusinessErrorCode.INVALID_MEMBER_ID);
+ }
+ }
+
@Nested
class ValidateNickname {
@Test
void ์ค๋ณต๋์ง_์์_๋๋ค์์ด๋ฉด_์์ธ๊ฐ_๋ฐ์ํ์ง_์๋๋ค() {
- memberGenerator.generate("123", "nickname");
- Member member = memberGenerator.generate("456", "unique-nickname");
+ memberGenerator.generate("123", "abc@kakao.com", "nickname");
+ Member member = memberGenerator.generate("456", "def@kakao.com", "unique-nickname");
String newNickname = "new-unique-nickname";
assertThatCode(() -> memberService.validateNickname(newNickname, member.getId()))
@@ -35,7 +64,7 @@ class ValidateNickname {
@Test
void ์์ ์_๊ธฐ์กด_๋๋ค์์ด๋ฉด_์์ธ๊ฐ_๋ฐ์ํ์ง_์๋๋ค() {
- Member member = memberGenerator.generate("123", "nickname");
+ Member member = memberGenerator.generateByNickname("123", "nickname");
String newNickname = "nickname";
assertThatCode(() -> memberService.validateNickname(newNickname, member.getId()))
@@ -44,8 +73,8 @@ class ValidateNickname {
@Test
void ์ค๋ณต๋_๋๋ค์์ด_์์ผ๋ฉด_์์ธ๊ฐ_๋ฐ์ํ๋ค() {
- memberGenerator.generate("123", "duplicate-nickname");
- Member member = memberGenerator.generate("456", "another-nickname");
+ memberGenerator.generate("123", "abc@kakao.com", "duplicate-nickname");
+ Member member = memberGenerator.generate("456", "def@kakao.com", "another-nickname");
String newNickname = "duplicate-nickname";
BusinessException exception = assertThrows(BusinessException.class,
@@ -60,8 +89,8 @@ class ValidatePhoneNumber {
@Test
void ์ค๋ณต๋์ง_์์_์ ํ๋ฒํธ์ด๋ฉด_์์ธ๊ฐ_๋ฐ์ํ์ง_์๋๋ค() {
- memberGenerator.generate("123", "nickname");
- Member member = memberGenerator.generate("456", "unique-nickname");
+ memberGenerator.generate("123", "abc@kakao.com", "nickname");
+ Member member = memberGenerator.generate("456", "def@kakao.com", "unique-nickname");
String newPhoneNumber = "01012345678";
assertThatCode(() -> memberService.validatePhoneNumber(newPhoneNumber, member.getId()))
@@ -70,7 +99,7 @@ class ValidatePhoneNumber {
@Test
void ์์ ์_๊ธฐ์กด_์ ํ๋ฒํธ์ด๋ฉด_์์ธ๊ฐ_๋ฐ์ํ์ง_์๋๋ค() {
- Member member = memberGenerator.generateRegisteredMember("123", "nickname", "01012345678");
+ Member member = memberGenerator.generateRegisteredMember("nickname", "hij@kakao.com", "123", "01012345678");
String newPhoneNumber = "01012345678";
assertThatCode(() -> memberService.validatePhoneNumber(newPhoneNumber, member.getId()))
@@ -79,8 +108,9 @@ class ValidatePhoneNumber {
@Test
void ์ค๋ณต๋_์ ํ๋ฒํธ๊ฐ_์์ผ๋ฉด_์์ธ๊ฐ_๋ฐ์ํ๋ค() {
- memberGenerator.generateRegisteredMember("123", "nickname1", "01012345678");
- Member member = memberGenerator.generateRegisteredMember("456", "nickname2", "01087654321");
+ memberGenerator.generateRegisteredMember("nickname1", "abc@kakao.com", "123", "01012345678");
+ Member member = memberGenerator.generateRegisteredMember("nickname2", "def@kakao.com", "456",
+ "01087654321");
String newPhoneNumber = "01012345678";
BusinessException exception = assertThrows(BusinessException.class,
@@ -96,7 +126,7 @@ class Update {
@Test
void ํ์_์ ๋ณด๋ฅผ_์์ ํ _์_์๋ค() {
Member member = memberGenerator.generate("123");
- MemberUpdateRequest request = new MemberUpdateRequest("update-nickname", "01012345678", "์ฑ๋ถ๊ตฌ", true);
+ MemberUpdateRequest request = new MemberUpdateRequest("update-nickname", "01012345678", true);
MemberResponse response = memberService.update(member.getId(), request);
@@ -105,17 +135,16 @@ class Update {
() -> assertThat(response.isSignUp()).isFalse(),
() -> assertThat(response.nickname()).isEqualTo("update-nickname"),
() -> assertThat(response.phoneNumber()).isEqualTo("01012345678"),
- () -> assertThat(response.interestArea()).isEqualTo("์ฑ๋ถ๊ตฌ"),
() -> assertThat(response.optInMarketing()).isTrue()
);
}
@Test
void ์ค๋ณต๋_๋๋ค์์ด_์์ผ๋ฉด_์์ธ๊ฐ_๋ฐ์ํ๋ค() {
- Member existMember = memberGenerator.generate("123", "duplicate-nickname");
- Member updatedMember = memberGenerator.generate("456");
+ Member existMember = memberGenerator.generate("123", "abc@kakao.com", "duplicate-nickname");
+ Member updatedMember = memberGenerator.generate("456", "def@kakao.com", "another-nickname");
MemberUpdateRequest request =
- new MemberUpdateRequest(existMember.getNickname(), "01012345678", "์ฑ๋ถ๊ตฌ", true);
+ new MemberUpdateRequest(existMember.getNickname(), "01012345678", true);
BusinessException exception = assertThrows(BusinessException.class,
() -> memberService.update(updatedMember.getId(), request));
@@ -125,9 +154,9 @@ class Update {
@Test
void ๊ธฐ์กด์_๋๋ค์๊ณผ_๋์ผํ๋ฉด_์์ผ๋ฉด_์ ์์ ์ผ๋ก_ํ์_์ ๋ณด๊ฐ_์์ ๋๋ค() {
- Member member = memberGenerator.generate("123", "duplicate-nickname");
+ Member member = memberGenerator.generateByNickname("123", "duplicate-nickname");
MemberUpdateRequest request =
- new MemberUpdateRequest(member.getNickname(), "01012345678", "์ฑ๋ถ๊ตฌ", true);
+ new MemberUpdateRequest(member.getNickname(), "01012345678", true);
MemberResponse response = memberService.update(member.getId(), request);
@@ -137,10 +166,10 @@ class Update {
@Test
void ์ค๋ณต๋_์ ํ๋ฒํธ๊ฐ_์์ผ๋ฉด_์์ธ๊ฐ_๋ฐ์ํ๋ค() {
String phoneNumber = "01012345678";
- memberGenerator.generateRegisteredMember("123", "nickname1", phoneNumber);
- Member updatedMember = memberGenerator.generate("456", "nickname2");
+ memberGenerator.generateRegisteredMember("nickname1", "hij@kakao.com", "123", phoneNumber);
+ Member updatedMember = memberGenerator.generate("456", "abc@kakao.com", "nickname2");
MemberUpdateRequest request =
- new MemberUpdateRequest("new-nickname", phoneNumber, "์ฑ๋ถ๊ตฌ", true);
+ new MemberUpdateRequest("new-nickname", phoneNumber, true);
BusinessException exception = assertThrows(BusinessException.class,
() -> memberService.update(updatedMember.getId(), request));
@@ -151,9 +180,9 @@ class Update {
@Test
void ๊ธฐ์กด์_์ ํ๋ฒํธ์_๋์ผํ๋ฉด_์ ์์ ์ผ๋ก_ํ์_์ ๋ณด๊ฐ_์์ ๋๋ค() {
String phoneNumber = "01012345678";
- Member member = memberGenerator.generateRegisteredMember("123", "nickname1", phoneNumber);
+ Member member = memberGenerator.generateRegisteredMember("nickname1", "hij@kakao.com", "123", phoneNumber);
MemberUpdateRequest request =
- new MemberUpdateRequest("new-nickname", phoneNumber, "์ฑ๋ถ๊ตฌ", true);
+ new MemberUpdateRequest("new-nickname", phoneNumber, true);
MemberResponse response = memberService.update(member.getId(), request);
diff --git a/src/test/java/eatda/service/store/CheerServiceTest.java b/src/test/java/eatda/service/store/CheerServiceTest.java
new file mode 100644
index 00000000..184b077b
--- /dev/null
+++ b/src/test/java/eatda/service/store/CheerServiceTest.java
@@ -0,0 +1,41 @@
+package eatda.service.store;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertAll;
+
+import eatda.controller.store.CheersResponse;
+import eatda.domain.member.Member;
+import eatda.domain.store.Cheer;
+import eatda.domain.store.Store;
+import eatda.service.BaseServiceTest;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+
+class CheerServiceTest extends BaseServiceTest {
+
+ @Autowired
+ private CheerService cheerService;
+
+ @Nested
+ class GetCheers {
+
+ @Test
+ void ์์ฒญํ_์์_๊ฐ์๋งํผ_์์์_์ต์ ์์ผ๋ก_๋ฐํํ๋ค() {
+ Member member = memberGenerator.generate("123");
+ Store store1 = storeGenerator.generate("123", "์์ธ์ ๊ฐ๋จ๊ตฌ ์ญ์ผ๋ 123-45");
+ Store store2 = storeGenerator.generate("456", "์์ธ์ ์ฑ๋ถ๊ตฌ ์๊ด๋ 123-45");
+ Cheer cheer1 = cheerGenerator.generateAdmin(member, store1);
+ Cheer cheer2 = cheerGenerator.generateAdmin(member, store1);
+ Cheer cheer3 = cheerGenerator.generateAdmin(member, store2);
+
+ CheersResponse response = cheerService.getCheers(2);
+
+ assertAll(
+ () -> assertThat(response.cheers()).hasSize(2),
+ () -> assertThat(response.cheers().get(0).cheerId()).isEqualTo(cheer3.getId()),
+ () -> assertThat(response.cheers().get(1).cheerId()).isEqualTo(cheer2.getId())
+ );
+ }
+ }
+}
diff --git a/src/test/java/eatda/service/store/StoreSearchFilterTest.java b/src/test/java/eatda/service/store/StoreSearchFilterTest.java
new file mode 100644
index 00000000..d6eca025
--- /dev/null
+++ b/src/test/java/eatda/service/store/StoreSearchFilterTest.java
@@ -0,0 +1,42 @@
+package eatda.service.store;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import eatda.client.map.StoreSearchResult;
+import java.util.List;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+class StoreSearchFilterTest {
+
+ private final StoreSearchFilter storeSearchFilter = new StoreSearchFilter();
+
+ @Nested
+ class FilterSearchedStores {
+
+ @Test
+ void ๋น_๊ฒ์_๊ฒฐ๊ณผ๋ฅผ_๋ฃ์ผ๋ฉด_๋น_๋ฆฌ์คํธ๋ฅผ_๋ฐํํ๋ค() {
+ List actual = storeSearchFilter.filterSearchedStores(List.of());
+
+ assertThat(actual).isEmpty();
+ }
+
+ @Test
+ void ์์์ ์ด_์๋๊ฑฐ๋_์์ธ์_์์นํ์ง_์๋_๊ฐ๊ฒ๋_์ ์ธํ๋ค() {
+ StoreSearchResult store1 = createStore("1", "์์ธ์์์ 1","FD6", "์์ธ ๊ฐ๋จ๊ตฌ ๋์น๋ 896-33");
+ StoreSearchResult store2 = createStore("2", "์นดํ", "CD2","์์ธ ๊ฐ๋จ๊ตฌ ๋์น๋ 896-33");
+ StoreSearchResult store3 = createStore("3", "์์ธ์์์ 2", "FD6","์์ธ ๊ฐ๋จ๊ตฌ ๋์น๋ 896-33");
+ StoreSearchResult store4 = createStore("4", "๋ถ์ฐ์์์ ", "FD6","๋ถ์ฐ ์ฐ์ ๊ตฌ ์ฐ์ฐ๋ 632-8");
+
+ List searchResults = List.of(store1, store2, store3, store4);
+ List actual = storeSearchFilter.filterSearchedStores(searchResults);
+
+ assertThat(actual).containsExactly(store1, store3);
+ }
+
+ private StoreSearchResult createStore(String id, String name, String categoryGroupCode, String location) {
+ return new StoreSearchResult(id, categoryGroupCode, "์์์ > ์๋น", "010-1234-1234", name, "https://yapp.co.kr",
+ location, null, 37.0d, 128.0d);
+ }
+ }
+}
diff --git a/src/test/java/eatda/service/store/StoreServiceTest.java b/src/test/java/eatda/service/store/StoreServiceTest.java
new file mode 100644
index 00000000..b36b95bb
--- /dev/null
+++ b/src/test/java/eatda/service/store/StoreServiceTest.java
@@ -0,0 +1,77 @@
+package eatda.service.store;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertAll;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doReturn;
+
+import eatda.client.map.StoreSearchResult;
+import eatda.domain.member.Member;
+import eatda.domain.store.Store;
+import eatda.service.BaseServiceTest;
+import java.util.List;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+
+class StoreServiceTest extends BaseServiceTest {
+
+ @Autowired
+ private StoreService storeService;
+
+ @Nested
+ class GetStores {
+
+ @Test
+ void ์์์ _๋ชฉ๋ก์_์ต์ ์์ผ๋ก_์กฐํํ๋ค() {
+ Member member = memberGenerator.generate("111");
+ Store store1 = storeGenerator.generate("๋๋ฏผ๋ฐฑ์์๋", "์์ธ ๊ฐ๋จ๊ตฌ ๋์น๋ 896-33");
+ cheerGenerator.generateCommon(member, store1, "image-key-1");
+ Store store2 = storeGenerator.generate("์๊ด๋๋ก๋ณถ์ด", "์์ธ ์ฑ๋ถ๊ตฌ ์๊ด๋ 123-45");
+ cheerGenerator.generateCommon(member, store2, "image-key-2");
+ Store store3 = storeGenerator.generate("๊ฐ๋จ์๋๊ตญ", "์์ธ ๊ฐ๋จ๊ตฌ ์ญ์ผ๋ 678-90");
+ cheerGenerator.generateCommon(member, store3, "image-key-3");
+
+ int size = 2;
+
+ var response = storeService.getStores(size);
+
+ assertAll(
+ () -> assertThat(response.stores()).hasSize(size),
+ () -> assertThat(response.stores().get(0).id()).isEqualTo(store3.getId()),
+ () -> assertThat(response.stores().get(1).id()).isEqualTo(store2.getId())
+ );
+ }
+ }
+
+ @Nested
+ class SearchStores {
+
+ @Test
+ void ์์์ _๊ฒ์_๊ฒฐ๊ณผ๋ฅผ_๋ฐํํ๋ค() {
+ mockingMapClient();
+ String query = "๋๋ฏผ๋ฐฑ์์๋";
+
+ var response = storeService.searchStores(query);
+
+ assertAll(
+ () -> assertThat(response.stores()).hasSize(2),
+ () -> assertThat(response.stores().get(0).kakaoId()).isEqualTo("123"),
+ () -> assertThat(response.stores().get(0).address()).isEqualTo("์์ธ ๊ฐ๋จ๊ตฌ ๋์น๋ 896-33"),
+ () -> assertThat(response.stores().get(1).kakaoId()).isEqualTo("456"),
+ () -> assertThat(response.stores().get(1).address()).isEqualTo("์์ธ ์ค๊ตฌ ๋ถ์ฐฝ๋ 19-4")
+ );
+ }
+ }
+
+ void mockingMapClient() {
+ List searchResults = List.of(
+ new StoreSearchResult("123", "FD6", "์์์ > ํ์ > ๊ตญ๋ฐฅ", "010-1234-1234", "๋๋ฏผ๋ฐฑ์์๋ ๋ณธ์ ", "https://yapp.co.kr",
+ "์์ธ ๊ฐ๋จ๊ตฌ ๋์น๋ 896-33", "์์ธ ๊ฐ๋จ๊ตฌ ์ ๋ฆ๋ก86๊ธธ 40-4", 37.0d, 128.0d),
+ new StoreSearchResult("456", "FD6", "์์์ > ํ์ > ๊ตญ๋ฐฅ", "010-1234-1234", "๋๋ฏผ๋ฐฑ์์๋ ์์ฒญ์ ", "http://yapp.kr",
+ "์์ธ ์ค๊ตฌ ๋ถ์ฐฝ๋ 19-4", null, 37.0d, 128.0d)
+ );
+
+ doReturn(searchResults).when(mapClient).searchShops(anyString());
+ }
+}
diff --git a/src/test/java/eatda/service/story/StoryServiceTest.java b/src/test/java/eatda/service/story/StoryServiceTest.java
new file mode 100644
index 00000000..ce56540b
--- /dev/null
+++ b/src/test/java/eatda/service/story/StoryServiceTest.java
@@ -0,0 +1,61 @@
+package eatda.service.story;
+
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import eatda.client.map.StoreSearchResult;
+import eatda.controller.story.StoryRegisterRequest;
+import eatda.domain.member.Member;
+import eatda.exception.BusinessErrorCode;
+import eatda.exception.BusinessException;
+import eatda.service.BaseServiceTest;
+import eatda.service.common.ImageDomain;
+import java.util.Collections;
+import java.util.List;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.multipart.MultipartFile;
+
+public class StoryServiceTest extends BaseServiceTest {
+
+ @Autowired
+ private StoryService storyService;
+
+ @Nested
+ class RegisterStory {
+
+ @Test
+ void ์คํ ๋ฆฌ_๋ฑ๋ก์_์ฑ๊ณตํ๋ค() {
+ Member member = memberGenerator.generate("12345");
+ StoryRegisterRequest request = new StoryRegisterRequest("๊ณฑ์ฐฝ", "123", "๋ฏธ์ณค๋ค ์ฌ๊ธฐ");
+ MultipartFile image = mock(MultipartFile.class);
+
+ StoreSearchResult store = new StoreSearchResult(
+ "123", "FD6", "์์์ > ํ์", "010-1234-5678",
+ "๊ณฑ์ฐฝ์ง", "http://example.com",
+ "์์ธ ๊ฐ๋จ๊ตฌ", "์์ธ ๊ฐ๋จ๊ตฌ", 37.0, 127.0
+ );
+ doReturn(List.of(store)).when(mapClient).searchShops(request.query());
+ when(imageService.upload(image, ImageDomain.STORY)).thenReturn("image-key");
+
+ assertDoesNotThrow(() -> storyService.registerStory(request, image, member.getId()));
+ }
+
+ @Test
+ void ํด๋ผ์ด์ธํธ_์์ฒญ๊ณผ_์ผ์นํ๋_๊ฐ๊ฒ๊ฐ_์์ผ๋ฉด_์คํจํ๋ค() {
+ Member member = memberGenerator.generate("12345");
+ StoryRegisterRequest request = new StoryRegisterRequest("๊ณฑ์ฐฝ", "999", "๋ฏธ์ณค๋ค ์ฌ๊ธฐ");
+
+ MultipartFile image = mock(MultipartFile.class);
+ doReturn(Collections.emptyList()).when(mapClient).searchShops(request.query());
+
+ assertThatThrownBy(() -> storyService.registerStory(request, image, member.getId()))
+ .isInstanceOf(BusinessException.class)
+ .hasMessageContaining(BusinessErrorCode.STORE_NOT_FOUND.getMessage());
+ }
+ }
+}
diff --git a/src/test/java/timeeat/controller/BaseControllerTest.java b/src/test/java/timeeat/controller/BaseControllerTest.java
deleted file mode 100644
index 67378ca5..00000000
--- a/src/test/java/timeeat/controller/BaseControllerTest.java
+++ /dev/null
@@ -1,89 +0,0 @@
-package timeeat.controller;
-
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.doReturn;
-
-import io.restassured.RestAssured;
-import io.restassured.builder.RequestSpecBuilder;
-import io.restassured.filter.Filter;
-import io.restassured.filter.log.RequestLoggingFilter;
-import io.restassured.filter.log.ResponseLoggingFilter;
-import io.restassured.specification.RequestSpecification;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.List;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.extension.ExtendWith;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.boot.test.web.server.LocalServerPort;
-import org.springframework.test.context.bean.override.mockito.MockitoBean;
-import timeeat.DatabaseCleaner;
-import timeeat.client.oauth.OauthClient;
-import timeeat.client.oauth.OauthMemberInformation;
-import timeeat.client.oauth.OauthToken;
-import timeeat.controller.web.jwt.JwtManager;
-import timeeat.domain.member.Member;
-import timeeat.fixture.MemberGenerator;
-import timeeat.repository.member.MemberRepository;
-
-@ExtendWith(DatabaseCleaner.class)
-@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
-public class BaseControllerTest {
-
- private static final List SPEC_FILTERS = List.of(new RequestLoggingFilter(), new ResponseLoggingFilter());
-
- private static final OauthToken DEFAULT_OAUTH_TOKEN = new OauthToken("oauth-access-token");
- private static final OauthMemberInformation DEFAULT_OAUTH_MEMBER_INFO =
- new OauthMemberInformation(314159248183772L, "nickname");
-
- @LocalServerPort
- private int port;
-
- @Autowired
- protected MemberGenerator memberGenerator;
-
- @Autowired
- protected MemberRepository memberRepository;
-
- @Autowired
- private JwtManager jwtManager;
-
- @MockitoBean
- private OauthClient oauthClient;
-
- private RequestSpecification spec;
-
- @BeforeEach
- final void setEnvironment() {
- RestAssured.port = port;
- spec = new RequestSpecBuilder()
- .addFilters(SPEC_FILTERS)
- .build();
- }
-
- @BeforeEach
- final void mockingClient() throws URISyntaxException {
- doReturn(new URI("http://localhost:8080/login/callback")).when(oauthClient).getOauthLoginUrl(anyString());
- doReturn(DEFAULT_OAUTH_TOKEN).when(oauthClient).requestOauthToken(anyString(), anyString());
- doReturn(DEFAULT_OAUTH_MEMBER_INFO).when(oauthClient).requestMemberInformation(DEFAULT_OAUTH_TOKEN);
- }
-
- protected final RequestSpecification given() {
- return RestAssured.given(spec);
- }
-
- protected final String accessToken() {
- Member member = memberGenerator.generate(Long.toString(DEFAULT_OAUTH_MEMBER_INFO.socialId()));
- return jwtManager.issueAccessToken(member.getId());
- }
-
- protected final String refreshToken() {
- Member member = memberGenerator.generate(Long.toString(DEFAULT_OAUTH_MEMBER_INFO.socialId()));
- return jwtManager.issueRefreshToken(member.getId());
- }
-
- protected final String oauthLoginSocialId() {
- return Long.toString(DEFAULT_OAUTH_MEMBER_INFO.socialId());
- }
-}
diff --git a/src/test/java/timeeat/document/member/MemberDocumentTest.java b/src/test/java/timeeat/document/member/MemberDocumentTest.java
deleted file mode 100644
index 36857092..00000000
--- a/src/test/java/timeeat/document/member/MemberDocumentTest.java
+++ /dev/null
@@ -1,130 +0,0 @@
-package timeeat.document.member;
-
-import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
-import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName;
-import static org.springframework.restdocs.payload.JsonFieldType.BOOLEAN;
-import static org.springframework.restdocs.payload.JsonFieldType.NUMBER;
-import static org.springframework.restdocs.payload.JsonFieldType.STRING;
-import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
-import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
-
-import io.restassured.http.ContentType;
-import org.junit.jupiter.api.Nested;
-import org.junit.jupiter.api.Test;
-import org.springframework.http.HttpHeaders;
-import timeeat.controller.member.MemberResponse;
-import timeeat.controller.member.MemberUpdateRequest;
-import timeeat.document.BaseDocumentTest;
-import timeeat.document.RestDocsRequest;
-import timeeat.document.RestDocsResponse;
-import timeeat.document.Tag;
-
-public class MemberDocumentTest extends BaseDocumentTest {
-
- @Nested
- class CheckNickname {
-
- RestDocsRequest requestDocument = request()
- .tag(Tag.MEMBER_API)
- .summary("๋๋ค์ ์ค๋ณต ๊ฒ์ฌ")
- .requestHeader(
- headerWithName(HttpHeaders.AUTHORIZATION).description("์ก์ธ์ค ํ ํฐ")
- ).queryParameter(
- parameterWithName("nickname").description("๊ฒ์ฌํ ๋๋ค์")
- );
-
- @Test
- void ์ค๋ณต๋์ง_์๋_๋๋ค์์_ํ์ธํ _์_์๋ค() {
- doNothing().when(memberService).validateNickname(anyString(), anyLong());
-
- var document = document("member/nickname-check", 204)
- .request(requestDocument)
- .build();
-
- given(document)
- .contentType(ContentType.JSON)
- .header(HttpHeaders.AUTHORIZATION, accessToken())
- .queryParam("nickname", "new-nickname")
- .when().get("/api/member/nickname/check")
- .then().statusCode(204);
- }
- }
-
- @Nested
- class CheckPhoneNumber {
-
- RestDocsRequest requestDocument = request()
- .tag(Tag.MEMBER_API)
- .summary("์ ํ๋ฒํธ ์ค๋ณต ๊ฒ์ฌ")
- .requestHeader(
- headerWithName(HttpHeaders.AUTHORIZATION).description("์ก์ธ์ค ํ ํฐ")
- ).queryParameter(
- parameterWithName("phoneNumber").description("๊ฒ์ฌํ ์ ํ๋ฒํธ ex) 01012345678")
- );
-
- @Test
- void ์ค๋ณต๋์ง_์๋_์ ํ๋ฒํธ๋ฅผ_ํ์ธํ _์_์๋ค() {
- doNothing().when(memberService).validatePhoneNumber(anyString(), anyLong());
-
- var document = document("member/phone-number-check", 204)
- .request(requestDocument)
- .build();
-
- given(document)
- .contentType(ContentType.JSON)
- .header(HttpHeaders.AUTHORIZATION, accessToken())
- .queryParam("phoneNumber", "01098765432")
- .when().get("/api/member/phone-number/check")
- .then().statusCode(204);
- }
- }
-
- @Nested
- class UpdateMember {
-
- RestDocsRequest requestDocument = request()
- .tag(Tag.MEMBER_API)
- .summary("ํ์ ์ ๋ณด ์์ ")
- .requestHeader(
- headerWithName(HttpHeaders.AUTHORIZATION).description("์ก์ธ์ค ํ ํฐ")
- ).requestBodyField(
- fieldWithPath("nickname").type(STRING).description("ํ์ ๋๋ค์"),
- fieldWithPath("phoneNumber").type(STRING).description("ํ์ ์ ํ๋ฒํธ ex) 01012345678"),
- fieldWithPath("interestArea").type(STRING).description("ํ์ ๊ด์ฌ ์ง์ญ ex) ์ข
๋ก๊ตฌ"),
- fieldWithPath("optInMarketing").type(BOOLEAN).description("๋ง์ผํ
๋์ ์ฌ๋ถ")
- );
-
- RestDocsResponse responseDocument = response()
- .responseBodyField(
- fieldWithPath("id").type(NUMBER).description("ํ์ ์๋ณ์"),
- fieldWithPath("isSignUp").type(BOOLEAN).description("ํ์ ๊ฐ์
์์ฒญ ์ฌ๋ถ (false ๊ณ ์ )"),
- fieldWithPath("nickname").type(STRING).description("ํ์ ๋๋ค์").optional(),
- fieldWithPath("phoneNumber").type(STRING).description("ํ์ ์ ํ๋ฒํธ ex) 01012345678").optional(),
- fieldWithPath("interestArea").type(STRING).description("ํ์ ๊ด์ฌ ์ง์ญ ex) ์ข
๋ก๊ตฌ").optional(),
- fieldWithPath("optInMarketing").type(BOOLEAN).description("๋ง์ผํ
๋์ ์ฌ๋ถ").optional()
- );
-
- @Test
- void ํ์_์ ๋ณด_์์ _์ฑ๊ณต() {
- MemberUpdateRequest request = new MemberUpdateRequest("update-nickname", "01012345678", "์ฑ๋ถ๊ตฌ", true);
- MemberResponse response = new MemberResponse(1L, false, "update-nickname", "01012345678", "์ฑ๋ถ๊ตฌ", true);
- doReturn(response).when(memberService).update(anyLong(), eq(request));
-
- var document = document("member/update", 200)
- .request(requestDocument)
- .response(responseDocument)
- .build();
-
- given(document)
- .contentType(ContentType.JSON)
- .header(HttpHeaders.AUTHORIZATION, accessToken())
- .body(request)
- .when().put("/api/member")
- .then().statusCode(200);
- }
- }
-}
diff --git a/src/test/java/timeeat/domain/bookmark/BookmarkTest.java b/src/test/java/timeeat/domain/bookmark/BookmarkTest.java
deleted file mode 100644
index 510f0452..00000000
--- a/src/test/java/timeeat/domain/bookmark/BookmarkTest.java
+++ /dev/null
@@ -1,75 +0,0 @@
-package timeeat.domain.bookmark;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.jupiter.api.Assertions.assertAll;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-
-import java.time.LocalTime;
-import org.junit.jupiter.api.DisplayName;
-import org.junit.jupiter.api.Nested;
-import org.junit.jupiter.api.Test;
-import timeeat.domain.member.Member;
-import timeeat.domain.store.Store;
-import timeeat.exception.BusinessErrorCode;
-import timeeat.exception.BusinessException;
-
-class BookmarkTest {
-
- @Nested
- @DisplayName("๋ถ๋งํฌ ์์ฑ ์")
- class CreateBookmark {
-
- @Test
- void ์ ์์ ์ธ_๋ฉค๋ฒ์_๊ฐ๊ฒ๋ก_๋ถ๋งํฌ๋ฅผ_์์ฑํ๋ค() {
- Member member = new Member("socialId123", "nickname");
- Store store = new Store(
- "๊ฐ๊ฒ๋ช
",
- "์์",
- "์ฃผ์",
- 37.5,
- 127.0,
- "0212345678",
- null,
- LocalTime.of(11, 30),
- LocalTime.of(21, 0),
- null,
- "๊ฐ๋จ๊ตฌ");
-
- Bookmark bookmark = new Bookmark(member, store);
-
- assertAll(
- () -> assertThat(bookmark.getMember()).isEqualTo(member),
- () -> assertThat(bookmark.getStore()).isEqualTo(store)
- );
- }
-
- @Test
- void ๋ฉค๋ฒ๊ฐ_null์ด๋ฉด_์์ธ๋ฅผ_๋์ง๋ค() {
- Store store = new Store(
- "๊ฐ๊ฒ๋ช
",
- "์์",
- "์ฃผ์",
- 37.5,
- 127.0,
- "0212345678",
- null,
- LocalTime.of(11, 30),
- LocalTime.of(21, 0),
- null,
- "๊ฐ๋จ๊ตฌ");
-
- BusinessException exception = assertThrows(BusinessException.class, () -> new Bookmark(null, store));
-
- assertThat(exception.getErrorCode()).isEqualTo(BusinessErrorCode.BOOKMARK_MEMBER_REQUIRED);
- }
-
- @Test
- void ๊ฐ๊ฒ๊ฐ_null์ด๋ฉด_์์ธ๋ฅผ_๋์ง๋ค() {
- Member member = new Member("socialId123", "nickname");
-
- BusinessException exception = assertThrows(BusinessException.class, () -> new Bookmark(member, null));
-
- assertThat(exception.getErrorCode()).isEqualTo(BusinessErrorCode.BOOKMARK_STORE_REQUIRED);
- }
- }
-}
diff --git a/src/test/java/timeeat/domain/menu/DiscountTest.java b/src/test/java/timeeat/domain/menu/DiscountTest.java
deleted file mode 100644
index 18842dad..00000000
--- a/src/test/java/timeeat/domain/menu/DiscountTest.java
+++ /dev/null
@@ -1,86 +0,0 @@
-package timeeat.domain.menu;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-
-import java.time.LocalDateTime;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.DisplayName;
-import org.junit.jupiter.api.Nested;
-import org.junit.jupiter.api.Test;
-import timeeat.exception.BusinessErrorCode;
-import timeeat.exception.BusinessException;
-
-class DiscountTest {
-
- private Price originalPrice;
-
- @BeforeEach
- void setUp() {
- originalPrice = new Price(10000);
- }
-
- @Nested
- @DisplayName("ํ ์ธ์ ์์ฑํ ๋")
- class CreateDiscount {
-
- @Test
- void ๋ชจ๋ _์ ๋ณด๊ฐ_์ ์์ ์ผ_๋_์์ฑํ๋ค() {
- Integer discountPrice = 8000;
- LocalDateTime startTime = LocalDateTime.now();
- LocalDateTime endTime = startTime.plusHours(2);
-
- Discount discount = new Discount(originalPrice, discountPrice, startTime, endTime);
-
- assertThat(discount.getDiscountPrice()).isEqualTo(discountPrice);
- assertThat(discount.getStartTime()).isEqualTo(startTime);
- assertThat(discount.getEndTime()).isEqualTo(endTime);
- }
-
- @Test
- void ํ ์ธ_์ ๋ณด๊ฐ_์์ด๋_์์ฑํ๋ค() {
- Discount discount = new Discount(originalPrice, null, null, null);
-
- assertThat(discount.getDiscountPrice()).isNull();
- assertThat(discount.getStartTime()).isNull();
- assertThat(discount.getEndTime()).isNull();
- }
-
- @Test
- void ํ ์ธ_๊ฐ๊ฒฉ์ด_์๊ฐ๋ณด๋ค_๋น์ธ๋ฉด_์์ธ๋ฅผ_๋์ง๋ค() {
- Integer invalidDiscountPrice = 12000;
-
- BusinessException exception = assertThrows(BusinessException.class, () -> new Discount(originalPrice, invalidDiscountPrice, null, null));
-
- assertThat(exception.getErrorCode()).isEqualTo(BusinessErrorCode.INVALID_MENU_DISCOUNT_PRICE);
- }
-
- @Test
- void ํ ์ธ_๊ฐ๊ฒฉ์ด_์๊ฐ์_๊ฐ์ผ๋ฉด_์์ธ๋ฅผ_๋์ง๋ค() {
- Integer sameDiscountPrice = 10000;
-
- BusinessException exception = assertThrows(BusinessException.class, () -> new Discount(originalPrice, sameDiscountPrice, null, null));
-
- assertThat(exception.getErrorCode()).isEqualTo(BusinessErrorCode.INVALID_MENU_DISCOUNT_PRICE);
- }
-
- @Test
- void ํ ์ธ_๊ฐ๊ฒฉ์ด_0์์ด๋ฉด_์์ธ๋ฅผ_๋์ง๋ค() {
- Integer zeroDiscountPrice = 0;
-
- BusinessException exception = assertThrows(BusinessException.class, () -> new Discount(originalPrice, zeroDiscountPrice, null, null));
-
- assertThat(exception.getErrorCode()).isEqualTo(BusinessErrorCode.INVALID_MENU_DISCOUNT_PRICE);
- }
-
- @Test
- void ์์_์๊ฐ์ด_์ข
๋ฃ_์๊ฐ๋ณด๋ค_๋ฆ์ผ๋ฉด_์์ธ๋ฅผ_๋์ง๋ค() {
- LocalDateTime startTime = LocalDateTime.now();
- LocalDateTime endTime = startTime.minusHours(2);
-
- BusinessException exception = assertThrows(BusinessException.class, () -> new Discount(originalPrice, 8000, startTime, endTime));
-
- assertThat(exception.getErrorCode()).isEqualTo(BusinessErrorCode.INVALID_MENU_DISCOUNT_TIME);
- }
- }
-}
diff --git a/src/test/java/timeeat/domain/menu/MenuTest.java b/src/test/java/timeeat/domain/menu/MenuTest.java
deleted file mode 100644
index 9eb8d520..00000000
--- a/src/test/java/timeeat/domain/menu/MenuTest.java
+++ /dev/null
@@ -1,65 +0,0 @@
-package timeeat.domain.menu;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.jupiter.api.Assertions.assertAll;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-
-import java.time.LocalDateTime;
-import org.junit.jupiter.api.DisplayName;
-import org.junit.jupiter.api.Nested;
-import org.junit.jupiter.api.Test;
-import timeeat.exception.BusinessErrorCode;
-import timeeat.exception.BusinessException;
-
-class MenuTest {
-
- @Nested
- @DisplayName("๋ฉ๋ด๋ฅผ ์์ฑํ ๋")
- class CreateMenu {
-
- @Test
- void ๋ชจ๋ _์ ๋ณด๊ฐ_์ ์์ ์ผ_๋_๋ฉ๋ด๋ฅผ_์์ฑํ๋ค() {
- String name = "ํด๋ฌผ ๋ณถ์๋ฐฅ";
- Integer price = 15000;
- Integer discountPrice = 12000;
-
- Menu menu = new Menu(name, "์ค๋ช
", price, "url", discountPrice, LocalDateTime.now(),
- LocalDateTime.now().plusHours(1));
-
- assertAll(
- () -> assertThat(menu.getName()).isEqualTo(name),
- () -> assertThat(menu.getPrice().getValue()).isEqualTo(price),
- () -> assertThat(menu.getDiscount().getDiscountPrice()).isEqualTo(discountPrice)
- );
- }
-
- @Test
- void ์ด๋ฆ์ด_null์ด๋ฉด_์์ธ๋ฅผ_๋์ง๋ค() {
- BusinessException exception = assertThrows(BusinessException.class,
- () -> new Menu(null, "์ค๋ช
", 10000, "url", null, null, null));
-
- assertThat(exception.getErrorCode()).isEqualTo(BusinessErrorCode.INVALID_MENU_NAME);
- }
-
- @Test
- void ํ ์ธ_๊ฐ๊ฒฉ์ด_์๊ฐ๋ณด๋ค_๋น์ธ๋ฉด_์์ธ๋ฅผ_๋์ง๋ค() {
- Integer price = 10000;
- Integer invalidDiscountPrice = 12000;
-
- BusinessException exception = assertThrows(BusinessException.class,
- () -> new Menu("์ด๋ฆ", "์ค๋ช
", price, "url", invalidDiscountPrice, null, null));
-
- assertThat(exception.getErrorCode()).isEqualTo(BusinessErrorCode.INVALID_MENU_DISCOUNT_PRICE);
- }
-
- @Test
- void ์ด๋ฆ์ด_์ต๋_๊ธธ์ด๋ฅผ_์ด๊ณผํ๋ฉด_์์ธ๋ฅผ_๋์ง๋ค() {
- String overflowName = "t".repeat(255 + 1);
-
- BusinessException exception = assertThrows(BusinessException.class,
- () -> new Menu(overflowName, "์ค๋ช
", 10000, "url", null, null, null));
-
- assertThat(exception.getErrorCode()).isEqualTo(BusinessErrorCode.INVALID_MENU_LENGTH);
- }
- }
-}
diff --git a/src/test/java/timeeat/domain/menu/PriceTest.java b/src/test/java/timeeat/domain/menu/PriceTest.java
deleted file mode 100644
index fb22f78e..00000000
--- a/src/test/java/timeeat/domain/menu/PriceTest.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package timeeat.domain.menu;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-
-import org.junit.jupiter.api.DisplayName;
-import org.junit.jupiter.api.Nested;
-import org.junit.jupiter.api.Test;
-import timeeat.exception.BusinessErrorCode;
-import timeeat.exception.BusinessException;
-
-class PriceTest {
-
- @Nested
- @DisplayName("๊ฐ๊ฒฉ์ ์์ฑํ ๋")
- class CreatePrice {
-
- @Test
- void ์ ์์ ์ธ_๊ฐ๊ฒฉ์ผ๋ก_์์ฑํ๋ค() {
- Integer validPriceValue = 10000;
-
- Price price = new Price(validPriceValue);
-
- assertThat(price.getValue()).isEqualTo(validPriceValue);
- }
-
- @Test
- void ๊ฐ๊ฒฉ์ด_null์ด๋ฉด_์์ธ๋ฅผ_๋์ง๋ค() {
- BusinessException exception = assertThrows(BusinessException.class, () -> new Price(null));
- assertThat(exception.getErrorCode()).isEqualTo(BusinessErrorCode.INVALID_MENU_PRICE);
- }
-
- @Test
- void ๊ฐ๊ฒฉ์ด_0์_์ดํ์ด๋ฉด_์์ธ๋ฅผ_๋์ง๋ค() {
- BusinessException exception = assertThrows(BusinessException.class, () -> new Price(0));
- assertThat(exception.getErrorCode()).isEqualTo(BusinessErrorCode.INVALID_MENU_PRICE);
- }
- }
-}
diff --git a/src/test/java/timeeat/domain/store/StoreHoursTest.java b/src/test/java/timeeat/domain/store/StoreHoursTest.java
deleted file mode 100644
index 36d8ad6f..00000000
--- a/src/test/java/timeeat/domain/store/StoreHoursTest.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package timeeat.domain.store;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-
-import java.time.LocalTime;
-import org.junit.jupiter.api.DisplayName;
-import org.junit.jupiter.api.Nested;
-import org.junit.jupiter.api.Test;
-import timeeat.exception.BusinessErrorCode;
-import timeeat.exception.BusinessException;
-
-class StoreHoursTest {
-
- @Nested
- @DisplayName("์์
์๊ฐ ์์ฑ ์")
- class CreateStoreHours {
-
- @Test
- void ์ ์์ ์ธ_์์
์๊ฐ์ผ๋ก_์์ฑํ๋ค() {
- LocalTime open = LocalTime.of(9, 0);
- LocalTime close = LocalTime.of(18, 0);
-
- StoreHours storeHours = new StoreHours(open, close);
-
- assertThat(storeHours.getOpenTime()).isEqualTo(open);
- assertThat(storeHours.getCloseTime()).isEqualTo(close);
- }
-
- @Test
- void openTime์ด_null์ด๋ฉด_์์ธ๋ฅผ_๋์ง๋ค() {
- LocalTime close = LocalTime.of(18, 0);
-
- BusinessException exception = assertThrows(BusinessException.class, () -> new StoreHours(null, close));
-
- assertThat(exception.getErrorCode()).isEqualTo(BusinessErrorCode.INVALID_STORE_TIME_NULL);
- }
-
- @Test
- void closeTime์ด_null์ด๋ฉด_์์ธ๋ฅผ_๋์ง๋ค() {
- LocalTime open = LocalTime.of(9, 0);
-
- BusinessException exception = assertThrows(BusinessException.class, () -> new StoreHours(open, null));
-
- assertThat(exception.getErrorCode()).isEqualTo(BusinessErrorCode.INVALID_STORE_TIME_NULL);
- }
-
- @Test
- void openTime์ด_closeTime๋ณด๋ค_๋ฆ์ผ๋ฉด_์์ธ๋ฅผ_๋์ง๋ค() {
- LocalTime open = LocalTime.of(20, 0);
- LocalTime close = LocalTime.of(9, 0);
-
- BusinessException exception = assertThrows(BusinessException.class, () -> new StoreHours(open, close));
-
- assertThat(exception.getErrorCode()).isEqualTo(BusinessErrorCode.INVALID_STORE_TIME_ORDER);
- }
- }
-}
diff --git a/src/test/java/timeeat/domain/store/StorePhoneNumberTest.java b/src/test/java/timeeat/domain/store/StorePhoneNumberTest.java
deleted file mode 100644
index f1e34ff7..00000000
--- a/src/test/java/timeeat/domain/store/StorePhoneNumberTest.java
+++ /dev/null
@@ -1,44 +0,0 @@
-package timeeat.domain.store;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-
-import org.junit.jupiter.api.DisplayName;
-import org.junit.jupiter.api.Nested;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.ValueSource;
-import timeeat.exception.BusinessErrorCode;
-import timeeat.exception.BusinessException;
-
-class StorePhoneNumberTest {
-
- @Nested
- @DisplayName("๊ฐ๊ฒ ๋ฒํธ๋ฅผ ์์ฑํ ๋")
- class CreateStorePhoneNumber {
-
- @ParameterizedTest
- @ValueSource(strings = {"021234567", "03112345678", "15771234", "077012345678"})
- void ๊ฐ๊ฒ_๋ฒํธ๋ฅผ_์ ์์ ์ผ๋ก_์์ฑํ๋ค(String validPhoneNumber) {
- StorePhoneNumber phoneNumber = new StorePhoneNumber(validPhoneNumber);
-
- assertThat(phoneNumber.getValue()).isEqualTo(validPhoneNumber);
- }
-
- @Test
- void null_๊ฐ์ผ๋ก_์์ฑํ _์_์๋ค() {
- String nullNumber = null;
-
- StorePhoneNumber phoneNumber = new StorePhoneNumber(nullNumber);
-
- assertThat(phoneNumber.getValue()).isNull();
- }
-
- @ParameterizedTest
- @ValueSource(strings = {"1234567", "1234567890123", "02-1234-5678", "abcdefghij"})
- void ๋ฒํธ_ํ์์ด_์ฌ๋ฐ๋ฅด์ง_์์ผ๋ฉด_์์ธ๋ฅผ_๋์ง๋ค(String invalidNumber) {
- BusinessException exception = assertThrows(BusinessException.class, () -> new StorePhoneNumber(invalidNumber));
- assertThat(exception.getErrorCode()).isEqualTo(BusinessErrorCode.INVALID_STORE_PHONE_NUMBER);
- }
- }
-}
diff --git a/src/test/java/timeeat/domain/store/StoreTest.java b/src/test/java/timeeat/domain/store/StoreTest.java
deleted file mode 100644
index 696e1902..00000000
--- a/src/test/java/timeeat/domain/store/StoreTest.java
+++ /dev/null
@@ -1,96 +0,0 @@
-package timeeat.domain.store;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.jupiter.api.Assertions.assertAll;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-
-import java.time.LocalTime;
-import org.junit.jupiter.api.DisplayName;
-import org.junit.jupiter.api.Nested;
-import org.junit.jupiter.api.Test;
-import timeeat.enums.StoreCategory;
-import timeeat.exception.BusinessErrorCode;
-import timeeat.exception.BusinessException;
-
-class StoreTest {
-
- @Nested
- @DisplayName("๊ฐ๊ฒ ์ ๋ณด๋ฅผ ์์ฑํ ๋")
- class CreateStore {
-
- @Test
- void ๋ชจ๋ _์ ๋ณด๊ฐ_์ ์์ ์ผ_๋_๊ฐ๊ฒ๋ฅผ_์์ฑํ๋ค() {
- String name = "์ง๋๋ฐฐ๊ธฐ ํ์คํ";
- String category = "์์";
- String address = "์์ธ์ ๊ฐ๋จ๊ตฌ ํ
ํค๋๋ก 212";
- Double latitude = 37.5012;
- Double longitude = 127.0396;
- String phoneNumber = "0212345678";
- LocalTime openTime = LocalTime.of(11, 30);
- LocalTime closeTime = LocalTime.of(21, 0);
-
- Store store = new Store(name, category, address, latitude, longitude,
- phoneNumber, null, openTime, closeTime, null, "๊ฐ๋จ๊ตฌ");
-
- assertAll(
- () -> assertThat(store.getName()).isEqualTo(name),
- () -> assertThat(store.getCategory()).isEqualTo(StoreCategory.WESTERN),
- () -> assertThat(store.getAddress()).isEqualTo(address),
- () -> assertThat(store.getCoordinates().getLatitude()).isEqualTo(latitude),
- () -> assertThat(store.getStorePhoneNumber().getValue()).isEqualTo(phoneNumber),
- () -> assertThat(store.getStoreHours().getOpenTime()).isEqualTo(openTime)
- );
- }
-
- @Test
- void ๊ฐ๊ฒ_์ด๋ฆ์ด_null์ด๋ฉด_์์ธ๋ฅผ_๋์ง๋ค() {
- BusinessException exception = assertThrows(BusinessException.class,
- () -> new Store(null, "์์", "์ฃผ์", 37.5, 127.0, "0212345678", null, LocalTime.now(), LocalTime.now().plusHours(1), null, "๊ฐ๋จ๊ตฌ"));
-
- assertThat(exception.getErrorCode()).isEqualTo(BusinessErrorCode.INVALID_STORE_NAME);
- }
-
- @Test
- void ์ฃผ์๊ฐ_null์ด๋ฉด_์์ธ๋ฅผ_๋์ง๋ค() {
- BusinessException exception = assertThrows(BusinessException.class,
- () -> new Store("๊ฐ๊ฒ", "์์", null, 37.5, 127.0, "0212345678", null, LocalTime.now(), LocalTime.now().plusHours(1), null, "๊ฐ๋จ๊ตฌ"));
-
- assertThat(exception.getErrorCode()).isEqualTo(BusinessErrorCode.INVALID_STORE_ADDRESS);
- }
-
- @Test
- void ์ ํจํ์ง_์์_์นดํ
๊ณ ๋ฆฌ๋ฉด_์์ธ๋ฅผ_๋์ง๋ค() {
- BusinessException exception = assertThrows(BusinessException.class,
- () -> new Store("๊ฐ๊ฒ", "์๋์นดํ
๊ณ ๋ฆฌ", "์ฃผ์", 37.5, 127.0, "0212345678", null, LocalTime.now(), LocalTime.now().plusHours(1), null, "๊ฐ๋จ๊ตฌ"));
-
- assertThat(exception.getErrorCode()).isEqualTo(BusinessErrorCode.INVALID_STORE_CATEGORY);
- }
-
- @Test
- void ์ขํ๊ฐ์ด_null์ด๋ฉด_์์ธ๋ฅผ_๋์ง๋ค() {
- BusinessException exception = assertThrows(BusinessException.class,
- () -> new Store("๊ฐ๊ฒ", "์์", "์ฃผ์", null, 127.0, "0212345678", null, LocalTime.now(), LocalTime.now().plusHours(1), null, "๊ฐ๋จ๊ตฌ"));
-
- assertThat(exception.getErrorCode()).isEqualTo(BusinessErrorCode.INVALID_STORE_COORDINATES_NULL);
- }
-
- @Test
- void ์ ํ๋ฒํธ_ํ์์ด_ํ๋ฆฌ๋ฉด_์์ธ๋ฅผ_๋์ง๋ค() {
- BusinessException exception = assertThrows(BusinessException.class,
- () -> new Store("๊ฐ๊ฒ", "์์", "์ฃผ์", 37.5, 127.0, "123", null, LocalTime.now(), LocalTime.now().plusHours(1), null, "๊ฐ๋จ๊ตฌ"));
-
- assertThat(exception.getErrorCode()).isEqualTo(BusinessErrorCode.INVALID_STORE_PHONE_NUMBER);
- }
-
- @Test
- void ์์
_์๊ฐ_์์๊ฐ_ํ๋ฆฌ๋ฉด_์์ธ๋ฅผ_๋์ง๋ค() {
- LocalTime openTime = LocalTime.of(22, 0);
- LocalTime closeTime = LocalTime.of(10, 0);
-
- BusinessException exception = assertThrows(BusinessException.class,
- () -> new Store("๊ฐ๊ฒ", "์์", "์ฃผ์", 37.5, 127.0, "0212345678", null, openTime, closeTime, null, "๊ฐ๋จ๊ตฌ"));
-
- assertThat(exception.getErrorCode()).isEqualTo(BusinessErrorCode.INVALID_STORE_TIME_ORDER);
- }
- }
-}
diff --git a/src/test/java/timeeat/enums/InterestAreaTest.java b/src/test/java/timeeat/enums/InterestAreaTest.java
deleted file mode 100644
index c7f101b9..00000000
--- a/src/test/java/timeeat/enums/InterestAreaTest.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package timeeat.enums;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-
-import org.junit.jupiter.api.DisplayName;
-import org.junit.jupiter.api.Nested;
-import org.junit.jupiter.api.Test;
-import timeeat.exception.BusinessErrorCode;
-import timeeat.exception.BusinessException;
-
-class InterestAreaTest {
-
- @Nested
- @DisplayName("from ๋ฉ์๋ ํ
์คํธ")
- class FromMethod {
-
- @Test
- void ์ ํจํ_์ด๋ฆ์ผ๋ก_InterestArea_๊ฐ์ฒด๋ฅผ_๋ฐํํ๋ค() {
- assertThat(InterestArea.from("๊ฐ๋จ๊ตฌ")).isEqualTo(InterestArea.GANGNAM);
- assertThat(InterestArea.from("์์ด๊ตฌ")).isEqualTo(InterestArea.SEOCHO);
- }
-
- @Test
- void ์ ํจํ์ง_์์_์ด๋ฆ์ผ๋ก_์์ธ๋ฅผ_๋์ง๋ค() {
- BusinessException exception = assertThrows(BusinessException.class, () -> InterestArea.from("์๋๋๋ค"));
-
- assertThat(exception.getErrorCode()).isEqualTo(BusinessErrorCode.INVALID_INTEREST_AREA);
- }
- }
-}
diff --git a/src/test/java/timeeat/fixture/MemberGenerator.java b/src/test/java/timeeat/fixture/MemberGenerator.java
deleted file mode 100644
index 8aa0b701..00000000
--- a/src/test/java/timeeat/fixture/MemberGenerator.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package timeeat.fixture;
-
-import org.springframework.stereotype.Component;
-import timeeat.domain.member.Member;
-import timeeat.enums.InterestArea;
-import timeeat.repository.member.MemberRepository;
-
-@Component
-public class MemberGenerator {
-
- private static final String DEFAULT_INTEREST_AREA = InterestArea.SEONGBUK.getAreaName();
-
- private final MemberRepository memberRepository;
-
- public MemberGenerator(MemberRepository memberRepository) {
- this.memberRepository = memberRepository;
- }
-
- public Member generate(String socialId) {
- return memberRepository.save(new Member(socialId, "nickname"));
- }
-
- public Member generate(String socialId, String nickname) {
- return memberRepository.save(new Member(socialId, nickname));
- }
-
- public Member generateRegisteredMember(String socialId, String nickname, String phoneNumber) {
- return memberRepository.save(new Member(socialId, nickname, phoneNumber, DEFAULT_INTEREST_AREA, true));
- }
-}
diff --git a/src/test/java/timeeat/repository/BaseRepositoryTest.java b/src/test/java/timeeat/repository/BaseRepositoryTest.java
deleted file mode 100644
index 3a512c09..00000000
--- a/src/test/java/timeeat/repository/BaseRepositoryTest.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package timeeat.repository;
-
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
-import timeeat.fixture.MemberGenerator;
-import timeeat.repository.member.MemberRepository;
-
-@DataJpaTest
-public abstract class BaseRepositoryTest {
-
- @Autowired
- protected MemberGenerator memberGenerator;
-
- @Autowired
- protected MemberRepository memberRepository;
-}
diff --git a/src/test/java/timeeat/service/BaseServiceTest.java b/src/test/java/timeeat/service/BaseServiceTest.java
deleted file mode 100644
index d376f1ca..00000000
--- a/src/test/java/timeeat/service/BaseServiceTest.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package timeeat.service;
-
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.doReturn;
-
-import java.net.URI;
-import java.net.URISyntaxException;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.extension.ExtendWith;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.test.context.bean.override.mockito.MockitoBean;
-import timeeat.DatabaseCleaner;
-import timeeat.client.oauth.OauthClient;
-import timeeat.client.oauth.OauthMemberInformation;
-import timeeat.client.oauth.OauthToken;
-import timeeat.fixture.MemberGenerator;
-import timeeat.repository.member.MemberRepository;
-
-@ExtendWith(DatabaseCleaner.class)
-@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
-public abstract class BaseServiceTest {
-
- private static final OauthToken DEFAULT_OAUTH_TOKEN = new OauthToken("oauth-access-token");
- private static final OauthMemberInformation DEFAULT_OAUTH_MEMBER_INFO =
- new OauthMemberInformation(123L, "nickname");
-
- @Autowired
- protected MemberGenerator memberGenerator;
-
- @Autowired
- protected MemberRepository memberRepository;
-
- @MockitoBean
- private OauthClient oauthClient;
-
- @BeforeEach
- protected final void mockingClient() throws URISyntaxException {
- doReturn(new URI("http://localhost:8080/login/callback")).when(oauthClient).getOauthLoginUrl(anyString());
- doReturn(DEFAULT_OAUTH_TOKEN).when(oauthClient).requestOauthToken(anyString(), anyString());
- doReturn(DEFAULT_OAUTH_MEMBER_INFO).when(oauthClient).requestMemberInformation(DEFAULT_OAUTH_TOKEN);
- }
-}
diff --git a/src/test/resources/application-flyway.yml b/src/test/resources/application-flyway.yml
new file mode 100644
index 00000000..d04eda7b
--- /dev/null
+++ b/src/test/resources/application-flyway.yml
@@ -0,0 +1,13 @@
+spring:
+ datasource:
+ driver-class-name: org.h2.Driver
+ username: sa
+ password:
+
+ jpa:
+ hibernate:
+ ddl-auto: validate
+
+ flyway:
+ enabled: true
+ baseline-on-migrate: false
diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml
index eb236fc9..62cf1e9b 100644
--- a/src/test/resources/application.yml
+++ b/src/test/resources/application.yml
@@ -11,6 +11,13 @@ spring:
import:
- optional:file:.env-local[.properties]
+ cloud:
+ aws:
+ region:
+ static: ap-northeast-2
+ s3:
+ bucket: eatda-storage-test
+
datasource:
driver-class-name: org.h2.Driver
url: jdbc:h2:mem:database
@@ -45,3 +52,6 @@ oauth:
redirect-path: /callback
allowed-origins:
- "https://example.eat-da.com"
+
+kakao:
+ api-key: ablselfsgbwrlgrwrlhknwnkefknkwelfk
diff --git a/terraform/dev/ec2/main.tf b/terraform/dev/ec2/main.tf
index 0c250ba6..42fe8a70 100644
--- a/terraform/dev/ec2/main.tf
+++ b/terraform/dev/ec2/main.tf
@@ -6,7 +6,7 @@ resource "aws_instance" "dev" {
key_name = var.instance_definitions.key_name
user_data = var.instance_definitions.user_data
vpc_security_group_ids = [var.ec2_sg_id]
- user_data_replace_on_change = true
+ user_data_replace_on_change = false
metadata_options {
http_tokens = "required"
diff --git a/terraform/dev/locals.tf b/terraform/dev/locals.tf
index 7521c329..65750768 100644
--- a/terraform/dev/locals.tf
+++ b/terraform/dev/locals.tf
@@ -23,8 +23,14 @@ locals {
environment = "dev"
name_prefix = "eatda"
+ bucket_name_prefix = "eatda-storage"
+ allowed_origins = [
+ "https://dev.eatda.net",
+ "http://localhost:3000"
+ ]
+
ec2_sg_id = data.terraform_remote_state.common.outputs.security_group_ids["ec2"]
- instance_subnet_map = data.terraform_remote_state.common.outputs.public_subnet_ids # ์๋ฌ๊ฐ ๋ฌ๋ ๋ถ๋ถ
+ instance_subnet_map = data.terraform_remote_state.common.outputs.public_subnet_ids
ecr_repo_urls = data.terraform_remote_state.bootstrap.outputs.ecr_repo_urls
ecs_services = var.ecs_services
alb_target_group_arns = data.terraform_remote_state.common.outputs.target_group_arns
diff --git a/terraform/dev/main.tf b/terraform/dev/main.tf
index 50f03f5a..192a1b09 100644
--- a/terraform/dev/main.tf
+++ b/terraform/dev/main.tf
@@ -16,3 +16,10 @@ module "ecs" {
environment = local.environment
tags = local.common_tags
}
+
+module "s3" {
+ source = "./s3"
+ bucket_name_prefix = local.bucket_name_prefix
+ environment = local.environment
+ allowed_origins = local.allowed_origins
+}
diff --git a/terraform/dev/s3/main.tf b/terraform/dev/s3/main.tf
new file mode 100644
index 00000000..c4253bcf
--- /dev/null
+++ b/terraform/dev/s3/main.tf
@@ -0,0 +1,40 @@
+resource "aws_s3_bucket" "dev" {
+ bucket = "${var.bucket_name_prefix}-${var.environment}"
+
+ tags = {
+ Name = "${var.bucket_name_prefix}-${var.environment}"
+ Environment = var.environment
+ ManagedBy = "Terraform"
+ }
+}
+
+resource "aws_s3_bucket_public_access_block" "dev" {
+ bucket = aws_s3_bucket.dev.id
+
+ block_public_acls = true
+ block_public_policy = true
+ ignore_public_acls = true
+ restrict_public_buckets = true
+}
+
+resource "aws_s3_bucket_server_side_encryption_configuration" "dev" {
+ bucket = aws_s3_bucket.dev.id
+
+ rule {
+ apply_server_side_encryption_by_default {
+ sse_algorithm = "AES256"
+ }
+ }
+}
+
+resource "aws_s3_bucket_cors_configuration" "dev" {
+ bucket = aws_s3_bucket.dev.id
+
+ cors_rule {
+ allowed_headers = ["*"]
+ allowed_methods = ["GET"]
+ allowed_origins = var.allowed_origins
+ expose_headers = ["ETag"]
+ max_age_seconds = 3000
+ }
+}
diff --git a/terraform/dev/s3/outputs.tf b/terraform/dev/s3/outputs.tf
new file mode 100644
index 00000000..e69de29b
diff --git a/terraform/dev/s3/variables.tf b/terraform/dev/s3/variables.tf
new file mode 100644
index 00000000..e2279053
--- /dev/null
+++ b/terraform/dev/s3/variables.tf
@@ -0,0 +1,11 @@
+variable "bucket_name_prefix" {
+ type = string
+}
+
+variable "environment" {
+ type = string
+}
+
+variable "allowed_origins" {
+ type = list(string)
+}
diff --git a/terraform/prod/ec2/main.tf b/terraform/prod/ec2/main.tf
index a48228fd..d5183697 100644
--- a/terraform/prod/ec2/main.tf
+++ b/terraform/prod/ec2/main.tf
@@ -6,7 +6,7 @@ resource "aws_instance" "prod" {
key_name = var.instance_definitions.key_name
user_data = var.instance_definitions.user_data
vpc_security_group_ids = [var.ec2_sg_id]
- user_data_replace_on_change = true
+ user_data_replace_on_change = false
metadata_options {
http_tokens = "required"
@@ -25,4 +25,4 @@ resource "aws_eip" "prod" {
tags = {
Name = "${var.name_prefix}-${var.instance_definitions.role}-eip"
}
-}
\ No newline at end of file
+}
diff --git a/terraform/prod/locals.tf b/terraform/prod/locals.tf
index a10816fa..3af96ec9 100644
--- a/terraform/prod/locals.tf
+++ b/terraform/prod/locals.tf
@@ -23,6 +23,12 @@ locals {
environment = "prod"
name_prefix = "eatda"
+ bucket_name_prefix = "eatda-storage"
+ allowed_origins = [
+ "https://eatda.net",
+ "https://www.eatda.net"
+ ]
+
ec2_sg_id = data.terraform_remote_state.common.outputs.security_group_ids["ec2"]
instance_definitions = data.terraform_remote_state.common.outputs.instance_profile_name["ec2-to-ecs"]
instance_subnet_map = data.terraform_remote_state.common.outputs.public_subnet_ids
diff --git a/terraform/prod/main.tf b/terraform/prod/main.tf
index b6aea617..ce4a01d3 100644
--- a/terraform/prod/main.tf
+++ b/terraform/prod/main.tf
@@ -46,3 +46,10 @@ module "rds" {
storage_encrypted = true
tags = local.common_tags
}
+
+module "s3" {
+ source = "./s3"
+ bucket_name_prefix = local.bucket_name_prefix
+ environment = local.environment
+ allowed_origins = local.allowed_origins
+}
diff --git a/terraform/prod/s3/main.tf b/terraform/prod/s3/main.tf
new file mode 100644
index 00000000..5fced905
--- /dev/null
+++ b/terraform/prod/s3/main.tf
@@ -0,0 +1,40 @@
+resource "aws_s3_bucket" "prod" {
+ bucket = "${var.bucket_name_prefix}-${var.environment}"
+
+ tags = {
+ Name = "${var.bucket_name_prefix}-${var.environment}"
+ Environment = var.environment
+ ManagedBy = "Terraform"
+ }
+}
+
+resource "aws_s3_bucket_public_access_block" "prod" {
+ bucket = aws_s3_bucket.prod.id
+
+ block_public_acls = true
+ block_public_policy = true
+ ignore_public_acls = true
+ restrict_public_buckets = true
+}
+
+resource "aws_s3_bucket_server_side_encryption_configuration" "prod" {
+ bucket = aws_s3_bucket.prod.id
+
+ rule {
+ apply_server_side_encryption_by_default {
+ sse_algorithm = "AES256"
+ }
+ }
+}
+
+resource "aws_s3_bucket_cors_configuration" "prod" {
+ bucket = aws_s3_bucket.prod.id
+
+ cors_rule {
+ allowed_headers = ["*"]
+ allowed_methods = ["GET"]
+ allowed_origins = var.allowed_origins
+ expose_headers = ["ETag"]
+ max_age_seconds = 3000
+ }
+}
diff --git a/terraform/prod/s3/outputs.tf b/terraform/prod/s3/outputs.tf
new file mode 100644
index 00000000..e69de29b
diff --git a/terraform/prod/s3/variables.tf b/terraform/prod/s3/variables.tf
new file mode 100644
index 00000000..e2279053
--- /dev/null
+++ b/terraform/prod/s3/variables.tf
@@ -0,0 +1,11 @@
+variable "bucket_name_prefix" {
+ type = string
+}
+
+variable "environment" {
+ type = string
+}
+
+variable "allowed_origins" {
+ type = list(string)
+}