diff --git a/.gitmodules b/.gitmodules index 5ad3462f2..3c09b9341 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,15 +2,3 @@ path = backend/src/main/resources/config url = git@github.com:zzimkkong/config.git branch = main -[submodule "s3proxy/src/main/resources/s3proxy-config"] - path = s3proxy/src/main/resources/s3proxy-config - url = git@github.com:zzimkkong/s3proxy-config.git - branch = main -[submodule "backend/src/main/resources/infra-appender"] - path = backend/src/main/resources/infra-appender - url = git@github.com:zzimkkong/infra-appender.git - branch = main -[submodule "s3proxy/src/main/resources/config"] - path = s3proxy/src/main/resources/config - url = git@github.com:zzimkkong/config.git - branch = main diff --git a/backend/build.gradle b/backend/build.gradle index 75d5b6282..304f0ea30 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -3,8 +3,6 @@ plugins { id 'io.spring.dependency-management' version '1.0.11.RELEASE' id "org.asciidoctor.convert" version "1.5.10" id 'java' - id 'jacoco' - id "org.sonarqube" version "3.3" } group = 'com.woowacourse' @@ -26,10 +24,6 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-webflux' - // Redis - implementation 'org.springframework.boot:spring-boot-starter-data-redis' - implementation 'org.springframework.boot:spring-boot-starter-cache:2.5.4' - // Security implementation 'org.springframework.boot:spring-boot-starter-security' testImplementation 'org.springframework.security:spring-security-test' @@ -52,12 +46,6 @@ dependencies { // Jwt implementation 'io.jsonwebtoken:jjwt:0.9.1' - // SvgToPng - implementation 'org.apache.xmlgraphics:batik-all:1.12' - implementation 'org.apache.xmlgraphics:xmlgraphics-commons:2.4' - implementation 'xml-apis:xml-apis:1.4.01' - implementation 'xml-apis:xml-apis-ext:1.3.04' - // Cryptor implementation 'commons-codec:commons-codec:1.15' @@ -75,9 +63,6 @@ dependencies { // Logstash implementation 'net.logstash.logback:logstash-logback-encoder:6.6' - // Kafka Appender - implementation 'com.github.danielwegener:logback-kafka-appender:0.2.0-RC2' - // Reflections implementation 'org.reflections:reflections:0.9.11' } @@ -85,10 +70,6 @@ dependencies { test { outputs.dir snippetsDir useJUnitPlatform() - jacoco { - destinationFile = file("$buildDir/jacoco/jacoco.exec") - } - finalizedBy jacocoTestReport } asciidoctor { @@ -108,56 +89,3 @@ bootJar { into 'static/docs' } } - -jacocoTestReport { - reports { - html.enabled true - xml.enabled true - csv.enabled false - } - finalizedBy jacocoTestCoverageVerification -} - -jacocoTestCoverageVerification { - violationRules { - rule { - element = 'CLASS' - excludes = ["**.exception.**", "**.ControllerAdvice", "**.*ErrorResponse", "**.ValidatorMessage", - "**.ZzimkkongApplication", "**.DataLoader", "**.config.**", - "**.LoginInterceptor", "**.AuthenticationPrincipalArgumentResolver", - "**.slack.**", "**.Slack*", "**.AdminPageController", "**.Warmer*"] - - limit { - counter = 'BRANCH' - value = 'COVEREDRATIO' - minimum = 1.0 - } - - limit { - counter = 'INSTRUCTION' - value = 'COVEREDRATIO' - minimum = 0.9 - } - } - } -} - -project.tasks["jacocoTestCoverageVerification"].finalizedBy "sonarqube" -sonarqube { - def sonarProperties = new Properties() - sonarProperties.load(new FileInputStream(file("src/main/resources/config/sonar.properties"))) - def sonarToken = sonarProperties.getProperty("SONAR_TOKEN") - - properties { - property "sonar.host.url", "http://zzimkkong-service.o-r.kr:8000" - property "sonar.login", sonarToken - property 'sonar.sources', 'src' - property 'sonar.language', 'java' - property 'sonar.projectVersion', '0.0.1-SNAPSHOT' - property 'sonar.sourceEncoding', 'UTF-8' - property 'sonar.coverage.jacoco.xmlReportPaths', 'build/reports/jacoco/test/jacocoTestReport.xml' - property 'sonar.java.binaries', 'build/classes' - property 'sonar.test.inclusions', '**/*Test.java' - property 'sonar.exclusions', '**/*Doc*.java, **/resources/**, **/config/datasource/**, **/DataLoader.java, **/Warmer.java' - } -} diff --git a/backend/src/docs/asciidoc/map.adoc b/backend/src/docs/asciidoc/map.adoc index 42861b9a0..e4b7db0fd 100644 --- a/backend/src/docs/asciidoc/map.adoc +++ b/backend/src/docs/asciidoc/map.adoc @@ -35,3 +35,15 @@ include::{snippets}/map/delete/http-response.adoc[] include::{snippets}/map/getBySharingId/http-request.adoc[] ==== Response include::{snippets}/map/getBySharingId/http-response.adoc[] + +=== 맵별 슬랙알림 url 등록 +==== Request +include::{snippets}/map/slackPost/http-request.adoc[] +==== Response +include::{snippets}/map/slackPost/http-response.adoc[] + +=== 맵별 슬랙알림 url 조회 +==== Request +include::{snippets}/map/slackGet/http-request.adoc[] +==== Response +include::{snippets}/map/slackGet/http-response.adoc[] \ No newline at end of file diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/DataLoader.java b/backend/src/main/java/com/woowacourse/zzimkkong/DataLoader.java index da635b8b3..9276ee639 100644 --- a/backend/src/main/java/com/woowacourse/zzimkkong/DataLoader.java +++ b/backend/src/main/java/com/woowacourse/zzimkkong/DataLoader.java @@ -48,7 +48,7 @@ public void run(String... args) { new Map( "루터회관", "{'id': '1', 'type': 'polyline', 'fill': '', 'stroke': 'rgba(111, 111, 111, 1)', 'points': '['60,250', '1,231', '242,252']', 'd': '[]', 'transform': ''}", - "https://d1dgzmdd5f1fx6.cloudfront.net/thumbnails-local/1.png", + "프론트 강의실1프론트 강의실 2회의실 3회의실 4회의실 51234방송실전화백엔드 강의실회의실 1회의실 25트랙방", pobi) ); diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/ZzimkkongApplication.java b/backend/src/main/java/com/woowacourse/zzimkkong/ZzimkkongApplication.java index e45a1d105..f1902de5e 100644 --- a/backend/src/main/java/com/woowacourse/zzimkkong/ZzimkkongApplication.java +++ b/backend/src/main/java/com/woowacourse/zzimkkong/ZzimkkongApplication.java @@ -5,10 +5,12 @@ import java.util.TimeZone; +import static com.woowacourse.zzimkkong.infrastructure.datetime.TimeZoneUtils.UTC; + @SpringBootApplication public class ZzimkkongApplication { public static void main(String[] args) { - TimeZone.setDefault(TimeZone.getTimeZone("Asia/Seoul")); + TimeZone.setDefault(UTC); SpringApplication.run(ZzimkkongApplication.class, args); } } diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/config/LogAspectConfig.java b/backend/src/main/java/com/woowacourse/zzimkkong/config/LogAspectConfig.java deleted file mode 100644 index 29f556200..000000000 --- a/backend/src/main/java/com/woowacourse/zzimkkong/config/LogAspectConfig.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.woowacourse.zzimkkong.config; - -import com.woowacourse.zzimkkong.config.logaspect.LogAspectConfigurer; -import org.reflections.Reflections; -import org.springframework.context.annotation.Configuration; - -import java.util.Set; - -@Configuration -public class LogAspectConfig extends LogAspectConfigurer { - @Override - protected void registerBeans(LogRegistry logRegistry) { - Reflections reflections = new Reflections("com.woowacourse.zzimkkong"); - Set> typesAnnotatedWith = reflections.getTypesAnnotatedWith(com.woowacourse.zzimkkong.config.logaspect.LogRegistry.class); - - for (Class clazz : typesAnnotatedWith) { - String logGroup = clazz.getAnnotation(com.woowacourse.zzimkkong.config.logaspect.LogRegistry.class).group(); - logRegistry.add(clazz, logGroup); - } - } -} diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/config/LogConfig.java b/backend/src/main/java/com/woowacourse/zzimkkong/config/LogConfig.java new file mode 100644 index 000000000..b44ec67e3 --- /dev/null +++ b/backend/src/main/java/com/woowacourse/zzimkkong/config/LogConfig.java @@ -0,0 +1,21 @@ +package com.woowacourse.zzimkkong.config; + +import com.woowacourse.zzimkkong.config.logaspect.LogTraceIdInterceptor; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class LogConfig implements WebMvcConfigurer { + private final LogTraceIdInterceptor logTraceIdInterceptor; + + public LogConfig(final LogTraceIdInterceptor logTraceIdInterceptor) { + this.logTraceIdInterceptor = logTraceIdInterceptor; + } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(logTraceIdInterceptor) + .addPathPatterns("/**"); + } +} diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/config/S3ProxyConfig.java b/backend/src/main/java/com/woowacourse/zzimkkong/config/S3ProxyConfig.java deleted file mode 100644 index 68825ba00..000000000 --- a/backend/src/main/java/com/woowacourse/zzimkkong/config/S3ProxyConfig.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.woowacourse.zzimkkong.config; - -import com.woowacourse.zzimkkong.infrastructure.thumbnail.S3ProxyUploader; -import com.woowacourse.zzimkkong.infrastructure.thumbnail.StorageUploader; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Profile; -import org.springframework.context.annotation.PropertySource; -import org.springframework.web.reactive.function.client.WebClient; - -@Configuration -@PropertySource("classpath:config/s3proxy.properties") -public class S3ProxyConfig { - private final WebClient webClient; - - public S3ProxyConfig(final WebClient webClient) { - this.webClient = webClient; - } - - @Bean(name = "storageUploader") - @Profile("prod") - public StorageUploader storageUploaderProd( - @Value("${s3proxy.server-uri.prod}") final String serverUri, - @Value("${s3proxy.secret-key.prod}") final String secretKey) { - return new S3ProxyUploader(serverUri, secretKey, webClient); - } - - @Bean(name = "storageUploader") - @Profile({"dev", "local", "test"}) - public StorageUploader storageUploaderDev( - @Value("${s3proxy.server-uri.dev}") final String serverUri, - @Value("${s3proxy.secret-key.dev}") final String secretKey) { - return new S3ProxyUploader(serverUri, secretKey, webClient); - } -} diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/config/SlackConfig.java b/backend/src/main/java/com/woowacourse/zzimkkong/config/SlackConfig.java deleted file mode 100644 index 82317f2bc..000000000 --- a/backend/src/main/java/com/woowacourse/zzimkkong/config/SlackConfig.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.woowacourse.zzimkkong.config; - -import com.woowacourse.zzimkkong.domain.SlackUrl; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Profile; -import org.springframework.context.annotation.PropertySource; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -@Configuration -@PropertySource("classpath:config/slack.properties") -public class SlackConfig implements WebMvcConfigurer { - @Bean - @Profile("prod") - public SlackUrl slackUrlProd( - @Value("${slack.webhook.prod}") final String prodUrl) { - return new SlackUrl(prodUrl); - } - - @Bean - @Profile({"local", "dev"}) - public SlackUrl slackUrlDev( - @Value("${slack.webhook.local}") final String devUrl) { - return new SlackUrl(devUrl); - } - - @Bean - @Profile("test") - public SlackUrl slackUrlTest( - @Value("${slack.webhook.test}") final String testUrl) { - return new SlackUrl(testUrl); - } -} diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/config/datasource/CustomDataSourceConfig.java b/backend/src/main/java/com/woowacourse/zzimkkong/config/datasource/CustomDataSourceConfig.java deleted file mode 100644 index 2bec4c289..000000000 --- a/backend/src/main/java/com/woowacourse/zzimkkong/config/datasource/CustomDataSourceConfig.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.woowacourse.zzimkkong.config.datasource; - -import com.zaxxer.hikari.HikariDataSource; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.jdbc.DataSourceBuilder; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Primary; -import org.springframework.context.annotation.Profile; -import org.springframework.data.jpa.repository.config.EnableJpaRepositories; -import org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy; -import org.springframework.transaction.annotation.EnableTransactionManagement; - -import javax.sql.DataSource; -import java.util.*; - -import static com.woowacourse.zzimkkong.config.datasource.ReplicationRoutingDataSource.DATASOURCE_KEY_MASTER; -import static com.woowacourse.zzimkkong.config.datasource.ReplicationRoutingDataSource.DATASOURCE_KEY_SLAVE; - -@Configuration -@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class}) -@EnableTransactionManagement -@EnableJpaRepositories(basePackages = {"com.woowacourse.zzimkkong"}) -@Profile("prod") -public class CustomDataSourceConfig { - - @Bean - @ConfigurationProperties(prefix = "spring.datasource.hikari.master") - public DataSource masterDataSource() { - return DataSourceBuilder.create().type(HikariDataSource.class).build(); - } - - @Bean - @ConfigurationProperties(prefix = "spring.datasource.hikari.slave") - public DataSource slaveDataSource() { - return DataSourceBuilder.create().type(HikariDataSource.class).build(); - } - - @Bean - public DataSource routingDataSource(@Qualifier("masterDataSource") DataSource master, - @Qualifier("slaveDataSource") DataSource slave) { - ReplicationRoutingDataSource routingDataSource = new ReplicationRoutingDataSource(); - - HashMap sources = new HashMap<>(); - sources.put(DATASOURCE_KEY_MASTER, master); - sources.put(DATASOURCE_KEY_SLAVE, slave); - - routingDataSource.setTargetDataSources(sources); - routingDataSource.setDefaultTargetDataSource(master); - - return routingDataSource; - } - - @Primary - @Bean - public DataSource dataSource(@Qualifier("routingDataSource") DataSource routingDataSource) { - return new LazyConnectionDataSourceProxy(routingDataSource); - } -} diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/config/datasource/ReplicationRoutingDataSource.java b/backend/src/main/java/com/woowacourse/zzimkkong/config/datasource/ReplicationRoutingDataSource.java deleted file mode 100644 index 9e5f19c89..000000000 --- a/backend/src/main/java/com/woowacourse/zzimkkong/config/datasource/ReplicationRoutingDataSource.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.woowacourse.zzimkkong.config.datasource; - -import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; -import org.springframework.transaction.support.TransactionSynchronizationManager; - -public class ReplicationRoutingDataSource extends AbstractRoutingDataSource { - public static final String DATASOURCE_KEY_MASTER = "master"; - public static final String DATASOURCE_KEY_SLAVE = "slave"; - - @Override - protected Object determineCurrentLookupKey() { - boolean isReadOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly(); - if (isReadOnly) { - logger.info("Connection Slave"); - return DATASOURCE_KEY_SLAVE; - } else { - logger.info("Connection Master"); - return DATASOURCE_KEY_MASTER; - } - } -} diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/config/logaspect/ExecutionTimeLogAdvice.java b/backend/src/main/java/com/woowacourse/zzimkkong/config/logaspect/ExecutionTimeLogAdvice.java new file mode 100644 index 000000000..f755213d8 --- /dev/null +++ b/backend/src/main/java/com/woowacourse/zzimkkong/config/logaspect/ExecutionTimeLogAdvice.java @@ -0,0 +1,31 @@ +package com.woowacourse.zzimkkong.config.logaspect; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; + +import java.lang.reflect.Method; + +public class ExecutionTimeLogAdvice implements MethodInterceptor { + private final LogAspect logAspect; + private final Class typeToLog; + private final String logGroup; + + protected ExecutionTimeLogAdvice(final LogAspect logAspect, final Class typeToLog, final String logGroup) { + this.logAspect = logAspect; + this.typeToLog = typeToLog; + this.logGroup = logGroup; + } + + @Override + public Object invoke(MethodInvocation invocation) throws Throwable { + long startTime = System.currentTimeMillis(); + final Object result = invocation.proceed(); + long endTime = System.currentTimeMillis(); + long timeTaken = endTime - startTime; + + Method method = invocation.getMethod(); + logAspect.logExecutionInfo(typeToLog, method, timeTaken, logGroup); + + return result; + } +} diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/config/logaspect/LogRegistry.java b/backend/src/main/java/com/woowacourse/zzimkkong/config/logaspect/FindInstanceAndCreateLogProxy.java similarity index 85% rename from backend/src/main/java/com/woowacourse/zzimkkong/config/logaspect/LogRegistry.java rename to backend/src/main/java/com/woowacourse/zzimkkong/config/logaspect/FindInstanceAndCreateLogProxy.java index 72964a72c..fafee396b 100644 --- a/backend/src/main/java/com/woowacourse/zzimkkong/config/logaspect/LogRegistry.java +++ b/backend/src/main/java/com/woowacourse/zzimkkong/config/logaspect/FindInstanceAndCreateLogProxy.java @@ -7,6 +7,6 @@ @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) -public @interface LogRegistry { +public @interface FindInstanceAndCreateLogProxy { String group(); } diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/config/logaspect/LogAspect.java b/backend/src/main/java/com/woowacourse/zzimkkong/config/logaspect/LogAspect.java index 6a68e001b..b2dd48470 100644 --- a/backend/src/main/java/com/woowacourse/zzimkkong/config/logaspect/LogAspect.java +++ b/backend/src/main/java/com/woowacourse/zzimkkong/config/logaspect/LogAspect.java @@ -4,12 +4,14 @@ import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; +import org.slf4j.MDC; +import org.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor; +import org.springframework.aop.framework.ProxyFactory; import org.springframework.stereotype.Component; -import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; -import java.lang.reflect.Proxy; import static net.logstash.logback.argument.StructuredArguments.value; @@ -17,8 +19,10 @@ @Component @Aspect public class LogAspect { - @Around("@within(com.woowacourse.zzimkkong.config.logaspect.LogMethodExecutionTime)" + - "&& execution(public * *.*(..))") + public static final String ALL_ZZIMKKONG_PUBLIC_METHOD_POINTCUT_EXPRESSION = "execution(public * com.woowacourse.zzimkkong..*(..))"; + + @Around("@target(com.woowacourse.zzimkkong.config.logaspect.LogMethodExecutionTime)" + + "&& allZzimkkongPublicMethod()") public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable { long startTime = System.currentTimeMillis(); Object result = joinPoint.proceed(); @@ -34,55 +38,43 @@ public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable { return result; } - private String getLogGroupFromAnnotation(ProceedingJoinPoint joinPoint) { - Class targetClass = joinPoint.getTarget().getClass(); - return targetClass.getAnnotation(LogMethodExecutionTime.class).group(); - } - - private static void logExecutionInfo(MethodSignature methodSignature, long timeTaken, String logGroup) { + void logExecutionInfo(MethodSignature methodSignature, long timeTaken, String logGroup) { final Class declaringType = methodSignature.getDeclaringType(); final Method method = methodSignature.getMethod(); logExecutionInfo(declaringType, method, timeTaken, logGroup); } - private static void logExecutionInfo(Class declaringType, Method method, long timeTaken, String logGroup) { - log.info("{} took {} ms. (info group by '{}')", - value("method", declaringType.getName() + "." + method.getName() + "()"), + void logExecutionInfo(Class typeToLog, Method method, long timeTaken, String logGroup) { + String traceId = MDC.get("traceId"); + + log.info("{} took {} ms. (info group: '{}', traceId: {})", + value("method", typeToLog.getName() + "." + method.getName() + "()"), value("execution_time", timeTaken), - value("group", logGroup)); + value("group", logGroup), + value("traceId", traceId)); } - static T createLogProxy(Object target, Class requiredType, String logGroup) { - final LogProxyHandler logProxyHandler = new LogProxyHandler(target, requiredType, logGroup); - return requiredType.cast( - Proxy.newProxyInstance( - requiredType.getClassLoader(), - new Class[]{requiredType}, - logProxyHandler)); - } + Object createLogProxy(Object target, Class typeToLog, String logGroup) { + AspectJExpressionPointcutAdvisor advisor = new AspectJExpressionPointcutAdvisor(); + advisor.setExpression(ALL_ZZIMKKONG_PUBLIC_METHOD_POINTCUT_EXPRESSION); - private static class LogProxyHandler implements InvocationHandler { - private final Object target; - private final Class declaringType; - private final String logGroup; + ExecutionTimeLogAdvice advice = new ExecutionTimeLogAdvice(this, typeToLog, logGroup); + advisor.setAdvice(advice); - public LogProxyHandler(Object target, Class declaringType, String logGroup) { - this.target = target; - this.declaringType = declaringType; - this.logGroup = logGroup; - } + ProxyFactory proxyFactory = new ProxyFactory(target); + proxyFactory.addAdvisor(advisor); + proxyFactory.setProxyTargetClass(true); - @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - long startTime = System.currentTimeMillis(); - final Object invokeResult = method.invoke(target, args); - long endTime = System.currentTimeMillis(); - long timeTaken = endTime - startTime; + return proxyFactory.getProxy(); + } - logExecutionInfo(declaringType, method, timeTaken, logGroup); + private String getLogGroupFromAnnotation(ProceedingJoinPoint joinPoint) { + Class targetClass = joinPoint.getTarget().getClass(); + return targetClass.getAnnotation(LogMethodExecutionTime.class).group(); + } - return invokeResult; - } + @Pointcut(ALL_ZZIMKKONG_PUBLIC_METHOD_POINTCUT_EXPRESSION) + private void allZzimkkongPublicMethod() { } } diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/config/logaspect/LogAspectConfigurer.java b/backend/src/main/java/com/woowacourse/zzimkkong/config/logaspect/LogAspectConfigurer.java deleted file mode 100644 index 0a4e6c1f7..000000000 --- a/backend/src/main/java/com/woowacourse/zzimkkong/config/logaspect/LogAspectConfigurer.java +++ /dev/null @@ -1,91 +0,0 @@ -package com.woowacourse.zzimkkong.config.logaspect; - -import com.woowacourse.zzimkkong.exception.config.logaspect.InvalidModifiableBeanFactoryException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; - -import javax.annotation.PostConstruct; -import java.util.ArrayList; -import java.util.List; - -public abstract class LogAspectConfigurer { - private ConfigurableListableBeanFactory beanFactory; - private BeanDefinitionRegistry beanDefinitionRegistry; - private final LogRegistry logRegistry = new LogRegistry(); - - @Autowired - public final void setBeanFactory(ConfigurableListableBeanFactory beanFactory) { - this.beanFactory = beanFactory; - if (!(beanFactory instanceof BeanDefinitionRegistry)) { - throw new InvalidModifiableBeanFactoryException(); - } - this.beanDefinitionRegistry = (BeanDefinitionRegistry) beanFactory; - } - - @PostConstruct - protected final void init() { - registerBeans(logRegistry); - - final List logTargetEntries = logRegistry.getLogTargetEntries(); - - for (LogTargetEntry logTargetEntry : logTargetEntries) { - replaceByProxy(logTargetEntry.getTargetClass(), logTargetEntry.getLogGroup()); - } - } - - abstract protected void registerBeans(final LogRegistry logRegistry); - - private void replaceByProxy(Class targetClass, String logGroupName) { - String[] beanNames = beanFactory.getBeanNamesForType(targetClass); - - for (String beanName : beanNames) { - replaceByProxy(beanName, targetClass, logGroupName); - } - } - - private void replaceByProxy(String beanName, Class targetClass, String logGroupName) { - final Object target = beanFactory.getBean(beanName); - final BeanDefinition targetBeanDefinition = beanFactory.getBeanDefinition(beanName); - - beanDefinitionRegistry.removeBeanDefinition(beanName); - beanDefinitionRegistry.registerBeanDefinition(beanName, targetBeanDefinition); - - final Object logProxy = LogAspect.createLogProxy(target, targetClass, logGroupName); - beanFactory.registerSingleton(beanName, logProxy); - } - - public final static class LogRegistry { - private final List entries = new ArrayList<>(); - - private LogRegistry() { - } - - public void add(Class clazz, String logGroup) { - this.entries.add(new LogTargetEntry(clazz, logGroup)); - } - - private List getLogTargetEntries() { - return entries; - } - } - - private static class LogTargetEntry { - Class targetClass; - String logGroup; - - private LogTargetEntry(Class targetClass, String logGroup) { - this.targetClass = targetClass; - this.logGroup = logGroup; - } - - private Class getTargetClass() { - return targetClass; - } - - private String getLogGroup() { - return logGroup; - } - } -} diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/config/logaspect/LogProxyPostProcessor.java b/backend/src/main/java/com/woowacourse/zzimkkong/config/logaspect/LogProxyPostProcessor.java new file mode 100644 index 000000000..ce220e8ef --- /dev/null +++ b/backend/src/main/java/com/woowacourse/zzimkkong/config/logaspect/LogProxyPostProcessor.java @@ -0,0 +1,37 @@ +package com.woowacourse.zzimkkong.config.logaspect; + +import org.reflections.Reflections; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.stereotype.Component; + +import java.util.Collections; +import java.util.Set; + +@Component +public class LogProxyPostProcessor implements BeanPostProcessor { + private final LogAspect logAspect; + private final Set> typesAnnotatedWith; + + protected LogProxyPostProcessor(final LogAspect logAspect) { + this.logAspect = logAspect; + + Reflections reflections = new Reflections("com.woowacourse.zzimkkong"); + typesAnnotatedWith = Collections.unmodifiableSet(reflections.getTypesAnnotatedWith(FindInstanceAndCreateLogProxy.class)); + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + return typesAnnotatedWith.stream() + .filter(typeToLog -> typeToLog.isAssignableFrom(bean.getClass())) + .findAny() + .map(typeToLog -> createLogProxy(bean, typeToLog)) + .orElse(bean); + } + + private Object createLogProxy(Object bean, Class typeToLog) { + FindInstanceAndCreateLogProxy annotation = typeToLog.getAnnotation(FindInstanceAndCreateLogProxy.class); + String groupName = annotation.group(); + return logAspect.createLogProxy(bean, typeToLog, groupName); + } +} diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/config/logaspect/LogTraceIdInterceptor.java b/backend/src/main/java/com/woowacourse/zzimkkong/config/logaspect/LogTraceIdInterceptor.java new file mode 100644 index 000000000..b135f4d05 --- /dev/null +++ b/backend/src/main/java/com/woowacourse/zzimkkong/config/logaspect/LogTraceIdInterceptor.java @@ -0,0 +1,32 @@ +package com.woowacourse.zzimkkong.config.logaspect; + +import lombok.extern.slf4j.Slf4j; +import org.slf4j.MDC; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.ModelAndView; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.UUID; + +@Slf4j +@Component +public class LogTraceIdInterceptor implements HandlerInterceptor { + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + String traceId = generateTraceId(); + MDC.put("traceId", traceId); + return true; + } + + @Override + public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { + MDC.clear(); + } + + private String generateTraceId() { + return UUID.randomUUID().toString().substring(24); + } +} diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/config/redis/RedisCachingConfiguration.java b/backend/src/main/java/com/woowacourse/zzimkkong/config/redis/RedisCachingConfiguration.java deleted file mode 100644 index 178e31601..000000000 --- a/backend/src/main/java/com/woowacourse/zzimkkong/config/redis/RedisCachingConfiguration.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.woowacourse.zzimkkong.config.redis; - -import com.woowacourse.zzimkkong.dto.map.MapFindResponse; -import org.springframework.cache.CacheManager; -import org.springframework.cache.annotation.EnableCaching; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Profile; -import org.springframework.data.redis.cache.RedisCacheConfiguration; -import org.springframework.data.redis.cache.RedisCacheManager; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; -import org.springframework.data.redis.serializer.RedisSerializationContext; -import org.springframework.data.redis.serializer.StringRedisSerializer; - -import java.time.Duration; - -@EnableCaching -@Configuration -@Profile("prod") -public class RedisCachingConfiguration { - private final RedisConnectionFactory redisConnectionFactory; - - public RedisCachingConfiguration(RedisConnectionFactory redisConnectionFactory) { - this.redisConnectionFactory = redisConnectionFactory; - } - - @Bean(name = "cacheManager") - public CacheManager redisCacheManager() { - RedisCacheConfiguration redisCachingConfiguration = RedisCacheConfiguration.defaultCacheConfig() - .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())) - .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer( - new Jackson2JsonRedisSerializer<>(MapFindResponse.class))) - .entryTtl(Duration.ofDays(1)); - - return RedisCacheManager.RedisCacheManagerBuilder - .fromConnectionFactory(redisConnectionFactory) - .cacheDefaults(redisCachingConfiguration).build(); - } -} diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/config/redis/RedisConfiguration.java b/backend/src/main/java/com/woowacourse/zzimkkong/config/redis/RedisConfiguration.java deleted file mode 100644 index 37f428a16..000000000 --- a/backend/src/main/java/com/woowacourse/zzimkkong/config/redis/RedisConfiguration.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.woowacourse.zzimkkong.config.redis; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Profile; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.connection.RedisStandaloneConfiguration; -import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.repository.configuration.EnableRedisRepositories; - -@EnableRedisRepositories -@Configuration -@Profile("prod") -public class RedisConfiguration { - private final String host; - private final String password; - private final int port; - - public RedisConfiguration( - @Value("${spring.redis.host}") String host, - @Value("${spring.redis.password}") String password, - @Value("${spring.redis.port}") int port) { - this.host = host; - this.password = password; - this.port = port; - } - - @Bean - public RedisConnectionFactory redisConnectionFactory() { - RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration(host, port); - configuration.setPassword(password); - return new LettuceConnectionFactory(configuration); - } - - @Bean - public RedisTemplate redisTemplate() { - RedisTemplate redisTemplate = new RedisTemplate<>(); - redisTemplate.setConnectionFactory(redisConnectionFactory()); - return redisTemplate; - } -} diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/config/warmup/WarmUpScheduler.java b/backend/src/main/java/com/woowacourse/zzimkkong/config/warmup/WarmUpScheduler.java deleted file mode 100644 index 120976f30..000000000 --- a/backend/src/main/java/com/woowacourse/zzimkkong/config/warmup/WarmUpScheduler.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.woowacourse.zzimkkong.config.warmup; - -import com.woowacourse.zzimkkong.infrastructure.warmup.Warmer; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Profile; -import org.springframework.scheduling.annotation.EnableScheduling; -import org.springframework.scheduling.annotation.Scheduled; - -import javax.annotation.PostConstruct; - -@EnableScheduling -@Configuration -@Profile("!test") -public class WarmUpScheduler { - private Warmer warmer; - - public WarmUpScheduler(final Warmer warmer) { - this.warmer = warmer; - } - - @Scheduled(fixedDelay = 60 * 60 * 1000) - public void warmUp() { - warmer.warmUp(); - } -} diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/config/warmup/WarmerConfig.java b/backend/src/main/java/com/woowacourse/zzimkkong/config/warmup/WarmerConfig.java deleted file mode 100644 index de3cf4dd5..000000000 --- a/backend/src/main/java/com/woowacourse/zzimkkong/config/warmup/WarmerConfig.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.woowacourse.zzimkkong.config.warmup; - -import com.woowacourse.zzimkkong.domain.SlackUrl; -import com.woowacourse.zzimkkong.infrastructure.thumbnail.BatikConverter; -import com.woowacourse.zzimkkong.infrastructure.warmup.Warmer; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Profile; -import org.springframework.context.annotation.PropertySource; -import org.springframework.web.reactive.function.client.WebClient; - -@Configuration -@PropertySource("classpath:config/s3proxy.properties") -@Profile("!test") -public class WarmerConfig { - private final BatikConverter batikConverter; - private final SlackUrl slackUrl; - private final WebClient webClient; - - public WarmerConfig(final BatikConverter batikConverter, final SlackUrl slackUrl, final WebClient webClient) { - this.batikConverter = batikConverter; - this.slackUrl = slackUrl; - this.webClient = webClient; - } - - @Bean(name = "warmer") - @Profile("prod") - public Warmer warmerProd( - @Value("${s3proxy.server-uri.prod}") final String serverUri, - @Value("${s3proxy.secret-key.prod}") final String secretKey) { - return new Warmer(batikConverter, slackUrl, webClient, serverUri, secretKey); - } - - @Bean(name = "warmer") - @Profile({"dev", "local"}) - public Warmer warmerDev( - @Value("${s3proxy.server-uri.dev}") final String serverUri, - @Value("${s3proxy.secret-key.dev}") final String secretKey) { - return new Warmer(batikConverter, slackUrl, webClient, serverUri, secretKey); - } -} diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/controller/ControllerAdvice.java b/backend/src/main/java/com/woowacourse/zzimkkong/controller/ControllerAdvice.java index a8ffd06a0..ef7896049 100644 --- a/backend/src/main/java/com/woowacourse/zzimkkong/controller/ControllerAdvice.java +++ b/backend/src/main/java/com/woowacourse/zzimkkong/controller/ControllerAdvice.java @@ -9,24 +9,36 @@ import com.woowacourse.zzimkkong.exception.infrastructure.InfrastructureMalfunctionException; import com.woowacourse.zzimkkong.exception.member.NoSuchOAuthMemberException; import lombok.extern.slf4j.Slf4j; +import org.slf4j.MDC; import org.springframework.dao.DataAccessException; import org.springframework.http.ResponseEntity; import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.security.web.firewall.RequestRejectedException; +import org.springframework.security.web.firewall.RequestRejectedHandler; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import javax.validation.ConstraintViolationException; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; -import static com.woowacourse.zzimkkong.dto.ValidatorMessage.FORMAT_MESSAGE; -import static com.woowacourse.zzimkkong.dto.ValidatorMessage.SERVER_ERROR_MESSAGE; +import static com.woowacourse.zzimkkong.dto.ValidatorMessage.*; +import static net.logstash.logback.argument.StructuredArguments.value; @Slf4j @RestControllerAdvice -public class ControllerAdvice { +public class ControllerAdvice implements RequestRejectedHandler { + private static final String MESSAGE_FORMAT = "{} (traceId: {})"; + private static final String TRACE_ID_KEY = "traceId"; + @ExceptionHandler(NoSuchOAuthMemberException.class) public ResponseEntity oAuthLoginFailHandler(final NoSuchOAuthMemberException exception) { - log.info(exception.getMessage()); + logInfo(exception.getMessage()); return ResponseEntity .status(exception.getStatus()) .body(OAuthLoginFailErrorResponse.from(exception)); @@ -34,7 +46,7 @@ public ResponseEntity oAuthLoginFailHandler(final N @ExceptionHandler(InputFieldException.class) public ResponseEntity inputFieldExceptionHandler(final InputFieldException exception) { - log.info(exception.getMessage()); + logInfo(exception.getMessage()); return ResponseEntity .status(exception.getStatus()) .body(InputFieldErrorResponse.from(exception)); @@ -42,7 +54,7 @@ public ResponseEntity inputFieldExceptionHandler(final @ExceptionHandler(InfrastructureMalfunctionException.class) public ResponseEntity wrongConfigurationOfInfrastructureException(final InfrastructureMalfunctionException exception) { - log.warn(exception.getMessage(), exception); + logWarn(exception.getMessage(), exception); return ResponseEntity .status(exception.getStatus()) .body(ErrorResponse.from(exception)); @@ -50,7 +62,7 @@ public ResponseEntity wrongConfigurationOfInfrastructureException @ExceptionHandler(ZzimkkongException.class) public ResponseEntity zzimkkongExceptionHandler(final ZzimkkongException exception) { - log.info(exception.getMessage()); + logInfo(exception.getMessage()); return ResponseEntity .status(exception.getStatus()) .body(ErrorResponse.from(exception)); @@ -58,31 +70,59 @@ public ResponseEntity zzimkkongExceptionHandler(final ZzimkkongEx @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity invalidArgumentHandler(final MethodArgumentNotValidException exception) { - log.info(exception.getMessage()); + logInfo(exception.getMessage()); return ResponseEntity.badRequest().body(InputFieldErrorResponse.from(exception)); } @ExceptionHandler(ConstraintViolationException.class) public ResponseEntity invalidParamHandler(final ConstraintViolationException exception) { - log.info(exception.getMessage()); + logInfo(exception.getMessage()); return ResponseEntity.badRequest().body(ErrorResponse.from(exception)); } @ExceptionHandler({InvalidFormatException.class, HttpMessageNotReadableException.class}) public ResponseEntity invalidFormatHandler() { - log.info(FORMAT_MESSAGE); + logInfo(FORMAT_MESSAGE); return ResponseEntity.badRequest().body(ErrorResponse.invalidFormat()); } @ExceptionHandler(DataAccessException.class) public ResponseEntity invalidDataAccessHandler(final DataAccessException exception) { - log.warn(SERVER_ERROR_MESSAGE, exception); + logWarn(SERVER_ERROR_MESSAGE, exception); return ResponseEntity.internalServerError().build(); } @ExceptionHandler(Exception.class) public ResponseEntity unhandledExceptionHandler(final Exception exception) { - log.warn(exception.getMessage(), exception); + logWarn(exception.getMessage(), exception); return ResponseEntity.internalServerError().build(); } + + private void logInfo(String message) { + log.info(MESSAGE_FORMAT, + message, + value(TRACE_ID_KEY, getTraceId())); + } + + private void logWarn(String message, Exception exception) { + StringWriter stringWriter = new StringWriter(); + PrintWriter printWriter = new PrintWriter(stringWriter); + + exception.printStackTrace(printWriter); + String stackTrace = stringWriter.toString(); + + log.warn(MESSAGE_FORMAT, + message, + value(TRACE_ID_KEY, getTraceId()), + value("stack_trace", stackTrace)); + } + + private Object getTraceId() { + return MDC.get(TRACE_ID_KEY); + } + + @Override + public void handle(HttpServletRequest request, HttpServletResponse response, RequestRejectedException requestRejectedException) throws IOException, ServletException { + response.sendError(HttpServletResponse.SC_BAD_REQUEST); + } } diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/controller/ManagerReservationController.java b/backend/src/main/java/com/woowacourse/zzimkkong/controller/ManagerReservationController.java index 065302749..1e5031767 100644 --- a/backend/src/main/java/com/woowacourse/zzimkkong/controller/ManagerReservationController.java +++ b/backend/src/main/java/com/woowacourse/zzimkkong/controller/ManagerReservationController.java @@ -2,7 +2,6 @@ import com.woowacourse.zzimkkong.domain.LoginEmail; import com.woowacourse.zzimkkong.config.logaspect.LogMethodExecutionTime; -import com.woowacourse.zzimkkong.domain.Member; import com.woowacourse.zzimkkong.dto.reservation.*; import com.woowacourse.zzimkkong.dto.slack.SlackResponse; import com.woowacourse.zzimkkong.dto.member.LoginEmailDto; diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/controller/MapController.java b/backend/src/main/java/com/woowacourse/zzimkkong/controller/MapController.java index 4beb75e0e..df72c7f9f 100644 --- a/backend/src/main/java/com/woowacourse/zzimkkong/controller/MapController.java +++ b/backend/src/main/java/com/woowacourse/zzimkkong/controller/MapController.java @@ -2,11 +2,7 @@ import com.woowacourse.zzimkkong.domain.LoginEmail; import com.woowacourse.zzimkkong.config.logaspect.LogMethodExecutionTime; -import com.woowacourse.zzimkkong.domain.Member; -import com.woowacourse.zzimkkong.dto.map.MapCreateResponse; -import com.woowacourse.zzimkkong.dto.map.MapCreateUpdateRequest; -import com.woowacourse.zzimkkong.dto.map.MapFindAllResponse; -import com.woowacourse.zzimkkong.dto.map.MapFindResponse; +import com.woowacourse.zzimkkong.dto.map.*; import com.woowacourse.zzimkkong.dto.member.LoginEmailDto; import com.woowacourse.zzimkkong.service.MapService; import org.springframework.http.ResponseEntity; @@ -58,5 +54,22 @@ public ResponseEntity delete(@PathVariable final Long mapId, @LoginEmail f mapService.deleteMap(mapId, loginEmailDto); return ResponseEntity.noContent().build(); } + + @PostMapping("/{mapId}/slack") + public ResponseEntity createSlackUrl( + @PathVariable final Long mapId, + @RequestBody final SlackCreateRequest slackCreateRequest, + @LoginEmail final LoginEmailDto loginEmailDto) { + mapService.saveSlackUrl(mapId, slackCreateRequest, loginEmailDto); + return ResponseEntity.ok().build(); + } + + @GetMapping("/{mapId}/slack") + public ResponseEntity findSlackUrl( + @PathVariable final Long mapId, + @LoginEmail final LoginEmailDto loginEmailDto) { + SlackFindResponse slackFindResponse = mapService.findSlackUrl(mapId, loginEmailDto); + return ResponseEntity.ok().body(slackFindResponse); + } } diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/domain/Map.java b/backend/src/main/java/com/woowacourse/zzimkkong/domain/Map.java index 863dfe519..9e269aeeb 100644 --- a/backend/src/main/java/com/woowacourse/zzimkkong/domain/Map.java +++ b/backend/src/main/java/com/woowacourse/zzimkkong/domain/Map.java @@ -26,7 +26,10 @@ public class Map { @Column(nullable = false) @Lob - private String mapImageUrl; + private String thumbnail; + + @Lob + private String slackUrl; @ManyToOne @JoinColumn(name = "member_id", foreignKey = @ForeignKey(name = "fk_map_member"), nullable = false) @@ -35,10 +38,10 @@ public class Map { @OneToMany(mappedBy = "map", cascade = CascadeType.REMOVE, fetch = FetchType.LAZY, orphanRemoval = true) private List spaces = new ArrayList<>(); - public Map(final String name, final String mapDrawing, final String mapImageUrl, final Member member) { + public Map(final String name, final String mapDrawing, final String thumbnail, final Member member) { this.name = name; this.mapDrawing = mapDrawing; - this.mapImageUrl = mapImageUrl; + this.thumbnail = thumbnail; this.member = member; if (member != null) { @@ -46,8 +49,8 @@ public Map(final String name, final String mapDrawing, final String mapImageUrl, } } - public Map(final Long id, final String name, final String mapDrawing, final String mapImageUrl, final Member member) { - this(name, mapDrawing, mapImageUrl, member); + public Map(final Long id, final String name, final String mapDrawing, final String thumbnail, final Member member) { + this(name, mapDrawing, thumbnail, member); this.id = id; } @@ -71,8 +74,12 @@ public Optional findSpaceById(final Long spaceId) { .findFirst(); } - public void updateImageUrl(final String mapImageUrl) { - this.mapImageUrl = mapImageUrl; + public void updateThumbnail(final String thumbnail) { + this.thumbnail = thumbnail; + } + + public void updateSlackUrl(final String slackUrl) { + this.slackUrl = slackUrl; } public void addSpace(final Space space) { diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/domain/Reservation.java b/backend/src/main/java/com/woowacourse/zzimkkong/domain/Reservation.java index dd4191b85..e01fe8539 100644 --- a/backend/src/main/java/com/woowacourse/zzimkkong/domain/Reservation.java +++ b/backend/src/main/java/com/woowacourse/zzimkkong/domain/Reservation.java @@ -1,5 +1,6 @@ package com.woowacourse.zzimkkong.domain; +import com.woowacourse.zzimkkong.infrastructure.datetime.TimeZoneUtils; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @@ -7,6 +8,9 @@ import javax.persistence.*; import java.time.LocalDate; import java.time.LocalDateTime; +import java.util.TimeZone; + +import static com.woowacourse.zzimkkong.infrastructure.datetime.TimeZoneUtils.UTC; @Getter @Builder @@ -64,26 +68,15 @@ protected Reservation( } public boolean hasConflictWith(final LocalDateTime startDateTime, final LocalDateTime endDateTime) { - boolean contains = contains(startDateTime, endDateTime); - boolean intersects = intersects(startDateTime, endDateTime); - boolean equals = equals(startDateTime, endDateTime); - - return contains || intersects || equals; - } - - private boolean contains(final LocalDateTime startDateTime, final LocalDateTime endDateTime) { - return (startDateTime.isAfter(startTime) && endDateTime.isBefore(endTime)) - || (startDateTime.isEqual(startTime) && endDateTime.isBefore(endTime)) - || (startDateTime.isAfter(startTime) && endDateTime.isEqual(endTime)); + return !(isEarlier(endDateTime) || isLater(startDateTime)); } - private boolean intersects(final LocalDateTime startDateTime, final LocalDateTime endDateTime) { - return (startDateTime.isBefore(startTime) && endDateTime.isAfter(startTime)) - || (endDateTime.isAfter(endTime) && startDateTime.isBefore(endTime)); + private boolean isEarlier(final LocalDateTime endDateTime) { + return endDateTime.equals(this.startTime) || endDateTime.isBefore(this.startTime); } - private boolean equals(final LocalDateTime startDateTime, final LocalDateTime endDateTime) { - return startDateTime.isEqual(startTime) && endDateTime.isEqual(endTime); + private boolean isLater(final LocalDateTime startDateTime) { + return startDateTime.equals(this.endTime) || startDateTime.isAfter(this.endTime); } public void update(final Reservation updateReservation, final Space space) { @@ -98,4 +91,10 @@ public void update(final Reservation updateReservation, final Space space) { public boolean isWrongPassword(final String password) { return !this.password.equals(password); } + + public boolean isBookedOn(final LocalDate date, final TimeZone timeZone) { + LocalDate convertedStartTimeDate = TimeZoneUtils.convert(startTime, UTC, timeZone).toLocalDate(); + LocalDate convertedEndTimeDate = TimeZoneUtils.convert(endTime, UTC, timeZone).toLocalDate(); + return date.equals(convertedStartTimeDate) && date.equals(convertedEndTimeDate); + } } diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/domain/SlackUrl.java b/backend/src/main/java/com/woowacourse/zzimkkong/domain/SlackUrl.java deleted file mode 100644 index 96ec9caa9..000000000 --- a/backend/src/main/java/com/woowacourse/zzimkkong/domain/SlackUrl.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.woowacourse.zzimkkong.domain; - -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Getter -@NoArgsConstructor -public class SlackUrl { - private String url; - - public SlackUrl(final String url) { - this.url = url; - } -} diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/domain/Space.java b/backend/src/main/java/com/woowacourse/zzimkkong/domain/Space.java index 20de01418..e0d509a51 100644 --- a/backend/src/main/java/com/woowacourse/zzimkkong/domain/Space.java +++ b/backend/src/main/java/com/woowacourse/zzimkkong/domain/Space.java @@ -96,8 +96,12 @@ public boolean isNotDivisibleByTimeUnit(final int minute) { return setting.isNotDivisibleByTimeUnit(minute); } - public boolean isIncorrectMinimumMaximumTimeUnit(final int durationMinutes) { - return durationMinutes < getReservationMinimumTimeUnit() || durationMinutes > getReservationMaximumTimeUnit(); + public boolean isIncorrectMinimumTimeUnit(final int durationMinutes) { + return durationMinutes < getReservationMinimumTimeUnit(); + } + + public boolean isIncorrectMaximumTimeUnit(final int durationMinutes) { + return durationMinutes > getReservationMaximumTimeUnit(); } public boolean isUnableToReserve() { diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/dto/ValidatorMessage.java b/backend/src/main/java/com/woowacourse/zzimkkong/dto/ValidatorMessage.java index 77038eccb..1d11c406f 100644 --- a/backend/src/main/java/com/woowacourse/zzimkkong/dto/ValidatorMessage.java +++ b/backend/src/main/java/com/woowacourse/zzimkkong/dto/ValidatorMessage.java @@ -19,7 +19,7 @@ private ValidatorMessage() { public static final String DATE_FORMAT = "yyyy-MM-dd"; public static final String TIME_FORMAT = "HH:mm:ss"; - public static final String DATETIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss"; + public static final String DATETIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ssxxx"; public static final String MEMBER_PW_FORMAT = "^(?=.*[a-zA-Z])(?=.*[0-9]).{8,20}$"; public static final String RESERVATION_PW_FORMAT = "^[0-9]{4}$"; public static final String ORGANIZATION_FORMAT = "^[ a-zA-Z0-9ㄱ-힣]{1,20}$"; diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/dto/map/MapCreateUpdateRequest.java b/backend/src/main/java/com/woowacourse/zzimkkong/dto/map/MapCreateUpdateRequest.java index 94d172cf9..8b4ff174d 100644 --- a/backend/src/main/java/com/woowacourse/zzimkkong/dto/map/MapCreateUpdateRequest.java +++ b/backend/src/main/java/com/woowacourse/zzimkkong/dto/map/MapCreateUpdateRequest.java @@ -17,11 +17,11 @@ public class MapCreateUpdateRequest { private String mapDrawing; @NotBlank(message = EMPTY_MESSAGE) - private String mapImageSvg; + private String thumbnail; - public MapCreateUpdateRequest(final String mapName, final String mapDrawing, final String mapImageSvg) { + public MapCreateUpdateRequest(final String mapName, final String mapDrawing, final String thumbnail) { this.mapName = mapName; this.mapDrawing = mapDrawing; - this.mapImageSvg = mapImageSvg; + this.thumbnail = thumbnail; } } diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/dto/map/MapFindResponse.java b/backend/src/main/java/com/woowacourse/zzimkkong/dto/map/MapFindResponse.java index ea4a04dcc..381cb3d88 100644 --- a/backend/src/main/java/com/woowacourse/zzimkkong/dto/map/MapFindResponse.java +++ b/backend/src/main/java/com/woowacourse/zzimkkong/dto/map/MapFindResponse.java @@ -10,32 +10,32 @@ public class MapFindResponse { private Long mapId; private String mapName; private String mapDrawing; - private String mapImageUrl; + private String thumbnail; private String sharingMapId; private String managerEmail; private MapFindResponse(final Long mapId, final String mapName, final String mapDrawing, - final String mapImageUrl, + final String thumbnail, final String sharingMapId) { this.mapId = mapId; this.mapName = mapName; this.mapDrawing = mapDrawing; - this.mapImageUrl = mapImageUrl; + this.thumbnail = thumbnail; this.sharingMapId = sharingMapId; } private MapFindResponse(final Long mapId, final String mapName, final String mapDrawing, - final String mapImageUrl, + final String thumbnail, final String sharingMapId, final String managerEmail) { this.mapId = mapId; this.mapName = mapName; this.mapDrawing = mapDrawing; - this.mapImageUrl = mapImageUrl; + this.thumbnail = thumbnail; this.sharingMapId = sharingMapId; this.managerEmail = managerEmail; } @@ -46,7 +46,7 @@ public static MapFindResponse of(final Map map, map.getId(), map.getName(), map.getMapDrawing(), - map.getMapImageUrl(), + map.getThumbnail(), sharingMapId ); } @@ -57,7 +57,7 @@ public static MapFindResponse ofAdmin(final Map map, map.getId(), map.getName(), map.getMapDrawing(), - map.getMapImageUrl(), + map.getThumbnail(), sharingMapId, map.getMember().getEmail() ); diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/dto/map/SlackCreateRequest.java b/backend/src/main/java/com/woowacourse/zzimkkong/dto/map/SlackCreateRequest.java new file mode 100644 index 000000000..2985a4a17 --- /dev/null +++ b/backend/src/main/java/com/woowacourse/zzimkkong/dto/map/SlackCreateRequest.java @@ -0,0 +1,14 @@ +package com.woowacourse.zzimkkong.dto.map; + +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class SlackCreateRequest { + private String slackUrl; + + public SlackCreateRequest(final String slackUrl) { + this.slackUrl = slackUrl; + } +} diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/dto/map/SlackFindResponse.java b/backend/src/main/java/com/woowacourse/zzimkkong/dto/map/SlackFindResponse.java new file mode 100644 index 000000000..a3ff4f4ee --- /dev/null +++ b/backend/src/main/java/com/woowacourse/zzimkkong/dto/map/SlackFindResponse.java @@ -0,0 +1,19 @@ +package com.woowacourse.zzimkkong.dto.map; + +import com.woowacourse.zzimkkong.domain.Map; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class SlackFindResponse { + private String slackUrl; + + private SlackFindResponse(final String slackUrl) { + this.slackUrl = slackUrl; + } + + public static SlackFindResponse from(Map map) { + return new SlackFindResponse(map.getSlackUrl()); + } +} diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/dto/reservation/ReservationCreateDto.java b/backend/src/main/java/com/woowacourse/zzimkkong/dto/reservation/ReservationCreateDto.java index ec305aa02..27e202b7d 100644 --- a/backend/src/main/java/com/woowacourse/zzimkkong/dto/reservation/ReservationCreateDto.java +++ b/backend/src/main/java/com/woowacourse/zzimkkong/dto/reservation/ReservationCreateDto.java @@ -25,8 +25,8 @@ protected ReservationCreateDto( final LoginEmailDto loginEmailDto) { this.mapId = mapId; this.spaceId = spaceId; - this.startDateTime = request.getStartDateTime(); - this.endDateTime = request.getEndDateTime(); + this.startDateTime = request.localStartDateTime(); + this.endDateTime = request.localEndDateTime(); this.password = request.getPassword(); this.name = request.getName(); this.description = request.getDescription(); diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/dto/reservation/ReservationCreateResponse.java b/backend/src/main/java/com/woowacourse/zzimkkong/dto/reservation/ReservationCreateResponse.java index 130457fac..56e10f137 100644 --- a/backend/src/main/java/com/woowacourse/zzimkkong/dto/reservation/ReservationCreateResponse.java +++ b/backend/src/main/java/com/woowacourse/zzimkkong/dto/reservation/ReservationCreateResponse.java @@ -16,8 +16,8 @@ private ReservationCreateResponse(final Long id, final SlackResponse slackRespon this.slackResponse = slackResponse; } - public static ReservationCreateResponse of(final Reservation reservation, final String sharingMapId) { - SlackResponse slackResponse = SlackResponse.of(reservation, sharingMapId); + public static ReservationCreateResponse of(final Reservation reservation, final String sharingMapId, final String slackUrl) { + SlackResponse slackResponse = SlackResponse.of(reservation, sharingMapId, slackUrl); return new ReservationCreateResponse(reservation.getId(), slackResponse); } } diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/dto/reservation/ReservationCreateUpdateRequest.java b/backend/src/main/java/com/woowacourse/zzimkkong/dto/reservation/ReservationCreateUpdateRequest.java index 98edaf43e..080927444 100644 --- a/backend/src/main/java/com/woowacourse/zzimkkong/dto/reservation/ReservationCreateUpdateRequest.java +++ b/backend/src/main/java/com/woowacourse/zzimkkong/dto/reservation/ReservationCreateUpdateRequest.java @@ -1,27 +1,31 @@ package com.woowacourse.zzimkkong.dto.reservation; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.woowacourse.zzimkkong.infrastructure.datetime.TimeZoneUtils; import lombok.Getter; import lombok.NoArgsConstructor; -import org.springframework.format.annotation.DateTimeFormat; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import javax.validation.constraints.Pattern; import javax.validation.constraints.Size; import java.time.LocalDateTime; +import java.time.ZonedDateTime; +import java.util.TimeZone; import static com.woowacourse.zzimkkong.dto.ValidatorMessage.*; +import static com.woowacourse.zzimkkong.infrastructure.datetime.TimeZoneUtils.UTC; @Getter @NoArgsConstructor public class ReservationCreateUpdateRequest { - @DateTimeFormat(pattern = DATETIME_FORMAT) + @JsonFormat(pattern = DATETIME_FORMAT) @NotNull(message = EMPTY_MESSAGE) - protected LocalDateTime startDateTime; + protected ZonedDateTime startDateTime; - @DateTimeFormat(pattern = DATETIME_FORMAT) + @JsonFormat(pattern = DATETIME_FORMAT) @NotNull(message = EMPTY_MESSAGE) - protected LocalDateTime endDateTime; + protected ZonedDateTime endDateTime; @NotBlank(message = EMPTY_MESSAGE) @Pattern(regexp = NAMING_FORMAT, message = NAME_MESSAGE) @@ -31,13 +35,13 @@ public class ReservationCreateUpdateRequest { @Size(max = 100, message = DESCRIPTION_MESSAGE) protected String description; - public ReservationCreateUpdateRequest( - final LocalDateTime startDateTime, - final LocalDateTime endDateTime, - final String name, - final String description) { - this.startDateTime = startDateTime; - this.endDateTime = endDateTime; + public ReservationCreateUpdateRequest(ZonedDateTime startDateTime, ZonedDateTime endDateTime, String name, String description) { + if (startDateTime != null) { + this.startDateTime = startDateTime.withZoneSameInstant(UTC.toZoneId()); + } + if (endDateTime != null) { + this.endDateTime = endDateTime.withZoneSameInstant(UTC.toZoneId()); + } this.name = name; this.description = description; } @@ -45,4 +49,12 @@ public ReservationCreateUpdateRequest( public String getPassword() { return null; } + + public LocalDateTime localStartDateTime() { + return startDateTime.toLocalDateTime(); + } + + public LocalDateTime localEndDateTime() { + return endDateTime.toLocalDateTime(); + } } diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/dto/reservation/ReservationCreateUpdateWithPasswordRequest.java b/backend/src/main/java/com/woowacourse/zzimkkong/dto/reservation/ReservationCreateUpdateWithPasswordRequest.java index ca9a0c63a..84012a85f 100644 --- a/backend/src/main/java/com/woowacourse/zzimkkong/dto/reservation/ReservationCreateUpdateWithPasswordRequest.java +++ b/backend/src/main/java/com/woowacourse/zzimkkong/dto/reservation/ReservationCreateUpdateWithPasswordRequest.java @@ -5,7 +5,7 @@ import javax.validation.constraints.NotBlank; import javax.validation.constraints.Pattern; -import java.time.LocalDateTime; +import java.time.ZonedDateTime; import static com.woowacourse.zzimkkong.dto.ValidatorMessage.*; @@ -17,8 +17,8 @@ public class ReservationCreateUpdateWithPasswordRequest extends ReservationCreat private String password; public ReservationCreateUpdateWithPasswordRequest( - final LocalDateTime startDateTime, - final LocalDateTime endDateTime, + final ZonedDateTime startDateTime, + final ZonedDateTime endDateTime, final String password, final String name, final String description) { diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/dto/reservation/ReservationResponse.java b/backend/src/main/java/com/woowacourse/zzimkkong/dto/reservation/ReservationResponse.java index acbc88a9b..cc0003c12 100644 --- a/backend/src/main/java/com/woowacourse/zzimkkong/dto/reservation/ReservationResponse.java +++ b/backend/src/main/java/com/woowacourse/zzimkkong/dto/reservation/ReservationResponse.java @@ -6,12 +6,15 @@ import com.woowacourse.zzimkkong.domain.Member; import com.woowacourse.zzimkkong.domain.Reservation; import com.woowacourse.zzimkkong.domain.Space; +import com.woowacourse.zzimkkong.infrastructure.datetime.TimeZoneUtils; import lombok.Getter; import lombok.NoArgsConstructor; import java.time.LocalDateTime; +import java.time.ZonedDateTime; import static com.woowacourse.zzimkkong.dto.ValidatorMessage.DATETIME_FORMAT; +import static com.woowacourse.zzimkkong.infrastructure.datetime.TimeZoneUtils.UTC; @Getter @NoArgsConstructor @@ -19,9 +22,9 @@ public class ReservationResponse { @JsonProperty private Long id; @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = DATETIME_FORMAT) - private LocalDateTime startDateTime; + private ZonedDateTime startDateTime; @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = DATETIME_FORMAT) - private LocalDateTime endDateTime; + private ZonedDateTime endDateTime; @JsonProperty private String name; @JsonProperty @@ -38,8 +41,8 @@ private ReservationResponse( final String name, final String description) { this.id = id; - this.startDateTime = startDateTime; - this.endDateTime = endDateTime; + this.startDateTime = startDateTime.atZone(UTC.toZoneId()); + this.endDateTime = endDateTime.atZone(UTC.toZoneId()); this.name = name; this.description = description; } diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/dto/slack/SlackResponse.java b/backend/src/main/java/com/woowacourse/zzimkkong/dto/slack/SlackResponse.java index e2d0609ed..d3c5780ed 100644 --- a/backend/src/main/java/com/woowacourse/zzimkkong/dto/slack/SlackResponse.java +++ b/backend/src/main/java/com/woowacourse/zzimkkong/dto/slack/SlackResponse.java @@ -1,6 +1,7 @@ package com.woowacourse.zzimkkong.dto.slack; import com.woowacourse.zzimkkong.domain.Reservation; +import com.woowacourse.zzimkkong.infrastructure.datetime.TimeZoneUtils; import lombok.Getter; import lombok.NoArgsConstructor; @@ -14,6 +15,7 @@ public class SlackResponse { private String reservationTime; private String description; private String sharingMapId; + private String slackUrl; private SlackResponse( final String spaceName, @@ -21,22 +23,34 @@ private SlackResponse( final LocalDateTime startTime, final LocalDateTime endTime, final String description, - final String sharingMapId) { + final String sharingMapId, + final String slackUrl) { this.spaceName = "회의실명 : " + spaceName; this.userName = "예약자명 : " + userName; this.reservationTime = "예약시간 : " + startTime + " ~ " + endTime; this.description = "예약내용 : " + description; this.sharingMapId = sharingMapId; + this.slackUrl = slackUrl; } - public static SlackResponse of(final Reservation reservation, final String sharingMapId) { + public static SlackResponse of(final Reservation reservation, final String sharingMapId, final String slackUrl) { + LocalDateTime reservationStartTimeKST = TimeZoneUtils.convert( + reservation.getStartTime(), + TimeZoneUtils.UTC, + TimeZoneUtils.KST); + LocalDateTime reservationEndTimeKST = TimeZoneUtils.convert( + reservation.getEndTime(), + TimeZoneUtils.UTC, + TimeZoneUtils.KST); + return new SlackResponse( reservation.getSpace().getName(), reservation.getUserName(), - reservation.getStartTime(), - reservation.getEndTime(), + reservationStartTimeKST, + reservationEndTimeKST, reservation.getDescription(), - sharingMapId); + sharingMapId, + slackUrl); } @Override diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/dto/space/SpaceCreateUpdateRequest.java b/backend/src/main/java/com/woowacourse/zzimkkong/dto/space/SpaceCreateUpdateRequest.java index aa198459f..8f1b98073 100644 --- a/backend/src/main/java/com/woowacourse/zzimkkong/dto/space/SpaceCreateUpdateRequest.java +++ b/backend/src/main/java/com/woowacourse/zzimkkong/dto/space/SpaceCreateUpdateRequest.java @@ -24,7 +24,7 @@ public class SpaceCreateUpdateRequest { private String area; @NotBlank(message = EMPTY_MESSAGE) - private String mapImageSvg; + private String thumbnail; @Valid private SettingsRequest settingsRequest; @@ -35,12 +35,12 @@ public SpaceCreateUpdateRequest( final String description, final String area, final SettingsRequest settingsRequest, - final String mapImageSvg) { + final String thumbnail) { this.name = name; this.color = color; this.description = description; this.area = area; this.settingsRequest = settingsRequest; - this.mapImageSvg = mapImageSvg; + this.thumbnail = thumbnail; } } diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/dto/space/SpaceDeleteRequest.java b/backend/src/main/java/com/woowacourse/zzimkkong/dto/space/SpaceDeleteRequest.java index e7cf4ee48..e6f384fb4 100644 --- a/backend/src/main/java/com/woowacourse/zzimkkong/dto/space/SpaceDeleteRequest.java +++ b/backend/src/main/java/com/woowacourse/zzimkkong/dto/space/SpaceDeleteRequest.java @@ -11,9 +11,9 @@ @NoArgsConstructor public class SpaceDeleteRequest { @NotBlank(message = EMPTY_MESSAGE) - private String mapImageSvg; + private String thumbnail; - public SpaceDeleteRequest(final String mapImageSvg) { - this.mapImageSvg = mapImageSvg; + public SpaceDeleteRequest(final String thumbnail) { + this.thumbnail = thumbnail; } } diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/exception/infrastructure/CannotDeleteConvertedFileException.java b/backend/src/main/java/com/woowacourse/zzimkkong/exception/infrastructure/CannotDeleteConvertedFileException.java deleted file mode 100644 index b24d3ef49..000000000 --- a/backend/src/main/java/com/woowacourse/zzimkkong/exception/infrastructure/CannotDeleteConvertedFileException.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.woowacourse.zzimkkong.exception.infrastructure; - -import org.springframework.http.HttpStatus; - -public class CannotDeleteConvertedFileException extends InfrastructureMalfunctionException { - private static final String MESSAGE = "변환된 이미지를 삭제하는 데에 실패했습니다. 관리자에게 문의하세요."; - - public CannotDeleteConvertedFileException() { - super(MESSAGE, HttpStatus.INTERNAL_SERVER_ERROR); - } -} diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/exception/infrastructure/CannotGenerateInputStreamFromSvgDataException.java b/backend/src/main/java/com/woowacourse/zzimkkong/exception/infrastructure/CannotGenerateInputStreamFromSvgDataException.java deleted file mode 100644 index 1ed8fd57a..000000000 --- a/backend/src/main/java/com/woowacourse/zzimkkong/exception/infrastructure/CannotGenerateInputStreamFromSvgDataException.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.woowacourse.zzimkkong.exception.infrastructure; - -import org.springframework.http.HttpStatus; - -public class CannotGenerateInputStreamFromSvgDataException extends InfrastructureMalfunctionException { - private static final String MESSAGE = "svg 데이터를 읽어올 수 없습니다."; - - public CannotGenerateInputStreamFromSvgDataException(Throwable throwable) { - super(MESSAGE, throwable, HttpStatus.BAD_REQUEST); - } -} diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/exception/infrastructure/NoMasterDataSourceException.java b/backend/src/main/java/com/woowacourse/zzimkkong/exception/infrastructure/NoMasterDataSourceException.java deleted file mode 100644 index 81f47ac2d..000000000 --- a/backend/src/main/java/com/woowacourse/zzimkkong/exception/infrastructure/NoMasterDataSourceException.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.woowacourse.zzimkkong.exception.infrastructure; - -import com.woowacourse.zzimkkong.exception.ZzimkkongException; -import org.springframework.http.HttpStatus; - -public class NoMasterDataSourceException extends ZzimkkongException { - private static final String MESSAGE = "Master DB의 DataSource 설정이 올바르지 않습니다."; - - public NoMasterDataSourceException() { - super(MESSAGE, HttpStatus.INTERNAL_SERVER_ERROR); - } -} diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/exception/infrastructure/S3ProxyRespondedFailException.java b/backend/src/main/java/com/woowacourse/zzimkkong/exception/infrastructure/S3ProxyRespondedFailException.java deleted file mode 100644 index 831b73242..000000000 --- a/backend/src/main/java/com/woowacourse/zzimkkong/exception/infrastructure/S3ProxyRespondedFailException.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.woowacourse.zzimkkong.exception.infrastructure; - -import org.springframework.http.HttpStatus; - -public class S3ProxyRespondedFailException extends InfrastructureMalfunctionException { - private static final String MESSAGE = "이미지 버킷 업로드에 실패했습니다."; - - public S3ProxyRespondedFailException() { - super(MESSAGE, HttpStatus.INTERNAL_SERVER_ERROR); - } -} diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/exception/infrastructure/S3UploadException.java b/backend/src/main/java/com/woowacourse/zzimkkong/exception/infrastructure/S3UploadException.java deleted file mode 100644 index d705eb2db..000000000 --- a/backend/src/main/java/com/woowacourse/zzimkkong/exception/infrastructure/S3UploadException.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.woowacourse.zzimkkong.exception.infrastructure; - -import org.springframework.http.HttpStatus; - -public class S3UploadException extends InfrastructureMalfunctionException { - private static final String MESSAGE = "이미지 버킷 업로드에 실패했습니다."; - - public S3UploadException() { - super(MESSAGE, HttpStatus.INTERNAL_SERVER_ERROR); - } - - public S3UploadException(final Exception exception) { - super(MESSAGE, exception, HttpStatus.INTERNAL_SERVER_ERROR); - } -} diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/exception/infrastructure/SvgToPngConvertException.java b/backend/src/main/java/com/woowacourse/zzimkkong/exception/infrastructure/SvgToPngConvertException.java deleted file mode 100644 index 1b5ad60bf..000000000 --- a/backend/src/main/java/com/woowacourse/zzimkkong/exception/infrastructure/SvgToPngConvertException.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.woowacourse.zzimkkong.exception.infrastructure; - -import org.springframework.http.HttpStatus; - -public class SvgToPngConvertException extends InfrastructureMalfunctionException { - private static final String MESSAGE = "svg 데이터를 png 파일로 변환할 수 없습니다. 데이터 형식을 확인해주세요."; - - public SvgToPngConvertException(final Exception exception) { - super(MESSAGE, exception, HttpStatus.BAD_REQUEST); - } -} diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/exception/reservation/InvalidDayOfWeekException.java b/backend/src/main/java/com/woowacourse/zzimkkong/exception/reservation/InvalidDayOfWeekException.java index b83765cb9..69c1e4741 100644 --- a/backend/src/main/java/com/woowacourse/zzimkkong/exception/reservation/InvalidDayOfWeekException.java +++ b/backend/src/main/java/com/woowacourse/zzimkkong/exception/reservation/InvalidDayOfWeekException.java @@ -1,4 +1,12 @@ package com.woowacourse.zzimkkong.exception.reservation; -public class InvalidDayOfWeekException extends ConflictSpaceSettingException { +import com.woowacourse.zzimkkong.exception.ZzimkkongException; +import org.springframework.http.HttpStatus; + +public class InvalidDayOfWeekException extends ZzimkkongException { + private static final String MESSAGE = "해당 요일에 예약이 불가능한 공간입니다."; + + public InvalidDayOfWeekException() { + super(MESSAGE, HttpStatus.BAD_REQUEST); + } } diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/exception/reservation/InvalidDurationTimeException.java b/backend/src/main/java/com/woowacourse/zzimkkong/exception/reservation/InvalidDurationTimeException.java deleted file mode 100644 index 954aa253d..000000000 --- a/backend/src/main/java/com/woowacourse/zzimkkong/exception/reservation/InvalidDurationTimeException.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.woowacourse.zzimkkong.exception.reservation; - -public class InvalidDurationTimeException extends ConflictSpaceSettingException { -} diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/exception/reservation/ConflictSpaceSettingException.java b/backend/src/main/java/com/woowacourse/zzimkkong/exception/reservation/InvalidMaximumDurationTimeException.java similarity index 50% rename from backend/src/main/java/com/woowacourse/zzimkkong/exception/reservation/ConflictSpaceSettingException.java rename to backend/src/main/java/com/woowacourse/zzimkkong/exception/reservation/InvalidMaximumDurationTimeException.java index d1a7917e5..5e9962843 100644 --- a/backend/src/main/java/com/woowacourse/zzimkkong/exception/reservation/ConflictSpaceSettingException.java +++ b/backend/src/main/java/com/woowacourse/zzimkkong/exception/reservation/InvalidMaximumDurationTimeException.java @@ -3,10 +3,10 @@ import com.woowacourse.zzimkkong.exception.ZzimkkongException; import org.springframework.http.HttpStatus; -public class ConflictSpaceSettingException extends ZzimkkongException { - private static final String MESSAGE = "공간의 예약조건을 확인해주세요."; +public class InvalidMaximumDurationTimeException extends ZzimkkongException { + private static final String MESSAGE = "최대 예약가능시간을 확인해주세요."; - public ConflictSpaceSettingException() { + public InvalidMaximumDurationTimeException() { super(MESSAGE, HttpStatus.BAD_REQUEST); } } diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/exception/reservation/InvalidMinimumDurationTimeException.java b/backend/src/main/java/com/woowacourse/zzimkkong/exception/reservation/InvalidMinimumDurationTimeException.java new file mode 100644 index 000000000..8189d4ace --- /dev/null +++ b/backend/src/main/java/com/woowacourse/zzimkkong/exception/reservation/InvalidMinimumDurationTimeException.java @@ -0,0 +1,12 @@ +package com.woowacourse.zzimkkong.exception.reservation; + +import com.woowacourse.zzimkkong.exception.ZzimkkongException; +import org.springframework.http.HttpStatus; + +public class InvalidMinimumDurationTimeException extends ZzimkkongException { + private static final String MESSAGE = "최소 예약가능시간을 확인해주세요."; + + public InvalidMinimumDurationTimeException() { + super(MESSAGE, HttpStatus.BAD_REQUEST); + } +} diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/exception/reservation/InvalidReservationEnableException.java b/backend/src/main/java/com/woowacourse/zzimkkong/exception/reservation/InvalidReservationEnableException.java index d3bb56423..541fcd0a2 100644 --- a/backend/src/main/java/com/woowacourse/zzimkkong/exception/reservation/InvalidReservationEnableException.java +++ b/backend/src/main/java/com/woowacourse/zzimkkong/exception/reservation/InvalidReservationEnableException.java @@ -1,4 +1,12 @@ package com.woowacourse.zzimkkong.exception.reservation; -public class InvalidReservationEnableException extends ConflictSpaceSettingException { +import com.woowacourse.zzimkkong.exception.ZzimkkongException; +import org.springframework.http.HttpStatus; + +public class InvalidReservationEnableException extends ZzimkkongException { + private static final String MESSAGE = "현재 예약이 불가능한 공간입니다."; + + public InvalidReservationEnableException() { + super(MESSAGE, HttpStatus.BAD_REQUEST); + } } diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/exception/reservation/InvalidStartEndTimeException.java b/backend/src/main/java/com/woowacourse/zzimkkong/exception/reservation/InvalidStartEndTimeException.java index 9edcc1bb3..9a74ad372 100644 --- a/backend/src/main/java/com/woowacourse/zzimkkong/exception/reservation/InvalidStartEndTimeException.java +++ b/backend/src/main/java/com/woowacourse/zzimkkong/exception/reservation/InvalidStartEndTimeException.java @@ -1,4 +1,12 @@ package com.woowacourse.zzimkkong.exception.reservation; -public class InvalidStartEndTimeException extends ConflictSpaceSettingException { +import com.woowacourse.zzimkkong.exception.ZzimkkongException; +import org.springframework.http.HttpStatus; + +public class InvalidStartEndTimeException extends ZzimkkongException { + private static final String MESSAGE = "공간의 예약가능 시간을 확인해주세요."; + + public InvalidStartEndTimeException() { + super(MESSAGE, HttpStatus.BAD_REQUEST); + } } diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/exception/reservation/InvalidTimeUnitException.java b/backend/src/main/java/com/woowacourse/zzimkkong/exception/reservation/InvalidTimeUnitException.java index 30f5be9c0..20e7d1dd7 100644 --- a/backend/src/main/java/com/woowacourse/zzimkkong/exception/reservation/InvalidTimeUnitException.java +++ b/backend/src/main/java/com/woowacourse/zzimkkong/exception/reservation/InvalidTimeUnitException.java @@ -1,4 +1,12 @@ package com.woowacourse.zzimkkong.exception.reservation; -public class InvalidTimeUnitException extends ConflictSpaceSettingException { +import com.woowacourse.zzimkkong.exception.ZzimkkongException; +import org.springframework.http.HttpStatus; + +public class InvalidTimeUnitException extends ZzimkkongException { + private static final String MESSAGE = "예약 시간단위를 확인해주세요."; + + public InvalidTimeUnitException() { + super(MESSAGE, HttpStatus.BAD_REQUEST); + } } diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/exception/reservation/ImpossibleReservationTimeException.java b/backend/src/main/java/com/woowacourse/zzimkkong/exception/reservation/ReservationAlreadyExistsException.java similarity index 52% rename from backend/src/main/java/com/woowacourse/zzimkkong/exception/reservation/ImpossibleReservationTimeException.java rename to backend/src/main/java/com/woowacourse/zzimkkong/exception/reservation/ReservationAlreadyExistsException.java index 5ecefafe1..71da5fccd 100644 --- a/backend/src/main/java/com/woowacourse/zzimkkong/exception/reservation/ImpossibleReservationTimeException.java +++ b/backend/src/main/java/com/woowacourse/zzimkkong/exception/reservation/ReservationAlreadyExistsException.java @@ -3,10 +3,10 @@ import com.woowacourse.zzimkkong.exception.InputFieldException; import org.springframework.http.HttpStatus; -public class ImpossibleReservationTimeException extends InputFieldException { - private static final String MESSAGE = "예약할 수 없는 시간입니다."; +public class ReservationAlreadyExistsException extends InputFieldException { + private static final String MESSAGE = "해당 시간에 이미 예약이 존재합니다."; - public ImpossibleReservationTimeException() { + public ReservationAlreadyExistsException() { super(MESSAGE, HttpStatus.BAD_REQUEST, START_DATE_TIME); } } diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/infrastructure/datetime/TimeZoneUtils.java b/backend/src/main/java/com/woowacourse/zzimkkong/infrastructure/datetime/TimeZoneUtils.java new file mode 100644 index 000000000..3cae39f5e --- /dev/null +++ b/backend/src/main/java/com/woowacourse/zzimkkong/infrastructure/datetime/TimeZoneUtils.java @@ -0,0 +1,19 @@ +package com.woowacourse.zzimkkong.infrastructure.datetime; + +import java.time.LocalDateTime; +import java.util.TimeZone; + +public class TimeZoneUtils { + public static final TimeZone UTC = TimeZone.getTimeZone("UTC"); + public static final TimeZone KST = TimeZone.getTimeZone("Asia/Seoul"); + public static final Long ONE_DAY_OFFSET = 1L; + + public static LocalDateTime convert( + final LocalDateTime dateTime, + final TimeZone fromTimeZone, + final TimeZone toTimeZone) { + return dateTime.atZone(fromTimeZone.toZoneId()) + .withZoneSameInstant(toTimeZone.toZoneId()) + .toLocalDateTime(); + } +} diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/infrastructure/thumbnail/BatikConverter.java b/backend/src/main/java/com/woowacourse/zzimkkong/infrastructure/thumbnail/BatikConverter.java deleted file mode 100644 index 9fa382f04..000000000 --- a/backend/src/main/java/com/woowacourse/zzimkkong/infrastructure/thumbnail/BatikConverter.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.woowacourse.zzimkkong.infrastructure.thumbnail; - -import com.woowacourse.zzimkkong.config.logaspect.LogMethodExecutionTime; -import com.woowacourse.zzimkkong.exception.infrastructure.SvgToPngConvertException; -import org.apache.batik.transcoder.TranscoderException; -import org.apache.batik.transcoder.TranscoderInput; -import org.apache.batik.transcoder.TranscoderOutput; -import org.apache.batik.transcoder.image.PNGTranscoder; -import org.springframework.stereotype.Component; - -import java.io.InputStream; -import java.io.OutputStream; - -@Component -@LogMethodExecutionTime(group = "infrastructure") -public class BatikConverter implements SvgConverter { - private final PNGTranscoder pngTranscoder = new PNGTranscoder(); - - @Override - public void convertSvgToPng(InputStream inputStream, OutputStream outputStream) { - try { - TranscoderInput transcoderInput = new TranscoderInput(inputStream); - TranscoderOutput transcoderOutput = new TranscoderOutput(outputStream); - - pngTranscoder.transcode(transcoderInput, transcoderOutput); - } catch (TranscoderException e) { - throw new SvgToPngConvertException(e); - } - } -} diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/infrastructure/thumbnail/S3ProxyUploader.java b/backend/src/main/java/com/woowacourse/zzimkkong/infrastructure/thumbnail/S3ProxyUploader.java deleted file mode 100644 index a6a155409..000000000 --- a/backend/src/main/java/com/woowacourse/zzimkkong/infrastructure/thumbnail/S3ProxyUploader.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.woowacourse.zzimkkong.infrastructure.thumbnail; - -import com.woowacourse.zzimkkong.config.logaspect.LogMethodExecutionTime; -import com.woowacourse.zzimkkong.exception.infrastructure.S3ProxyRespondedFailException; -import com.woowacourse.zzimkkong.exception.infrastructure.S3UploadException; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.core.io.InputStreamResource; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.client.MultipartBodyBuilder; -import org.springframework.web.reactive.function.BodyInserters; -import org.springframework.web.reactive.function.client.WebClient; -import reactor.core.publisher.Mono; - -import java.io.InputStream; -import java.util.Objects; - -@LogMethodExecutionTime(group = "infrastructure") -public class S3ProxyUploader implements StorageUploader { - private static final String PATH_DELIMITER = "/"; - private static final String API_PATH = "/api/storage"; - private static final String CONTENT_DISPOSITION_HEADER_VALUE_FORMAT = "form-data; name=file; filename=%s"; - - private final WebClient proxyServerClient; - private final String secretKey; - - public S3ProxyUploader( - @Value("${s3proxy.server-uri}") final String serverUri, - final String secretKey, - final WebClient webClient) { - this.proxyServerClient = webClient.mutate() - .baseUrl(serverUri) - .build(); - this.secretKey = secretKey; - } - - @Override - public String upload(String directoryName, String uploadFileName, InputStream inputStream) { - MultipartBodyBuilder multipartBodyBuilder = new MultipartBodyBuilder(); - multipartBodyBuilder.part("file", new InputStreamResource(inputStream)) - .header(HttpHeaders.CONTENT_DISPOSITION, - String.format(CONTENT_DISPOSITION_HEADER_VALUE_FORMAT, uploadFileName)); - - return requestMultipartUpload(directoryName, multipartBodyBuilder); - } - - private String requestMultipartUpload(String directoryName, MultipartBodyBuilder multipartBodyBuilder) { - return proxyServerClient - .method(HttpMethod.POST) - .uri(String.join(PATH_DELIMITER, API_PATH, directoryName)) - .header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE) - .header(HttpHeaders.AUTHORIZATION, secretKey) - .body(BodyInserters.fromMultipartData(multipartBodyBuilder.build())) - .exchangeToMono(clientResponse -> { - if (clientResponse.statusCode().equals(HttpStatus.CREATED)) { - String location = Objects.requireNonNull( - clientResponse.headers().asHttpHeaders().get(HttpHeaders.LOCATION)) - .stream().findFirst() - .orElseThrow(S3UploadException::new); - return Mono.just(location); - } - return Mono.error(S3ProxyRespondedFailException::new); - }) - .block(); - } - - @Override - public void delete(String directoryName, String fileName) { - proxyServerClient - .method(HttpMethod.DELETE) - .uri(String.join(PATH_DELIMITER, API_PATH, directoryName, fileName)) - .retrieve() - .bodyToMono(String.class) - .block(); - } -} diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/infrastructure/thumbnail/StorageUploader.java b/backend/src/main/java/com/woowacourse/zzimkkong/infrastructure/thumbnail/StorageUploader.java deleted file mode 100644 index e4a03d18d..000000000 --- a/backend/src/main/java/com/woowacourse/zzimkkong/infrastructure/thumbnail/StorageUploader.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.woowacourse.zzimkkong.infrastructure.thumbnail; - -import java.io.InputStream; - -public interface StorageUploader { - String upload(final String directoryName, String uploadFileName, final InputStream inputStream); - - void delete(final String directoryName, final String fileName); -} diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/infrastructure/thumbnail/SvgConverter.java b/backend/src/main/java/com/woowacourse/zzimkkong/infrastructure/thumbnail/SvgConverter.java deleted file mode 100644 index e7808ea78..000000000 --- a/backend/src/main/java/com/woowacourse/zzimkkong/infrastructure/thumbnail/SvgConverter.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.woowacourse.zzimkkong.infrastructure.thumbnail; - -import java.io.InputStream; -import java.io.OutputStream; - -public interface SvgConverter { - void convertSvgToPng(final InputStream inputStream, final OutputStream outputStream); -} diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/infrastructure/thumbnail/ThumbnailManager.java b/backend/src/main/java/com/woowacourse/zzimkkong/infrastructure/thumbnail/ThumbnailManager.java deleted file mode 100644 index 1400277b9..000000000 --- a/backend/src/main/java/com/woowacourse/zzimkkong/infrastructure/thumbnail/ThumbnailManager.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.woowacourse.zzimkkong.infrastructure.thumbnail; - -import com.woowacourse.zzimkkong.domain.Map; - -public interface ThumbnailManager { - String uploadMapThumbnail(final String svgData, final Map map); - - void deleteThumbnail(final Map map); -} diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/infrastructure/thumbnail/ThumbnailManagerImpl.java b/backend/src/main/java/com/woowacourse/zzimkkong/infrastructure/thumbnail/ThumbnailManagerImpl.java deleted file mode 100644 index dc02db47b..000000000 --- a/backend/src/main/java/com/woowacourse/zzimkkong/infrastructure/thumbnail/ThumbnailManagerImpl.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.woowacourse.zzimkkong.infrastructure.thumbnail; - -import com.woowacourse.zzimkkong.domain.Map; -import com.woowacourse.zzimkkong.exception.infrastructure.CannotGenerateInputStreamFromSvgDataException; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; - -import java.io.*; - -@Component -public class ThumbnailManagerImpl implements ThumbnailManager { - public static final String THUMBNAIL_EXTENSION = ".png"; - private static final String THUMBNAIL_FILE_FORMAT = "%s"; - - private final SvgConverter svgConverter; - private final StorageUploader storageUploader; - private final String thumbnailsDirectoryName; - - public ThumbnailManagerImpl( - final SvgConverter svgConverter, - final StorageUploader storageUploader, - @Value("${storage.thumbnails-directory}") final String thumbnailsDirectoryName) { - this.svgConverter = svgConverter; - this.storageUploader = storageUploader; - this.thumbnailsDirectoryName = thumbnailsDirectoryName; - } - - public String uploadMapThumbnail(final String svgData, final Map map) { - try (final ByteArrayInputStream svgInputStream = new ByteArrayInputStream(svgData.getBytes()); - final ByteArrayOutputStream pngOutputStream = new ByteArrayOutputStream()) { - final BufferedInputStream bufferedSvgInputStream = new BufferedInputStream(svgInputStream); - final BufferedOutputStream bufferedPngOutputStream = new BufferedOutputStream(pngOutputStream); - - final String fileName = makeThumbnailFileName(map); - svgConverter.convertSvgToPng(bufferedSvgInputStream, bufferedPngOutputStream); - - return uploadFromByteArray(fileName, pngOutputStream.toByteArray()); - } catch (IOException exception) { - throw new CannotGenerateInputStreamFromSvgDataException(exception); - } - } - - private String makeThumbnailFileName(final Map map) { - return String.format(THUMBNAIL_FILE_FORMAT, map.getId().toString() + THUMBNAIL_EXTENSION); - } - - private String uploadFromByteArray(String fileName, byte[] byteArray) throws IOException { - try (final BufferedInputStream bufferedPngInputStream = new BufferedInputStream(new ByteArrayInputStream(byteArray))) { - return storageUploader.upload(thumbnailsDirectoryName, fileName, bufferedPngInputStream); - } - } - - public void deleteThumbnail(final Map map) { - String fileName = makeThumbnailFileName(map); - storageUploader.delete(thumbnailsDirectoryName, fileName + THUMBNAIL_EXTENSION); - } -} diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/infrastructure/warmup/Warmer.java b/backend/src/main/java/com/woowacourse/zzimkkong/infrastructure/warmup/Warmer.java deleted file mode 100644 index 15f4caa27..000000000 --- a/backend/src/main/java/com/woowacourse/zzimkkong/infrastructure/warmup/Warmer.java +++ /dev/null @@ -1,87 +0,0 @@ -package com.woowacourse.zzimkkong.infrastructure.warmup; - -import com.woowacourse.zzimkkong.domain.SlackUrl; -import com.woowacourse.zzimkkong.infrastructure.thumbnail.BatikConverter; -import lombok.extern.slf4j.Slf4j; -import org.springframework.core.io.InputStreamResource; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.http.client.MultipartBodyBuilder; -import org.springframework.web.reactive.function.BodyInserters; -import org.springframework.web.reactive.function.client.WebClient; -import reactor.core.publisher.Mono; - -import java.io.*; - -@Slf4j -public class Warmer { - private final BatikConverter batikConverter; - private final SlackUrl slackUrl; - private final WebClient webClient; - private final String s3ProxyServerUri; - private final String s3ProxyServerSecretKey; - - public Warmer( - final BatikConverter batikConverter, - final SlackUrl slackUrl, - final WebClient webClient, - final String s3ProxyServerUri, - final String s3ProxyServerSecretKey) { - this.batikConverter = batikConverter; - this.slackUrl = slackUrl; - this.webClient = webClient; - this.s3ProxyServerUri = s3ProxyServerUri; - this.s3ProxyServerSecretKey = s3ProxyServerSecretKey; - } - - public void warmUp() { - log.info("warm up을 시작합니다"); - - initSvgConverter(); - initWebClient(); - - log.info("warm up이 완료 되었습니다."); - } - - private void initSvgConverter() { - String svgData = " "; - - try (final ByteArrayInputStream svgInputStream = new ByteArrayInputStream(svgData.getBytes()); - final ByteArrayOutputStream pngOutputStream = new ByteArrayOutputStream()) { - final BufferedInputStream bufferedSvgInputStream = new BufferedInputStream(svgInputStream); - final BufferedOutputStream bufferedPngOutputStream = new BufferedOutputStream(pngOutputStream); - batikConverter.convertSvgToPng(bufferedSvgInputStream, bufferedPngOutputStream); - } catch (IOException ignored) { - } - } - - private void initWebClient() { - webClient - .post() - .uri(slackUrl.getUrl()) - .contentType(MediaType.APPLICATION_JSON) - .bodyValue("let's warm up") - .retrieve() - .bodyToMono(String.class) - .onErrorResume(e -> Mono.just("warm up finisehd")) - .subscribe(); - - try (final BufferedInputStream bufferedPngInputStream = new BufferedInputStream(new ByteArrayInputStream("warmUp".getBytes()))) { - MultipartBodyBuilder multipartBodyBuilder = new MultipartBodyBuilder(); - multipartBodyBuilder.part("file", new InputStreamResource(bufferedPngInputStream)) - .header(HttpHeaders.CONTENT_DISPOSITION, "form-data; name=file; filename=warmFile"); - - webClient - .post() - .uri(s3ProxyServerUri + "/api/storage/warmup") - .header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE) - .header(HttpHeaders.AUTHORIZATION, s3ProxyServerSecretKey) - .body(BodyInserters.fromMultipartData(multipartBodyBuilder.build())) - .retrieve() - .bodyToMono(String.class) - .onErrorResume(e -> Mono.just("warm up finished")) - .block(); - } catch (IOException ignored) { - } - } -} diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/repository/MapRepository.java b/backend/src/main/java/com/woowacourse/zzimkkong/repository/MapRepository.java index 81a6b3bed..ce8fd3d85 100644 --- a/backend/src/main/java/com/woowacourse/zzimkkong/repository/MapRepository.java +++ b/backend/src/main/java/com/woowacourse/zzimkkong/repository/MapRepository.java @@ -1,6 +1,6 @@ package com.woowacourse.zzimkkong.repository; -import com.woowacourse.zzimkkong.config.logaspect.LogRegistry; +import com.woowacourse.zzimkkong.config.logaspect.FindInstanceAndCreateLogProxy; import com.woowacourse.zzimkkong.domain.Map; import com.woowacourse.zzimkkong.domain.Member; import org.springframework.data.domain.Page; @@ -12,7 +12,7 @@ import java.util.List; import java.util.Optional; -@LogRegistry(group = "repository") +@FindInstanceAndCreateLogProxy(group = "repository") public interface MapRepository extends JpaRepository { List findAllByMember(final Member member); diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/repository/MemberRepository.java b/backend/src/main/java/com/woowacourse/zzimkkong/repository/MemberRepository.java index ee6e050e8..819bd7639 100644 --- a/backend/src/main/java/com/woowacourse/zzimkkong/repository/MemberRepository.java +++ b/backend/src/main/java/com/woowacourse/zzimkkong/repository/MemberRepository.java @@ -1,6 +1,6 @@ package com.woowacourse.zzimkkong.repository; -import com.woowacourse.zzimkkong.config.logaspect.LogRegistry; +import com.woowacourse.zzimkkong.config.logaspect.FindInstanceAndCreateLogProxy; import com.woowacourse.zzimkkong.domain.Member; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -10,7 +10,7 @@ import java.util.Optional; -@LogRegistry(group = "repository") +@FindInstanceAndCreateLogProxy(group = "repository") public interface MemberRepository extends JpaRepository { boolean existsByEmail(String email); diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/repository/PresetRepository.java b/backend/src/main/java/com/woowacourse/zzimkkong/repository/PresetRepository.java index aa1a78f37..c529f0673 100644 --- a/backend/src/main/java/com/woowacourse/zzimkkong/repository/PresetRepository.java +++ b/backend/src/main/java/com/woowacourse/zzimkkong/repository/PresetRepository.java @@ -1,9 +1,9 @@ package com.woowacourse.zzimkkong.repository; -import com.woowacourse.zzimkkong.config.logaspect.LogRegistry; +import com.woowacourse.zzimkkong.config.logaspect.FindInstanceAndCreateLogProxy; import com.woowacourse.zzimkkong.domain.Preset; import org.springframework.data.jpa.repository.JpaRepository; -@LogRegistry(group = "repository") +@FindInstanceAndCreateLogProxy(group = "repository") public interface PresetRepository extends JpaRepository { } diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/repository/ReservationRepository.java b/backend/src/main/java/com/woowacourse/zzimkkong/repository/ReservationRepository.java index d0dfe2339..ff5da4363 100644 --- a/backend/src/main/java/com/woowacourse/zzimkkong/repository/ReservationRepository.java +++ b/backend/src/main/java/com/woowacourse/zzimkkong/repository/ReservationRepository.java @@ -1,6 +1,6 @@ package com.woowacourse.zzimkkong.repository; -import com.woowacourse.zzimkkong.config.logaspect.LogRegistry; +import com.woowacourse.zzimkkong.config.logaspect.FindInstanceAndCreateLogProxy; import com.woowacourse.zzimkkong.domain.Reservation; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -12,9 +12,12 @@ import java.util.Collection; import java.util.List; -@LogRegistry(group = "repository") +@FindInstanceAndCreateLogProxy(group = "repository") public interface ReservationRepository extends JpaRepository, ReservationRepositoryCustom { - List findAllBySpaceIdInAndDate(final Collection spaceIds, final LocalDate date); + List findAllBySpaceIdInAndDateGreaterThanEqualAndDateLessThanEqual( + final Collection spaceIds, + final LocalDate lowerBoundDate, + final LocalDate upperBoundDate); Boolean existsBySpaceIdAndEndTimeAfter(Long spaceId, LocalDateTime now); diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/repository/SpaceRepository.java b/backend/src/main/java/com/woowacourse/zzimkkong/repository/SpaceRepository.java index 98417ee05..2e0ef5578 100644 --- a/backend/src/main/java/com/woowacourse/zzimkkong/repository/SpaceRepository.java +++ b/backend/src/main/java/com/woowacourse/zzimkkong/repository/SpaceRepository.java @@ -1,13 +1,13 @@ package com.woowacourse.zzimkkong.repository; -import com.woowacourse.zzimkkong.config.logaspect.LogRegistry; +import com.woowacourse.zzimkkong.config.logaspect.FindInstanceAndCreateLogProxy; import com.woowacourse.zzimkkong.domain.Space; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; -@LogRegistry(group = "repository") +@FindInstanceAndCreateLogProxy(group = "repository") public interface SpaceRepository extends JpaRepository { @Query(value = "select s from Space s inner join fetch s.map m " + "inner join fetch m.member " + diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/service/MapService.java b/backend/src/main/java/com/woowacourse/zzimkkong/service/MapService.java index 977ec2907..f006ddf5e 100644 --- a/backend/src/main/java/com/woowacourse/zzimkkong/service/MapService.java +++ b/backend/src/main/java/com/woowacourse/zzimkkong/service/MapService.java @@ -3,22 +3,16 @@ import com.woowacourse.zzimkkong.domain.Map; import com.woowacourse.zzimkkong.domain.Member; import com.woowacourse.zzimkkong.domain.Space; -import com.woowacourse.zzimkkong.dto.map.MapCreateResponse; -import com.woowacourse.zzimkkong.dto.map.MapCreateUpdateRequest; -import com.woowacourse.zzimkkong.dto.map.MapFindAllResponse; -import com.woowacourse.zzimkkong.dto.map.MapFindResponse; +import com.woowacourse.zzimkkong.dto.map.*; +import com.woowacourse.zzimkkong.dto.member.LoginEmailDto; import com.woowacourse.zzimkkong.exception.authorization.NoAuthorityOnMapException; import com.woowacourse.zzimkkong.exception.map.NoSuchMapException; import com.woowacourse.zzimkkong.exception.member.NoSuchMemberException; import com.woowacourse.zzimkkong.exception.space.ReservationExistOnSpaceException; -import com.woowacourse.zzimkkong.dto.member.LoginEmailDto; import com.woowacourse.zzimkkong.infrastructure.sharingid.SharingIdGenerator; -import com.woowacourse.zzimkkong.infrastructure.thumbnail.ThumbnailManager; import com.woowacourse.zzimkkong.repository.MapRepository; import com.woowacourse.zzimkkong.repository.MemberRepository; import com.woowacourse.zzimkkong.repository.ReservationRepository; -import org.springframework.cache.annotation.CacheEvict; -import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -34,19 +28,16 @@ public class MapService { private final MemberRepository members; private final MapRepository maps; private final ReservationRepository reservations; - private final ThumbnailManager thumbnailManager; private final SharingIdGenerator sharingIdGenerator; public MapService( final MemberRepository members, final MapRepository maps, final ReservationRepository reservations, - final ThumbnailManager thumbnailManager, final SharingIdGenerator sharingIdGenerator) { this.members = members; this.maps = maps; this.reservations = reservations; - this.thumbnailManager = thumbnailManager; this.sharingIdGenerator = sharingIdGenerator; } @@ -56,12 +47,9 @@ public MapCreateResponse saveMap(final MapCreateUpdateRequest mapCreateUpdateReq Map saveMap = maps.save(new Map( mapCreateUpdateRequest.getMapName(), mapCreateUpdateRequest.getMapDrawing(), - mapCreateUpdateRequest.getMapImageSvg().substring(0, 10), + mapCreateUpdateRequest.getThumbnail(), manager)); - final String thumbnailUrl = thumbnailManager.uploadMapThumbnail(mapCreateUpdateRequest.getMapImageSvg(), saveMap); - saveMap.updateImageUrl(thumbnailUrl); - return MapCreateResponse.from(saveMap); } @@ -86,9 +74,6 @@ public MapFindAllResponse findAllMaps(final LoginEmailDto loginEmailDto) { .collect(collectingAndThen(toList(), mapFindResponses -> MapFindAllResponse.of(mapFindResponses, manager))); } - @Cacheable(key = "#sharingMapId", - value = "map", - unless = "#result == null") @Transactional(readOnly = true) public MapFindResponse findMapBySharingId(final String sharingMapId) { Long mapId = sharingIdGenerator.parseIdFrom(sharingMapId); @@ -97,8 +82,6 @@ public MapFindResponse findMapBySharingId(final String sharingMapId) { return MapFindResponse.of(map, sharingIdGenerator.from(map)); } - @CacheEvict(value = "map", - allEntries = true) public void updateMap(final Long mapId, final MapCreateUpdateRequest mapCreateUpdateRequest, final LoginEmailDto loginEmailDto) { @@ -106,10 +89,10 @@ public void updateMap(final Long mapId, .orElseThrow(NoSuchMapException::new); validateManagerOfMap(map, loginEmailDto.getEmail()); - thumbnailManager.uploadMapThumbnail(mapCreateUpdateRequest.getMapImageSvg(), map); map.update( mapCreateUpdateRequest.getMapName(), mapCreateUpdateRequest.getMapDrawing()); + map.updateThumbnail(mapCreateUpdateRequest.getThumbnail()); } public void deleteMap(final Long mapId, final LoginEmailDto loginEmailDto) { @@ -120,7 +103,23 @@ public void deleteMap(final Long mapId, final LoginEmailDto loginEmailDto) { validateExistReservations(map); maps.delete(map); - thumbnailManager.deleteThumbnail(map); + } + + public void saveSlackUrl(final Long mapId, + final SlackCreateRequest slackCreateRequest, + final LoginEmailDto loginEmailDto) { + Map map = maps.findById(mapId) + .orElseThrow(NoSuchMapException::new); + validateManagerOfMap(map, loginEmailDto.getEmail()); + map.updateSlackUrl(slackCreateRequest.getSlackUrl()); + } + + @Transactional(readOnly = true) + public SlackFindResponse findSlackUrl(final Long mapId, final LoginEmailDto loginEmailDto) { + Map map = maps.findById(mapId) + .orElseThrow(NoSuchMapException::new); + validateManagerOfMap(map, loginEmailDto.getEmail()); + return SlackFindResponse.from(map); } private void validateExistReservations(final Map map) { diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/service/ReservationService.java b/backend/src/main/java/com/woowacourse/zzimkkong/service/ReservationService.java index a5207418d..f9d62f9b8 100644 --- a/backend/src/main/java/com/woowacourse/zzimkkong/service/ReservationService.java +++ b/backend/src/main/java/com/woowacourse/zzimkkong/service/ReservationService.java @@ -8,6 +8,7 @@ import com.woowacourse.zzimkkong.exception.map.NoSuchMapException; import com.woowacourse.zzimkkong.exception.reservation.*; import com.woowacourse.zzimkkong.exception.space.NoSuchSpaceException; +import com.woowacourse.zzimkkong.infrastructure.datetime.TimeZoneUtils; import com.woowacourse.zzimkkong.infrastructure.sharingid.SharingIdGenerator; import com.woowacourse.zzimkkong.repository.MapRepository; import com.woowacourse.zzimkkong.repository.ReservationRepository; @@ -24,8 +25,11 @@ import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.TimeZone; import java.util.stream.Collectors; +import static com.woowacourse.zzimkkong.infrastructure.datetime.TimeZoneUtils.*; + @Service @Transactional public class ReservationService { @@ -60,18 +64,19 @@ public ReservationCreateResponse saveReservation( validateAvailability(space, reservationCreateDto, new ExcludeReservationCreateStrategy()); + LocalDate date = TimeZoneUtils.convert(reservationCreateDto.getStartDateTime(), UTC, KST).toLocalDate(); Reservation reservation = reservations.save( Reservation.builder() .startTime(reservationCreateDto.getStartDateTime()) .endTime(reservationCreateDto.getEndDateTime()) - .date(reservationCreateDto.getStartDateTime().toLocalDate()) + .date(date) .password(reservationCreateDto.getPassword()) .userName(reservationCreateDto.getName()) .description(reservationCreateDto.getDescription()) .space(space) .build()); String sharingMapId = sharingIdGenerator.from(map); - return ReservationCreateResponse.of(reservation, sharingMapId); + return ReservationCreateResponse.of(reservation, sharingMapId, map.getSlackUrl()); } @Transactional(readOnly = true) @@ -161,10 +166,11 @@ public SlackResponse updateReservation( validateAvailability(space, reservationUpdateDto, new ExcludeReservationUpdateStrategy(reservation)); + LocalDate date = TimeZoneUtils.convert(reservationUpdateDto.getStartDateTime(), UTC, KST).toLocalDate(); Reservation updateReservation = Reservation.builder() .startTime(reservationUpdateDto.getStartDateTime()) .endTime(reservationUpdateDto.getEndDateTime()) - .date(reservationUpdateDto.getStartDateTime().toLocalDate()) + .date(date) .userName(reservationUpdateDto.getName()) .description(reservationUpdateDto.getDescription()) .space(space) @@ -173,7 +179,7 @@ public SlackResponse updateReservation( reservation.update(updateReservation, space); String sharingMapId = sharingIdGenerator.from(map); - return SlackResponse.of(reservation, sharingMapId); + return SlackResponse.of(reservation, sharingMapId, map.getSlackUrl()); } public SlackResponse deleteReservation( @@ -200,7 +206,7 @@ public SlackResponse deleteReservation( reservations.delete(reservation); String sharingMapId = sharingIdGenerator.from(map); - return SlackResponse.of(reservation, sharingMapId); + return SlackResponse.of(reservation, sharingMapId, map.getSlackUrl()); } private void validateTime(final ReservationCreateDto reservationCreateDto, final boolean managerFlag) { @@ -213,12 +219,14 @@ private void validateTime(final ReservationCreateDto reservationCreateDto, final throw new ImpossibleEndTimeException(); } - if (!startDateTime.toLocalDate().isEqual(endDateTime.toLocalDate())) { + LocalDate startDateKST = TimeZoneUtils.convert(startDateTime, UTC, KST).toLocalDate(); + LocalDate endDateKST = TimeZoneUtils.convert(endDateTime, UTC, KST).toLocalDate(); + if (!startDateKST.isEqual(endDateKST)) { throw new NonMatchingStartAndEndDateException(); } } - private void validatePastTimeAndManager(LocalDateTime startDateTime, boolean managerFlag) { + private void validatePastTimeAndManager(final LocalDateTime startDateTime, final boolean managerFlag) { if (startDateTime.isBefore(LocalDateTime.now()) && !managerFlag) { throw new ImpossibleStartTimeException(); } @@ -241,18 +249,27 @@ private void validateAvailability( validateTimeConflicts(startDateTime, endDateTime, reservationsOnDate); } - private void validateSpaceSetting(Space space, LocalDateTime startDateTime, LocalDateTime endDateTime) { - int durationMinutes = (int) ChronoUnit.MINUTES.between(startDateTime, endDateTime); + private void validateSpaceSetting( + final Space space, + final LocalDateTime startDateTime, + final LocalDateTime endDateTime) { + LocalDateTime startDateTimeKST = TimeZoneUtils.convert(startDateTime, UTC, KST); + LocalDateTime endDateTimeKST = TimeZoneUtils.convert(endDateTime, UTC, KST); + int durationMinutes = (int) ChronoUnit.MINUTES.between(startDateTimeKST, endDateTimeKST); - if (space.isNotDivisibleByTimeUnit(startDateTime.getMinute()) || space.isNotDivisibleByTimeUnit(durationMinutes)) { + if (space.isNotDivisibleByTimeUnit(startDateTimeKST.getMinute()) || space.isNotDivisibleByTimeUnit(durationMinutes)) { throw new InvalidTimeUnitException(); } - if (space.isIncorrectMinimumMaximumTimeUnit(durationMinutes)) { - throw new InvalidDurationTimeException(); + if (space.isIncorrectMinimumTimeUnit(durationMinutes)) { + throw new InvalidMinimumDurationTimeException(); } - if (space.isNotBetweenAvailableTime(startDateTime, endDateTime)) { + if (space.isIncorrectMaximumTimeUnit(durationMinutes)) { + throw new InvalidMaximumDurationTimeException(); + } + + if (space.isNotBetweenAvailableTime(startDateTimeKST, endDateTimeKST)) { throw new InvalidStartEndTimeException(); } @@ -260,7 +277,7 @@ private void validateSpaceSetting(Space space, LocalDateTime startDateTime, Loca throw new InvalidReservationEnableException(); } - if (space.isClosedOn(startDateTime.getDayOfWeek())) { + if (space.isClosedOn(startDateTimeKST.getDayOfWeek())) { throw new InvalidDayOfWeekException(); } } @@ -271,7 +288,7 @@ private void validateTimeConflicts( final List reservationsOnDate) { for (Reservation existingReservation : reservationsOnDate) { if (existingReservation.hasConflictWith(startDateTime, endDateTime)) { - throw new ImpossibleReservationTimeException(); + throw new ReservationAlreadyExistsException(); } } } @@ -281,7 +298,14 @@ private List getReservations(final Collection findSpaces, fi .map(Space::getId) .collect(Collectors.toList()); - return reservations.findAllBySpaceIdInAndDate(spaceIds, date); + List reservationsWithinDateRange = reservations.findAllBySpaceIdInAndDateGreaterThanEqualAndDateLessThanEqual( + spaceIds, + date.minusDays(ONE_DAY_OFFSET), + date.plusDays(ONE_DAY_OFFSET)); + + return reservationsWithinDateRange.stream() + .filter(reservation -> reservation.isBookedOn(date, KST)) + .collect(Collectors.toList()); } private void validateSpaceExistence(final Map map, final Long spaceId) { diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/service/SlackService.java b/backend/src/main/java/com/woowacourse/zzimkkong/service/SlackService.java index 5b2deabca..8f3d23c52 100644 --- a/backend/src/main/java/com/woowacourse/zzimkkong/service/SlackService.java +++ b/backend/src/main/java/com/woowacourse/zzimkkong/service/SlackService.java @@ -1,6 +1,5 @@ package com.woowacourse.zzimkkong.service; -import com.woowacourse.zzimkkong.domain.SlackUrl; import com.woowacourse.zzimkkong.dto.slack.Attachments; import com.woowacourse.zzimkkong.dto.slack.SlackResponse; import org.springframework.beans.factory.annotation.Value; @@ -9,43 +8,47 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.web.reactive.function.client.WebClient; +import java.util.Objects; + @Service @Transactional(readOnly = true) public class SlackService { private final WebClient slackWebClient; private final String titleLink; - public SlackService(@Value("${service.url}") String titleLink, - final SlackUrl slackUrl, + public SlackService(@Value("${service.url}") final String titleLink, final WebClient webClient) { this.titleLink = titleLink; - this.slackWebClient = webClient.mutate() - .baseUrl(slackUrl.getUrl()) - .build(); + slackWebClient = webClient; } public void sendCreateMessage(SlackResponse slackResponse) { Attachments attachments = Attachments.createMessageOf(slackResponse, titleLink); - send(attachments); + send(attachments, slackResponse.getSlackUrl()); } public void sendUpdateMessage(SlackResponse slackResponse) { Attachments attachments = Attachments.updateMessageOf(slackResponse, titleLink); - send(attachments); + send(attachments, slackResponse.getSlackUrl()); } public void sendDeleteMessage(SlackResponse slackResponse) { Attachments attachments = Attachments.deleteMessageOf(slackResponse, titleLink); - send(attachments); + send(attachments, slackResponse.getSlackUrl()); } - private void send(final Attachments attachments) { - slackWebClient.post() - .contentType(MediaType.APPLICATION_JSON) - .bodyValue(attachments.toString()) - .retrieve() - .bodyToMono(String.class) - .then() - .subscribe(); + private void send(final Attachments attachments, final String slackUrl) { + if (!Objects.isNull(slackUrl)) { + slackWebClient.mutate() + .baseUrl(slackUrl) + .build() + .post() + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(attachments.toString()) + .retrieve() + .bodyToMono(String.class) + .then() + .subscribe(); + } } } diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/service/SpaceService.java b/backend/src/main/java/com/woowacourse/zzimkkong/service/SpaceService.java index 7b52d0fff..7f40b9ebf 100644 --- a/backend/src/main/java/com/woowacourse/zzimkkong/service/SpaceService.java +++ b/backend/src/main/java/com/woowacourse/zzimkkong/service/SpaceService.java @@ -9,7 +9,6 @@ import com.woowacourse.zzimkkong.exception.map.NoSuchMapException; import com.woowacourse.zzimkkong.exception.space.NoSuchSpaceException; import com.woowacourse.zzimkkong.exception.space.ReservationExistOnSpaceException; -import com.woowacourse.zzimkkong.infrastructure.thumbnail.ThumbnailManager; import com.woowacourse.zzimkkong.repository.MapRepository; import com.woowacourse.zzimkkong.repository.ReservationRepository; import com.woowacourse.zzimkkong.repository.SpaceRepository; @@ -25,17 +24,14 @@ public class SpaceService { private final MapRepository maps; private final SpaceRepository spaces; private final ReservationRepository reservations; - private final ThumbnailManager thumbnailManager; public SpaceService( final MapRepository maps, final SpaceRepository spaces, - final ReservationRepository reservations, - final ThumbnailManager thumbnailManager) { + final ReservationRepository reservations) { this.maps = maps; this.spaces = spaces; this.reservations = reservations; - this.thumbnailManager = thumbnailManager; } public SpaceCreateResponse saveSpace( @@ -57,7 +53,8 @@ public SpaceCreateResponse saveSpace( .build(); Space saveSpace = spaces.save(space); - thumbnailManager.uploadMapThumbnail(spaceCreateUpdateRequest.getMapImageSvg(), map); + map.updateThumbnail(spaceCreateUpdateRequest.getThumbnail()); + return SpaceCreateResponse.from(saveSpace); } @@ -119,7 +116,8 @@ public void updateSpace( .build(); space.update(updateSpace); - thumbnailManager.uploadMapThumbnail(spaceCreateUpdateRequest.getMapImageSvg(), map); + + map.updateThumbnail(spaceCreateUpdateRequest.getThumbnail()); } public void deleteSpace( @@ -137,7 +135,8 @@ public void deleteSpace( validateReservationExistence(spaceId); spaces.delete(space); - thumbnailManager.uploadMapThumbnail(spaceDeleteRequest.getMapImageSvg(), map); + + map.updateThumbnail(spaceDeleteRequest.getThumbnail()); } private Setting getSetting(final SpaceCreateUpdateRequest spaceCreateUpdateRequest) { diff --git a/backend/src/main/resources/appenders/logstash-appender-dev.xml b/backend/src/main/resources/appenders/logstash-appender-dev.xml deleted file mode 100644 index 0a4c4fc37..000000000 --- a/backend/src/main/resources/appenders/logstash-appender-dev.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - 192.168.1.222:5044 - - - - diff --git a/backend/src/main/resources/appenders/logstash-appender-prod.xml b/backend/src/main/resources/appenders/logstash-appender-prod.xml deleted file mode 100644 index d36a59e94..000000000 --- a/backend/src/main/resources/appenders/logstash-appender-prod.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - 192.168.1.210:5044 - - - - diff --git a/backend/src/main/resources/application-dev.properties b/backend/src/main/resources/application-dev.properties index feed1af88..14bdb42e0 100644 --- a/backend/src/main/resources/application-dev.properties +++ b/backend/src/main/resources/application-dev.properties @@ -1,6 +1,8 @@ +server.port=8081 + # Database spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver -spring.datasource.url=jdbc:mysql://localhost:3306/zzimkkong?characterEncoding=UTF-8&useLegacyDatetimeCode=false +spring.datasource.url=jdbc:mysql://192.168.0.164:3306/zzimkkong_dev?characterEncoding=UTF-8&useLegacyDatetimeCode=false spring.datasource.username=root spring.datasource.password=1234 spring.datasource.hikari.maximum-pool-size=45 diff --git a/backend/src/main/resources/config b/backend/src/main/resources/config index a2d168ac3..428882614 160000 --- a/backend/src/main/resources/config +++ b/backend/src/main/resources/config @@ -1 +1 @@ -Subproject commit a2d168ac3adfb324408ad8afa2bd3e4d3ac35220 +Subproject commit 428882614a89e1aa792745f16567e619a4f55c7c diff --git a/backend/src/main/resources/db/migration/prod/V13__thumbnail_as_svg.sql b/backend/src/main/resources/db/migration/prod/V13__thumbnail_as_svg.sql new file mode 100644 index 000000000..1357c4398 --- /dev/null +++ b/backend/src/main/resources/db/migration/prod/V13__thumbnail_as_svg.sql @@ -0,0 +1,11 @@ +SET foreign_key_checks = 0; + +TRUNCATE TABLE map; +TRUNCATE TABLE member; +TRUNCATE TABLE preset; +TRUNCATE TABLE reservation; +TRUNCATE TABLE space; + +SET foreign_key_checks = 1; + +ALTER TABLE map CHANGE COLUMN map_image_url thumbnail longtext not null; diff --git a/backend/src/main/resources/db/migration/prod/V14__map_add_column_slack.sql b/backend/src/main/resources/db/migration/prod/V14__map_add_column_slack.sql new file mode 100644 index 000000000..fb81cdbd6 --- /dev/null +++ b/backend/src/main/resources/db/migration/prod/V14__map_add_column_slack.sql @@ -0,0 +1 @@ +ALTER TABLE map ADD COLUMN slack_url longtext; \ No newline at end of file diff --git a/backend/src/main/resources/infra-appender b/backend/src/main/resources/infra-appender deleted file mode 160000 index 1a4ed48a7..000000000 --- a/backend/src/main/resources/infra-appender +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 1a4ed48a7bb9a8c399c6bc8cb5f2111e9e3c2cb9 diff --git a/backend/src/main/resources/logback-spring.xml b/backend/src/main/resources/logback-spring.xml index c35ab77a6..7369644ac 100644 --- a/backend/src/main/resources/logback-spring.xml +++ b/backend/src/main/resources/logback-spring.xml @@ -24,7 +24,6 @@ - @@ -32,7 +31,6 @@ - @@ -41,7 +39,6 @@ - @@ -49,7 +46,6 @@ - @@ -57,5 +53,4 @@ - diff --git a/backend/src/test/java/com/woowacourse/zzimkkong/Constants.java b/backend/src/test/java/com/woowacourse/zzimkkong/Constants.java index 2a46adade..11c58d29b 100644 --- a/backend/src/test/java/com/woowacourse/zzimkkong/Constants.java +++ b/backend/src/test/java/com/woowacourse/zzimkkong/Constants.java @@ -1,8 +1,10 @@ package com.woowacourse.zzimkkong; import java.time.LocalDate; -import java.time.LocalDateTime; import java.time.LocalTime; +import java.time.ZonedDateTime; + +import static com.woowacourse.zzimkkong.infrastructure.datetime.TimeZoneUtils.KST; public class Constants { private Constants() { @@ -21,7 +23,6 @@ private Constants() { public static final String DESCRIPTION = "찜꽁 1차 회의"; public static final String USER_NAME = "찜꽁"; public static final String RESERVATION_PW = "1234"; - public static final String MAP_IMAGE_URL = "https://zzimkkong-personal.s3.ap-northeast-2.amazonaws.com/thumbnails/2387563.png"; public static final String MAP_SVG = " "; public static final String SPACE_DRAWING = "{ \"id\": \"1\", \"type\" : \"rect\", \"x\": \"10\", \"y\": \"10\", \"width\": \"30\", \"height\": \"30\" }"; public static final String LUTHER_NAME = "루터회관"; @@ -55,27 +56,27 @@ private Constants() { public static final String PRESET_NAME1 = "프리셋1"; public static final String PRESET_NAME2 = "프리셋2"; - public static final LocalDateTime BE_AM_TEN_ELEVEN_START_TIME = THE_DAY_AFTER_TOMORROW.atTime(10,0); - public static final LocalDateTime BE_AM_TEN_ELEVEN_END_TIME = THE_DAY_AFTER_TOMORROW.atTime(11,0); + public static final ZonedDateTime BE_AM_TEN_ELEVEN_START_TIME_KST = THE_DAY_AFTER_TOMORROW.atTime(10, 0).atZone(KST.toZoneId()); + public static final ZonedDateTime BE_AM_TEN_ELEVEN_END_TIME_KST = THE_DAY_AFTER_TOMORROW.atTime(11, 0).atZone(KST.toZoneId()); public static final String BE_AM_TEN_ELEVEN_DESCRIPTION = DESCRIPTION; public static final String BE_AM_TEN_ELEVEN_USERNAME = USER_NAME; public static final String BE_AM_TEN_ELEVEN_PW = RESERVATION_PW; - public static final LocalDateTime BE_PM_ONE_TWO_START_TIME = THE_DAY_AFTER_TOMORROW.atTime(13, 0); - public static final LocalDateTime BE_PM_ONE_TWO_END_TIME = THE_DAY_AFTER_TOMORROW.atTime(14, 0); + public static final ZonedDateTime BE_PM_ONE_TWO_START_TIME_KST = THE_DAY_AFTER_TOMORROW.atTime(13, 0).atZone(KST.toZoneId()); + public static final ZonedDateTime BE_PM_ONE_TWO_END_TIME_KST = THE_DAY_AFTER_TOMORROW.atTime(14, 0).atZone(KST.toZoneId()); public static final String BE_PM_ONE_TWO_DESCRIPTION = "찜꽁 2차 회의"; public static final String BE_PM_ONE_TWO_USERNAME = USER_NAME; public static final String BE_PM_ONE_TWO_PW = RESERVATION_PW; - public static final LocalDateTime BE_NEXT_DAY_PM_FOUR_TO_SIX_START_TIME = THE_DAY_AFTER_TOMORROW.plusDays(1L).atTime(16, 0); - public static final LocalDateTime BE_NEXT_DAY_PM_FOUR_TO_SIX_END_TIME = THE_DAY_AFTER_TOMORROW.plusDays(1L).atTime(18, 0); + public static final ZonedDateTime BE_NEXT_DAY_PM_FOUR_TO_SIX_START_TIME_KST = THE_DAY_AFTER_TOMORROW.plusDays(1L).atTime(16, 0).atZone(KST.toZoneId()); + public static final ZonedDateTime BE_NEXT_DAY_PM_FOUR_TO_SIX_END_TIME_KST = THE_DAY_AFTER_TOMORROW.plusDays(1L).atTime(18, 0).atZone(KST.toZoneId()); public static final String BE_NEXT_DAY_PM_FOUR_TO_SIX_DESCRIPTION = "찜꽁 3차 회의"; public static final String BE_NEXT_DAY_PM_FOUR_TO_SIX_USERNAME = USER_NAME; public static final String BE_NEXT_DAY_PM_FOUR_TO_SIX_PW = RESERVATION_PW; - public static final LocalDateTime FE1_AM_TEN_ELEVEN_START_TIME = THE_DAY_AFTER_TOMORROW.atTime(10, 0); - public static final LocalDateTime FE1_AM_TEN_ELEVEN_END_TIME = THE_DAY_AFTER_TOMORROW.atTime(11, 0); + public static final ZonedDateTime FE1_AM_TEN_ELEVEN_START_TIME_KST = THE_DAY_AFTER_TOMORROW.atTime(10, 0).atZone(KST.toZoneId()); + public static final ZonedDateTime FE1_AM_TEN_ELEVEN_END_TIME_KST = THE_DAY_AFTER_TOMORROW.atTime(11, 0).atZone(KST.toZoneId()); public static final String FE1_AM_TEN_ELEVEN_DESCRIPTION = "찜꽁 5차 회의"; public static final String FE1_AM_TEN_ELEVEN_USERNAME = USER_NAME; public static final String FE1_AM_TEN_ELEVEN_PW = RESERVATION_PW; diff --git a/backend/src/test/java/com/woowacourse/zzimkkong/controller/AcceptanceTest.java b/backend/src/test/java/com/woowacourse/zzimkkong/controller/AcceptanceTest.java index 1aac0fa30..4c98cb85d 100644 --- a/backend/src/test/java/com/woowacourse/zzimkkong/controller/AcceptanceTest.java +++ b/backend/src/test/java/com/woowacourse/zzimkkong/controller/AcceptanceTest.java @@ -1,6 +1,7 @@ package com.woowacourse.zzimkkong.controller; import com.woowacourse.zzimkkong.DatabaseCleaner; +import com.woowacourse.zzimkkong.domain.Reservation; import com.woowacourse.zzimkkong.dto.map.MapCreateUpdateRequest; import com.woowacourse.zzimkkong.dto.member.LoginRequest; import com.woowacourse.zzimkkong.dto.member.MemberSaveRequest; @@ -9,7 +10,6 @@ import com.woowacourse.zzimkkong.dto.space.SpaceCreateUpdateRequest; import com.woowacourse.zzimkkong.infrastructure.oauth.GithubRequester; import com.woowacourse.zzimkkong.infrastructure.oauth.GoogleRequester; -import com.woowacourse.zzimkkong.infrastructure.thumbnail.StorageUploader; import io.restassured.RestAssured; import io.restassured.builder.RequestSpecBuilder; import io.restassured.specification.RequestSpecification; @@ -27,15 +27,16 @@ import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit.jupiter.SpringExtension; -import java.io.InputStream; +import java.time.LocalDate; +import java.util.List; +import java.util.stream.Collectors; import static com.woowacourse.zzimkkong.Constants.*; import static com.woowacourse.zzimkkong.DocumentUtils.setRequestSpecification; import static com.woowacourse.zzimkkong.controller.AuthControllerTest.getToken; import static com.woowacourse.zzimkkong.controller.MemberControllerTest.saveMember; +import static com.woowacourse.zzimkkong.infrastructure.datetime.TimeZoneUtils.KST; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.BDDMockito.given; import static org.springframework.restdocs.restassured3.RestAssuredRestDocumentation.documentationConfiguration; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @@ -88,9 +89,6 @@ class AcceptanceTest { @Autowired private DatabaseCleaner databaseCleaner; - @MockBean - private StorageUploader storageUploader; - @Autowired protected PasswordEncoder passwordEncoder; @@ -110,13 +108,16 @@ void setUp(RestDocumentationContextProvider restDocumentation) { saveMember(memberSaveRequest); accessToken = getToken(); - - given(storageUploader.upload(anyString(), anyString(), any(InputStream.class))) - .willReturn(MAP_IMAGE_URL); } @AfterEach void deleteAll() { databaseCleaner.execute(); } + + protected List filterReservationsByKST(List reservations, LocalDate date) { + return reservations.stream() + .filter(reservation -> reservation.isBookedOn(date, KST)) + .collect(Collectors.toList()); + } } diff --git a/backend/src/test/java/com/woowacourse/zzimkkong/controller/AdminControllerTest.java b/backend/src/test/java/com/woowacourse/zzimkkong/controller/AdminControllerTest.java index c63118ed4..512d68893 100644 --- a/backend/src/test/java/com/woowacourse/zzimkkong/controller/AdminControllerTest.java +++ b/backend/src/test/java/com/woowacourse/zzimkkong/controller/AdminControllerTest.java @@ -32,6 +32,7 @@ import static com.woowacourse.zzimkkong.controller.ManagerReservationControllerTest.saveReservation; import static com.woowacourse.zzimkkong.controller.ManagerSpaceControllerTest.saveSpace; import static com.woowacourse.zzimkkong.controller.MapControllerTest.saveMap; +import static com.woowacourse.zzimkkong.infrastructure.datetime.TimeZoneUtils.KST; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -40,7 +41,7 @@ class AdminControllerTest extends AcceptanceTest { private SlackService slackService; private static final Member POBI = new Member(memberSaveRequest.getEmail(), memberSaveRequest.getPassword(), memberSaveRequest.getOrganization()); - private static final Map LUTHER = new Map(LUTHER_NAME, MAP_DRAWING_DATA, MAP_IMAGE_URL, POBI); + private static final Map LUTHER = new Map(LUTHER_NAME, MAP_DRAWING_DATA, MAP_SVG, POBI); private static final Setting BE_SETTING = Setting.builder() .availableStartTime(BE_AVAILABLE_START_TIME) .availableEndTime(BE_AVAILABLE_END_TIME) @@ -152,15 +153,15 @@ void getReservations() { ExtractableResponse saveBeSpaceResponse = saveSpace(spaceApi, beSpaceCreateUpdateRequest); String beReservationApi = saveBeSpaceResponse.header("location") + "/reservations"; ReservationCreateUpdateWithPasswordRequest newReservationCreateUpdateWithPasswordRequest = new ReservationCreateUpdateWithPasswordRequest( - THE_DAY_AFTER_TOMORROW.atTime(19, 0), - THE_DAY_AFTER_TOMORROW.atTime(20, 0), + THE_DAY_AFTER_TOMORROW.atTime(19, 0).atZone(KST.toZoneId()), + THE_DAY_AFTER_TOMORROW.atTime(20, 0).atZone(KST.toZoneId()), SALLY_PW, SALLY_NAME, SALLY_DESCRIPTION); saveReservation(beReservationApi, newReservationCreateUpdateWithPasswordRequest); Reservation reservation = Reservation.builder() - .startTime(newReservationCreateUpdateWithPasswordRequest.getStartDateTime()) - .endTime(newReservationCreateUpdateWithPasswordRequest.getEndDateTime()) + .startTime(newReservationCreateUpdateWithPasswordRequest.localStartDateTime()) + .endTime(newReservationCreateUpdateWithPasswordRequest.localEndDateTime()) .userName(newReservationCreateUpdateWithPasswordRequest.getName()) .password(newReservationCreateUpdateWithPasswordRequest.getPassword()) .description(newReservationCreateUpdateWithPasswordRequest.getDescription()) diff --git a/backend/src/test/java/com/woowacourse/zzimkkong/controller/GuestReservationControllerTest.java b/backend/src/test/java/com/woowacourse/zzimkkong/controller/GuestReservationControllerTest.java index f515d721c..a118cd3a2 100644 --- a/backend/src/test/java/com/woowacourse/zzimkkong/controller/GuestReservationControllerTest.java +++ b/backend/src/test/java/com/woowacourse/zzimkkong/controller/GuestReservationControllerTest.java @@ -14,11 +14,14 @@ import org.springframework.http.MediaType; import java.util.Arrays; +import java.util.List; import static com.woowacourse.zzimkkong.Constants.*; import static com.woowacourse.zzimkkong.DocumentUtils.*; import static com.woowacourse.zzimkkong.controller.ManagerSpaceControllerTest.saveSpace; import static com.woowacourse.zzimkkong.controller.MapControllerTest.saveMap; +import static com.woowacourse.zzimkkong.infrastructure.datetime.TimeZoneUtils.KST; +import static com.woowacourse.zzimkkong.infrastructure.datetime.TimeZoneUtils.UTC; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.restdocs.restassured3.RestAssuredRestDocumentation.document; @@ -55,14 +58,14 @@ void setUp() { .replaceAll("managers", "guests") + "/reservations"; reservationCreateUpdateWithPasswordRequest = new ReservationCreateUpdateWithPasswordRequest( - THE_DAY_AFTER_TOMORROW.atTime(15, 0), - THE_DAY_AFTER_TOMORROW.atTime(16, 0), + THE_DAY_AFTER_TOMORROW.atTime(15, 0).atZone(KST.toZoneId()), + THE_DAY_AFTER_TOMORROW.atTime(16, 0).atZone(KST.toZoneId()), SALLY_PW, SALLY_NAME, SALLY_DESCRIPTION); Member pobi = new Member(EMAIL, passwordEncoder.encode(PW), ORGANIZATION); - Map luther = new Map(LUTHER_NAME, MAP_DRAWING_DATA, MAP_IMAGE_URL, pobi); + Map luther = new Map(LUTHER_NAME, MAP_DRAWING_DATA, MAP_SVG, pobi); Setting beSetting = Setting.builder() .availableStartTime(BE_AVAILABLE_START_TIME) @@ -106,8 +109,8 @@ void setUp() { saveExampleReservations(); savedReservationId = getReservationIdAfterSave(beReservationApi, reservationCreateUpdateWithPasswordRequest); savedReservation = Reservation.builder() - .startTime(reservationCreateUpdateWithPasswordRequest.getStartDateTime()) - .endTime(reservationCreateUpdateWithPasswordRequest.getEndDateTime()) + .startTime(reservationCreateUpdateWithPasswordRequest.localStartDateTime()) + .endTime(reservationCreateUpdateWithPasswordRequest.localEndDateTime()) .password(reservationCreateUpdateWithPasswordRequest.getPassword()) .userName(reservationCreateUpdateWithPasswordRequest.getName()) .description(reservationCreateUpdateWithPasswordRequest.getDescription()) @@ -120,8 +123,8 @@ void setUp() { void save() { //given ReservationCreateUpdateWithPasswordRequest newReservationCreateUpdateWithPasswordRequest = new ReservationCreateUpdateWithPasswordRequest( - THE_DAY_AFTER_TOMORROW.atTime(19, 0), - THE_DAY_AFTER_TOMORROW.atTime(20, 0), + THE_DAY_AFTER_TOMORROW.atTime(19, 0).atZone(KST.toZoneId()), + THE_DAY_AFTER_TOMORROW.atTime(20, 0).atZone(KST.toZoneId()), SALLY_PW, SALLY_NAME, SALLY_DESCRIPTION); @@ -140,10 +143,13 @@ void find() { ExtractableResponse response = findReservations(beReservationApi, THE_DAY_AFTER_TOMORROW.toString()); ReservationFindResponse actualResponse = response.as(ReservationFindResponse.class); - ReservationFindResponse expectedResponse = ReservationFindResponse.from( + + List expectedFindReservations = filterReservationsByKST( Arrays.asList(savedReservation, beAmZeroOne, - bePmOneTwo)); + bePmOneTwo), + THE_DAY_AFTER_TOMORROW); + ReservationFindResponse expectedResponse = ReservationFindResponse.from(expectedFindReservations); //then assertThat(response.statusCode()).isEqualTo(HttpStatus.OK.value()); @@ -161,12 +167,17 @@ void findAll() { ExtractableResponse response = findAllReservations(api, THE_DAY_AFTER_TOMORROW.toString()); ReservationFindAllResponse actualResponse = response.as(ReservationFindAllResponse.class); - ReservationFindAllResponse expectedResponse = ReservationFindAllResponse.of( - Arrays.asList(be, fe), - Arrays.asList(savedReservation, + + List expectedFindReservations = filterReservationsByKST( + Arrays.asList( + savedReservation, beAmZeroOne, bePmOneTwo, - fe1ZeroOne)); + fe1ZeroOne), + THE_DAY_AFTER_TOMORROW); + ReservationFindAllResponse expectedResponse = ReservationFindAllResponse.of( + Arrays.asList(be, fe), + expectedFindReservations); //then assertThat(response.statusCode()).isEqualTo(HttpStatus.OK.value()); @@ -182,8 +193,8 @@ void findAll() { void update_sameSpace() { //given ReservationCreateUpdateWithPasswordRequest reservationCreateUpdateWithPasswordRequestSameSpace = new ReservationCreateUpdateWithPasswordRequest( - THE_DAY_AFTER_TOMORROW.atTime(19, 0), - THE_DAY_AFTER_TOMORROW.atTime(20, 30), + THE_DAY_AFTER_TOMORROW.atTime(19, 0).atZone(KST.toZoneId()), + THE_DAY_AFTER_TOMORROW.atTime(20, 30).atZone(KST.toZoneId()), reservationCreateUpdateWithPasswordRequest.getPassword(), "sally", "회의입니다." @@ -198,8 +209,8 @@ void update_sameSpace() { ReservationResponse expectedResponse = ReservationResponse.from( Reservation.builder() .id(savedReservationId) - .startTime(reservationCreateUpdateWithPasswordRequestSameSpace.getStartDateTime()) - .endTime(reservationCreateUpdateWithPasswordRequestSameSpace.getEndDateTime()) + .startTime(reservationCreateUpdateWithPasswordRequestSameSpace.localStartDateTime()) + .endTime(reservationCreateUpdateWithPasswordRequestSameSpace.localEndDateTime()) .description(reservationCreateUpdateWithPasswordRequestSameSpace.getDescription()) .userName(reservationCreateUpdateWithPasswordRequestSameSpace.getName()) .space(be) @@ -217,8 +228,8 @@ void update_sameSpace() { void update_spaceUpdate() { //given ReservationCreateUpdateWithPasswordRequest reservationCreateUpdateWithPasswordRequestDifferentSpace = new ReservationCreateUpdateWithPasswordRequest( - THE_DAY_AFTER_TOMORROW.atTime(19, 30), - THE_DAY_AFTER_TOMORROW.atTime(20, 30), + THE_DAY_AFTER_TOMORROW.atTime(19, 30).atZone(KST.toZoneId()), + THE_DAY_AFTER_TOMORROW.atTime(20, 30).atZone(KST.toZoneId()), SALLY_PW, "sally", "회의입니다." @@ -231,19 +242,20 @@ void update_spaceUpdate() { ExtractableResponse findResponse = findReservations(fe1ReservationApi, THE_DAY_AFTER_TOMORROW.toString()); ReservationFindResponse actualResponse = findResponse.as(ReservationFindResponse.class); - ReservationFindResponse expectedResponse = ReservationFindResponse.from( + + List expectedFindReservations = filterReservationsByKST( Arrays.asList( Reservation.builder() - .startTime(reservationCreateUpdateWithPasswordRequestDifferentSpace.getStartDateTime()) - .endTime(reservationCreateUpdateWithPasswordRequestDifferentSpace.getEndDateTime()) + .startTime(reservationCreateUpdateWithPasswordRequestDifferentSpace.localStartDateTime()) + .endTime(reservationCreateUpdateWithPasswordRequestDifferentSpace.localEndDateTime()) .description(reservationCreateUpdateWithPasswordRequestDifferentSpace.getDescription()) .userName(reservationCreateUpdateWithPasswordRequestDifferentSpace.getName()) .password(reservationCreateUpdateWithPasswordRequestDifferentSpace.getPassword()) .space(fe) .build(), - fe1ZeroOne - ) - ); + fe1ZeroOne), + THE_DAY_AFTER_TOMORROW); + ReservationFindResponse expectedResponse = ReservationFindResponse.from(expectedFindReservations); //then assertThat(updateResponse.statusCode()).isEqualTo(HttpStatus.OK.value()); @@ -285,37 +297,37 @@ void findOne() { private void saveExampleReservations() { ReservationCreateUpdateWithPasswordRequest beAmZeroOneRequest = new ReservationCreateUpdateWithPasswordRequest( - BE_AM_TEN_ELEVEN_START_TIME, - BE_AM_TEN_ELEVEN_END_TIME, + BE_AM_TEN_ELEVEN_START_TIME_KST, + BE_AM_TEN_ELEVEN_END_TIME_KST, BE_AM_TEN_ELEVEN_PW, BE_AM_TEN_ELEVEN_USERNAME, BE_AM_TEN_ELEVEN_DESCRIPTION); ReservationCreateUpdateWithPasswordRequest bePmOneTwoRequest = new ReservationCreateUpdateWithPasswordRequest( - BE_PM_ONE_TWO_START_TIME, - BE_PM_ONE_TWO_END_TIME, + BE_PM_ONE_TWO_START_TIME_KST, + BE_PM_ONE_TWO_END_TIME_KST, BE_PM_ONE_TWO_PW, BE_PM_ONE_TWO_USERNAME, BE_PM_ONE_TWO_DESCRIPTION); ReservationCreateUpdateWithPasswordRequest beNextDayAmSixTwelveRequest = new ReservationCreateUpdateWithPasswordRequest( - BE_NEXT_DAY_PM_FOUR_TO_SIX_START_TIME, - BE_NEXT_DAY_PM_FOUR_TO_SIX_END_TIME, + BE_NEXT_DAY_PM_FOUR_TO_SIX_START_TIME_KST, + BE_NEXT_DAY_PM_FOUR_TO_SIX_END_TIME_KST, BE_NEXT_DAY_PM_FOUR_TO_SIX_PW, BE_NEXT_DAY_PM_FOUR_TO_SIX_USERNAME, BE_NEXT_DAY_PM_FOUR_TO_SIX_DESCRIPTION); ReservationCreateUpdateWithPasswordRequest feZeroOneRequest = new ReservationCreateUpdateWithPasswordRequest( - FE1_AM_TEN_ELEVEN_START_TIME, - FE1_AM_TEN_ELEVEN_END_TIME, + FE1_AM_TEN_ELEVEN_START_TIME_KST, + FE1_AM_TEN_ELEVEN_END_TIME_KST, FE1_AM_TEN_ELEVEN_PW, FE1_AM_TEN_ELEVEN_USERNAME, FE1_AM_TEN_ELEVEN_DESCRIPTION); beAmZeroOne = Reservation.builder() .id(getReservationIdAfterSave(beReservationApi, beAmZeroOneRequest)) - .startTime(BE_AM_TEN_ELEVEN_START_TIME) - .endTime(BE_AM_TEN_ELEVEN_END_TIME) + .startTime(BE_AM_TEN_ELEVEN_START_TIME_KST.withZoneSameInstant(UTC.toZoneId()).toLocalDateTime()) + .endTime(BE_AM_TEN_ELEVEN_END_TIME_KST.withZoneSameInstant(UTC.toZoneId()).toLocalDateTime()) .description(BE_AM_TEN_ELEVEN_DESCRIPTION) .userName(BE_AM_TEN_ELEVEN_USERNAME) .password(BE_AM_TEN_ELEVEN_PW) @@ -324,8 +336,8 @@ private void saveExampleReservations() { bePmOneTwo = Reservation.builder() .id(getReservationIdAfterSave(beReservationApi, bePmOneTwoRequest)) - .startTime(BE_PM_ONE_TWO_START_TIME) - .endTime(BE_PM_ONE_TWO_END_TIME) + .startTime(BE_PM_ONE_TWO_START_TIME_KST.withZoneSameInstant(UTC.toZoneId()).toLocalDateTime()) + .endTime(BE_PM_ONE_TWO_END_TIME_KST.withZoneSameInstant(UTC.toZoneId()).toLocalDateTime()) .description(BE_PM_ONE_TWO_DESCRIPTION) .userName(BE_PM_ONE_TWO_USERNAME) .password(BE_PM_ONE_TWO_PW) @@ -336,8 +348,8 @@ private void saveExampleReservations() { fe1ZeroOne = Reservation.builder() .id(getReservationIdAfterSave(fe1ReservationApi, feZeroOneRequest)) - .startTime(FE1_AM_TEN_ELEVEN_START_TIME) - .endTime(FE1_AM_TEN_ELEVEN_END_TIME) + .startTime(FE1_AM_TEN_ELEVEN_START_TIME_KST.withZoneSameInstant(UTC.toZoneId()).toLocalDateTime()) + .endTime(FE1_AM_TEN_ELEVEN_END_TIME_KST.withZoneSameInstant(UTC.toZoneId()).toLocalDateTime()) .description(FE1_AM_TEN_ELEVEN_DESCRIPTION) .userName(FE1_AM_TEN_ELEVEN_USERNAME) .password(FE1_AM_TEN_ELEVEN_PW) diff --git a/backend/src/test/java/com/woowacourse/zzimkkong/controller/GuestSpaceControllerTest.java b/backend/src/test/java/com/woowacourse/zzimkkong/controller/GuestSpaceControllerTest.java index 2b54ee2ff..76404468f 100644 --- a/backend/src/test/java/com/woowacourse/zzimkkong/controller/GuestSpaceControllerTest.java +++ b/backend/src/test/java/com/woowacourse/zzimkkong/controller/GuestSpaceControllerTest.java @@ -1,5 +1,6 @@ package com.woowacourse.zzimkkong.controller; +import com.woowacourse.zzimkkong.Constants; import com.woowacourse.zzimkkong.domain.Map; import com.woowacourse.zzimkkong.domain.Member; import com.woowacourse.zzimkkong.domain.Setting; @@ -39,7 +40,7 @@ void setUp() { Long feSpaceId = Long.valueOf(saveFe1SpaceResponse.header("location").split("/")[6]); Member pobi = new Member(EMAIL, passwordEncoder.encode(PW), ORGANIZATION); - Map luther = new Map(LUTHER_NAME, MAP_DRAWING_DATA, MAP_IMAGE_URL, pobi); + Map luther = new Map(LUTHER_NAME, MAP_DRAWING_DATA, MAP_SVG, pobi); Setting beSetting = Setting.builder() .availableStartTime(BE_AVAILABLE_START_TIME) .availableEndTime(BE_AVAILABLE_END_TIME) diff --git a/backend/src/test/java/com/woowacourse/zzimkkong/controller/ManagerReservationControllerTest.java b/backend/src/test/java/com/woowacourse/zzimkkong/controller/ManagerReservationControllerTest.java index d907aebd7..832329a15 100644 --- a/backend/src/test/java/com/woowacourse/zzimkkong/controller/ManagerReservationControllerTest.java +++ b/backend/src/test/java/com/woowacourse/zzimkkong/controller/ManagerReservationControllerTest.java @@ -15,11 +15,14 @@ import org.springframework.http.MediaType; import java.util.Arrays; +import java.util.List; import static com.woowacourse.zzimkkong.Constants.*; import static com.woowacourse.zzimkkong.DocumentUtils.*; import static com.woowacourse.zzimkkong.controller.ManagerSpaceControllerTest.saveSpace; import static com.woowacourse.zzimkkong.controller.MapControllerTest.saveMap; +import static com.woowacourse.zzimkkong.infrastructure.datetime.TimeZoneUtils.KST; +import static com.woowacourse.zzimkkong.infrastructure.datetime.TimeZoneUtils.UTC; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.restdocs.restassured3.RestAssuredRestDocumentation.document; @@ -53,14 +56,14 @@ void setUp() { fe1ReservationApi = saveFe1SpaceResponse.header("location") + "/reservations"; ReservationCreateUpdateWithPasswordRequest reservationCreateUpdateWithPasswordRequest = new ReservationCreateUpdateWithPasswordRequest( - THE_DAY_AFTER_TOMORROW.atTime(15, 0), - THE_DAY_AFTER_TOMORROW.atTime(16, 0), + THE_DAY_AFTER_TOMORROW.atTime(15, 0).atZone(KST.toZoneId()), + THE_DAY_AFTER_TOMORROW.atTime(16, 0).atZone(KST.toZoneId()), SALLY_PW, SALLY_NAME, SALLY_DESCRIPTION); Member pobi = new Member(EMAIL, passwordEncoder.encode(PW), ORGANIZATION); - Map luther = new Map(LUTHER_NAME, MAP_DRAWING_DATA, MAP_IMAGE_URL, pobi); + Map luther = new Map(LUTHER_NAME, MAP_DRAWING_DATA, MAP_SVG, pobi); Setting beSetting = Setting.builder() .availableStartTime(BE_AVAILABLE_START_TIME) @@ -104,8 +107,8 @@ void setUp() { saveExampleReservations(); savedReservationId = getReservationIdAfterSave(beReservationApi, reservationCreateUpdateWithPasswordRequest); savedReservation = Reservation.builder() - .startTime(reservationCreateUpdateWithPasswordRequest.getStartDateTime()) - .endTime(reservationCreateUpdateWithPasswordRequest.getEndDateTime()) + .startTime(reservationCreateUpdateWithPasswordRequest.localStartDateTime()) + .endTime(reservationCreateUpdateWithPasswordRequest.localEndDateTime()) .password(reservationCreateUpdateWithPasswordRequest.getPassword()) .userName(reservationCreateUpdateWithPasswordRequest.getName()) .description(reservationCreateUpdateWithPasswordRequest.getDescription()) @@ -118,8 +121,8 @@ void setUp() { void save() { //given ReservationCreateUpdateWithPasswordRequest newReservationCreateUpdateWithPasswordRequest = new ReservationCreateUpdateWithPasswordRequest( - THE_DAY_AFTER_TOMORROW.atTime(19, 0), - THE_DAY_AFTER_TOMORROW.atTime(20, 0), + THE_DAY_AFTER_TOMORROW.atTime(19, 0).atZone(KST.toZoneId()), + THE_DAY_AFTER_TOMORROW.atTime(20, 0).atZone(KST.toZoneId()), SALLY_PW, SALLY_NAME, SALLY_DESCRIPTION); @@ -138,10 +141,10 @@ void find() { ExtractableResponse response = findReservations(beReservationApi, THE_DAY_AFTER_TOMORROW.toString()); ReservationFindResponse actualResponse = response.as(ReservationFindResponse.class); - ReservationFindResponse expectedResponse = ReservationFindResponse.from( - Arrays.asList(savedReservation, - beAmZeroOne, - bePmOneTwo)); + List expectedFindReservations = filterReservationsByKST( + Arrays.asList(savedReservation, beAmZeroOne, bePmOneTwo), + THE_DAY_AFTER_TOMORROW); + ReservationFindResponse expectedResponse = ReservationFindResponse.from(expectedFindReservations); //then assertThat(response.statusCode()).isEqualTo(HttpStatus.OK.value()); @@ -159,12 +162,13 @@ void findAll() { ExtractableResponse response = findAllReservations(api, THE_DAY_AFTER_TOMORROW.toString()); ReservationFindAllResponse actualResponse = response.as(ReservationFindAllResponse.class); + + List expectedFindReservations = filterReservationsByKST( + Arrays.asList(savedReservation, beAmZeroOne, bePmOneTwo, fe1ZeroOne), + THE_DAY_AFTER_TOMORROW); ReservationFindAllResponse expectedResponse = ReservationFindAllResponse.of( Arrays.asList(be, fe), - Arrays.asList(savedReservation, - beAmZeroOne, - bePmOneTwo, - fe1ZeroOne)); + expectedFindReservations); //then assertThat(response.statusCode()).isEqualTo(HttpStatus.OK.value()); @@ -196,8 +200,8 @@ void findOne() { void update_sameSpace() { //given ReservationCreateUpdateRequest reservationCreateUpdateRequestSameSpace = new ReservationCreateUpdateRequest( - THE_DAY_AFTER_TOMORROW.atTime(19, 0), - THE_DAY_AFTER_TOMORROW.atTime(20, 30), + THE_DAY_AFTER_TOMORROW.atTime(19, 0).atZone(KST.toZoneId()), + THE_DAY_AFTER_TOMORROW.atTime(20, 30).atZone(KST.toZoneId()), "sally", "회의입니다." ); @@ -211,8 +215,8 @@ void update_sameSpace() { ReservationResponse expectedResponse = ReservationResponse.from( Reservation.builder() .id(savedReservationId) - .startTime(reservationCreateUpdateRequestSameSpace.getStartDateTime()) - .endTime(reservationCreateUpdateRequestSameSpace.getEndDateTime()) + .startTime(reservationCreateUpdateRequestSameSpace.localStartDateTime()) + .endTime(reservationCreateUpdateRequestSameSpace.localEndDateTime()) .description(reservationCreateUpdateRequestSameSpace.getDescription()) .userName(reservationCreateUpdateRequestSameSpace.getName()) .space(be) @@ -231,8 +235,8 @@ void update_sameSpace() { void update_spaceUpdate() { //given ReservationCreateUpdateWithPasswordRequest reservationCreateUpdateWithPasswordRequestDifferentSpace = new ReservationCreateUpdateWithPasswordRequest( - THE_DAY_AFTER_TOMORROW.atTime(19, 30), - THE_DAY_AFTER_TOMORROW.atTime(20, 30), + THE_DAY_AFTER_TOMORROW.atTime(19, 30).atZone(KST.toZoneId()), + THE_DAY_AFTER_TOMORROW.atTime(20, 30).atZone(KST.toZoneId()), SALLY_PW, "sally", "회의입니다." @@ -247,19 +251,20 @@ void update_spaceUpdate() { THE_DAY_AFTER_TOMORROW.toString()); ReservationFindResponse actualResponse = findResponse.as(ReservationFindResponse.class); - ReservationFindResponse expectedResponse = ReservationFindResponse.from( + + List expectedFindReservations = filterReservationsByKST( Arrays.asList( Reservation.builder() - .startTime(reservationCreateUpdateWithPasswordRequestDifferentSpace.getStartDateTime()) - .endTime(reservationCreateUpdateWithPasswordRequestDifferentSpace.getEndDateTime()) + .startTime(reservationCreateUpdateWithPasswordRequestDifferentSpace.localStartDateTime()) + .endTime(reservationCreateUpdateWithPasswordRequestDifferentSpace.localEndDateTime()) .description(reservationCreateUpdateWithPasswordRequestDifferentSpace.getDescription()) .userName(reservationCreateUpdateWithPasswordRequestDifferentSpace.getName()) .password(reservationCreateUpdateWithPasswordRequestDifferentSpace.getPassword()) .space(fe) .build(), - fe1ZeroOne - ) - ); + fe1ZeroOne), + THE_DAY_AFTER_TOMORROW); + ReservationFindResponse expectedResponse = ReservationFindResponse.from(expectedFindReservations); //then assertThat(updateResponse.statusCode()).isEqualTo(HttpStatus.OK.value()); @@ -284,37 +289,37 @@ void delete() { private void saveExampleReservations() { ReservationCreateUpdateWithPasswordRequest beAmZeroOneRequest = new ReservationCreateUpdateWithPasswordRequest( - BE_AM_TEN_ELEVEN_START_TIME, - BE_AM_TEN_ELEVEN_END_TIME, + BE_AM_TEN_ELEVEN_START_TIME_KST, + BE_AM_TEN_ELEVEN_END_TIME_KST, BE_AM_TEN_ELEVEN_PW, BE_AM_TEN_ELEVEN_USERNAME, BE_AM_TEN_ELEVEN_DESCRIPTION); ReservationCreateUpdateWithPasswordRequest bePmOneTwoRequest = new ReservationCreateUpdateWithPasswordRequest( - BE_PM_ONE_TWO_START_TIME, - BE_PM_ONE_TWO_END_TIME, + BE_PM_ONE_TWO_START_TIME_KST, + BE_PM_ONE_TWO_END_TIME_KST, BE_PM_ONE_TWO_PW, BE_PM_ONE_TWO_USERNAME, BE_PM_ONE_TWO_DESCRIPTION); ReservationCreateUpdateWithPasswordRequest beNextDayAmSixTwelveRequest = new ReservationCreateUpdateWithPasswordRequest( - BE_NEXT_DAY_PM_FOUR_TO_SIX_START_TIME, - BE_NEXT_DAY_PM_FOUR_TO_SIX_END_TIME, + BE_NEXT_DAY_PM_FOUR_TO_SIX_START_TIME_KST, + BE_NEXT_DAY_PM_FOUR_TO_SIX_END_TIME_KST, BE_NEXT_DAY_PM_FOUR_TO_SIX_PW, BE_NEXT_DAY_PM_FOUR_TO_SIX_USERNAME, BE_NEXT_DAY_PM_FOUR_TO_SIX_DESCRIPTION); ReservationCreateUpdateWithPasswordRequest feZeroOneRequest = new ReservationCreateUpdateWithPasswordRequest( - FE1_AM_TEN_ELEVEN_START_TIME, - FE1_AM_TEN_ELEVEN_END_TIME, + FE1_AM_TEN_ELEVEN_START_TIME_KST, + FE1_AM_TEN_ELEVEN_END_TIME_KST, FE1_AM_TEN_ELEVEN_PW, FE1_AM_TEN_ELEVEN_USERNAME, FE1_AM_TEN_ELEVEN_DESCRIPTION); beAmZeroOne = Reservation.builder() .id(getReservationIdAfterSave(beReservationApi, beAmZeroOneRequest)) - .startTime(BE_AM_TEN_ELEVEN_START_TIME) - .endTime(BE_AM_TEN_ELEVEN_END_TIME) + .startTime(BE_AM_TEN_ELEVEN_START_TIME_KST.withZoneSameInstant(UTC.toZoneId()).toLocalDateTime()) + .endTime(BE_AM_TEN_ELEVEN_END_TIME_KST.withZoneSameInstant(UTC.toZoneId()).toLocalDateTime()) .description(BE_AM_TEN_ELEVEN_DESCRIPTION) .userName(BE_AM_TEN_ELEVEN_USERNAME) .password(BE_AM_TEN_ELEVEN_PW) @@ -323,8 +328,8 @@ private void saveExampleReservations() { bePmOneTwo = Reservation.builder() .id(getReservationIdAfterSave(beReservationApi, bePmOneTwoRequest)) - .startTime(BE_PM_ONE_TWO_START_TIME) - .endTime(BE_PM_ONE_TWO_END_TIME) + .startTime(BE_PM_ONE_TWO_START_TIME_KST.withZoneSameInstant(UTC.toZoneId()).toLocalDateTime()) + .endTime(BE_PM_ONE_TWO_END_TIME_KST.withZoneSameInstant(UTC.toZoneId()).toLocalDateTime()) .description(BE_PM_ONE_TWO_DESCRIPTION) .userName(BE_PM_ONE_TWO_USERNAME) .password(BE_PM_ONE_TWO_PW) @@ -335,8 +340,8 @@ private void saveExampleReservations() { fe1ZeroOne = Reservation.builder() .id(getReservationIdAfterSave(fe1ReservationApi, feZeroOneRequest)) - .startTime(FE1_AM_TEN_ELEVEN_START_TIME) - .endTime(FE1_AM_TEN_ELEVEN_END_TIME) + .startTime(FE1_AM_TEN_ELEVEN_START_TIME_KST.withZoneSameInstant(UTC.toZoneId()).toLocalDateTime()) + .endTime(FE1_AM_TEN_ELEVEN_END_TIME_KST.withZoneSameInstant(UTC.toZoneId()).toLocalDateTime()) .description(FE1_AM_TEN_ELEVEN_DESCRIPTION) .userName(FE1_AM_TEN_ELEVEN_USERNAME) .password(FE1_AM_TEN_ELEVEN_PW) diff --git a/backend/src/test/java/com/woowacourse/zzimkkong/controller/ManagerSpaceControllerTest.java b/backend/src/test/java/com/woowacourse/zzimkkong/controller/ManagerSpaceControllerTest.java index 28ef82ed8..5464241b3 100644 --- a/backend/src/test/java/com/woowacourse/zzimkkong/controller/ManagerSpaceControllerTest.java +++ b/backend/src/test/java/com/woowacourse/zzimkkong/controller/ManagerSpaceControllerTest.java @@ -41,7 +41,7 @@ void setUp() { Long feSpaceId = Long.valueOf(saveFe1SpaceResponse.header("location").split("/")[6]); Member pobi = new Member(EMAIL, passwordEncoder.encode(PW), ORGANIZATION); - Map luther = new Map(LUTHER_NAME, MAP_DRAWING_DATA, MAP_IMAGE_URL, pobi); + Map luther = new Map(LUTHER_NAME, MAP_DRAWING_DATA, MAP_SVG, pobi); Setting beSetting = Setting.builder() .availableStartTime(BE_AVAILABLE_START_TIME) .availableEndTime(BE_AVAILABLE_END_TIME) diff --git a/backend/src/test/java/com/woowacourse/zzimkkong/controller/MapControllerTest.java b/backend/src/test/java/com/woowacourse/zzimkkong/controller/MapControllerTest.java index 61fdcc1de..81159273b 100644 --- a/backend/src/test/java/com/woowacourse/zzimkkong/controller/MapControllerTest.java +++ b/backend/src/test/java/com/woowacourse/zzimkkong/controller/MapControllerTest.java @@ -1,10 +1,9 @@ package com.woowacourse.zzimkkong.controller; +import com.woowacourse.zzimkkong.Constants; import com.woowacourse.zzimkkong.domain.Map; import com.woowacourse.zzimkkong.domain.Member; -import com.woowacourse.zzimkkong.dto.map.MapCreateUpdateRequest; -import com.woowacourse.zzimkkong.dto.map.MapFindAllResponse; -import com.woowacourse.zzimkkong.dto.map.MapFindResponse; +import com.woowacourse.zzimkkong.dto.map.*; import com.woowacourse.zzimkkong.infrastructure.auth.AuthorizationExtractor; import com.woowacourse.zzimkkong.infrastructure.sharingid.SharingIdGenerator; import io.restassured.RestAssured; @@ -43,8 +42,8 @@ void setUp() { // For Test Comparison pobi = new Member(EMAIL, passwordEncoder.encode(PW), ORGANIZATION); - luther = new Map(LUTHER_NAME, MAP_DRAWING_DATA, MAP_IMAGE_URL, pobi); - smallHouse = new Map(SMALL_HOUSE_NAME, MAP_DRAWING_DATA, MAP_IMAGE_URL, pobi); + luther = new Map(LUTHER_NAME, MAP_DRAWING_DATA, MAP_SVG, pobi); + smallHouse = new Map(SMALL_HOUSE_NAME, MAP_DRAWING_DATA, MAP_SVG, pobi); } @Test @@ -72,8 +71,8 @@ void findAll() { String lutherId = createdMapApi.split("/")[4]; String smallHouseId = smallHouseCreatedMapApi.header("location").split("/")[4]; - Map lutherWithId = new Map(Long.parseLong(lutherId), luther.getName(), luther.getMapDrawing(), luther.getMapImageUrl(), luther.getMember()); - Map smallHouseWithId = new Map(Long.parseLong(smallHouseId), smallHouse.getName(), smallHouse.getMapDrawing(), smallHouse.getMapImageUrl(), smallHouse.getMember()); + Map lutherWithId = new Map(Long.parseLong(lutherId), luther.getName(), luther.getMapDrawing(), luther.getThumbnail(), luther.getMember()); + Map smallHouseWithId = new Map(Long.parseLong(smallHouseId), smallHouse.getName(), smallHouse.getMapDrawing(), smallHouse.getThumbnail(), smallHouse.getMember()); Iterator expectedMapIterator = List.of(lutherWithId, smallHouseWithId).iterator(); // when @@ -127,6 +126,30 @@ void delete() { assertThat(response.statusCode()).isEqualTo(HttpStatus.NO_CONTENT.value()); } + @Test + @DisplayName("맵에 슬랙url을 추가한다.") + void saveSlackUrl() { + // given, when + ExtractableResponse response = saveSlackUrl(createdMapApi + "/slack", new SlackCreateRequest("slack.url")); + + // then + assertThat(response.statusCode()).isEqualTo(HttpStatus.OK.value()); + } + + @Test + @DisplayName("해당 맵의 슬랙url을 조회한다.") + void findSlackUrl() { + // given, when + ExtractableResponse response = findSlackUrl(createdMapApi + "/slack"); + SlackFindResponse actualResponse = response.as(SlackFindResponse.class); + SlackFindResponse expectedResponse = SlackFindResponse.from(luther); + + // then + assertThat(response.statusCode()).isEqualTo(HttpStatus.OK.value()); + assertThat(actualResponse).usingRecursiveComparison() + .isEqualTo(expectedResponse); + } + static ExtractableResponse saveMap(final String api, MapCreateUpdateRequest mapCreateUpdateRequest) { return RestAssured .given(getRequestSpecification()).log().all() @@ -139,6 +162,29 @@ static ExtractableResponse saveMap(final String api, MapCreateUpdateRe .then().log().all().extract(); } + private ExtractableResponse saveSlackUrl(final String api, SlackCreateRequest slackCreateRequest) { + return RestAssured + .given(getRequestSpecification()).log().all() + .accept("application/json") + .header("Authorization", AuthorizationExtractor.AUTHENTICATION_TYPE + " " + accessToken) + .filter(document("map/slackPost", getRequestPreprocessor(), getResponsePreprocessor())) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .body(slackCreateRequest) + .when().post(api) + .then().log().all().extract(); + } + + private ExtractableResponse findSlackUrl(final String api) { + return RestAssured + .given(getRequestSpecification()).log().all() + .accept("application/json") + .header("Authorization", AuthorizationExtractor.AUTHENTICATION_TYPE + " " + accessToken) + .filter(document("map/slackGet", getRequestPreprocessor(), getResponsePreprocessor())) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .when().get(api) + .then().log().all().extract(); + } + static ExtractableResponse findMap(final String api) { return RestAssured .given(getRequestSpecification()).log().all() diff --git a/backend/src/test/java/com/woowacourse/zzimkkong/domain/MapTest.java b/backend/src/test/java/com/woowacourse/zzimkkong/domain/MapTest.java index 906e9190e..64c0b6c61 100644 --- a/backend/src/test/java/com/woowacourse/zzimkkong/domain/MapTest.java +++ b/backend/src/test/java/com/woowacourse/zzimkkong/domain/MapTest.java @@ -13,7 +13,7 @@ class MapTest { @DisplayName("Space가 생성되면 Map에 Space를 추가한다") void addSpace() { Member pobi = new Member(EMAIL, PW, ORGANIZATION); - Map luther = new Map(LUTHER_NAME, MAP_DRAWING_DATA, MAP_IMAGE_URL, pobi); + Map luther = new Map(LUTHER_NAME, MAP_DRAWING_DATA, MAP_SVG, pobi); assertThat(luther.getSpaces().size()).isZero(); Space.builder() @@ -27,7 +27,7 @@ void addSpace() { @DisplayName("맵의 관리자가 맞으면 true, 아니면 false") void isOwnedBy(String email, boolean expected) { Member pobi = new Member(EMAIL, PW, ORGANIZATION); - Map luther = new Map(LUTHER_NAME, MAP_DRAWING_DATA, MAP_IMAGE_URL, pobi); + Map luther = new Map(LUTHER_NAME, MAP_DRAWING_DATA, MAP_SVG, pobi); boolean result = luther.isOwnedBy(email); assertThat(result).isEqualTo(expected); @@ -39,12 +39,12 @@ void isOwnedBy(String email, boolean expected) { void addMap(boolean nullable) { Map luther; if (nullable) { - luther = new Map(LUTHER_NAME, MAP_DRAWING_DATA, MAP_IMAGE_URL, null); + luther = new Map(LUTHER_NAME, MAP_DRAWING_DATA, MAP_SVG, null); assertThat(luther.getMember()).isNull(); return; } Member pobi = new Member(EMAIL, PW, ORGANIZATION); - luther = new Map(LUTHER_NAME, MAP_DRAWING_DATA, MAP_IMAGE_URL, pobi); + luther = new Map(LUTHER_NAME, MAP_DRAWING_DATA, MAP_SVG, pobi); assertThat(pobi.getMaps()).contains(luther); } diff --git a/backend/src/test/java/com/woowacourse/zzimkkong/domain/ReservationTest.java b/backend/src/test/java/com/woowacourse/zzimkkong/domain/ReservationTest.java index 4b0ff84ef..e69198878 100644 --- a/backend/src/test/java/com/woowacourse/zzimkkong/domain/ReservationTest.java +++ b/backend/src/test/java/com/woowacourse/zzimkkong/domain/ReservationTest.java @@ -2,6 +2,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; @@ -20,17 +21,25 @@ void setUp() { reservation = Reservation.builder() .startTime(THE_DAY_AFTER_TOMORROW.atTime(8, 0)) .endTime(THE_DAY_AFTER_TOMORROW.atTime(9, 0)) + .password("1234") .build(); } @ParameterizedTest @CsvSource(value = {"08:01+08:59+true", "07:59+08:01+true", "08:59+09:01+true", "07:59+09:01+true", "08:00+09:00+true", "07:59+08:00+false", - "09:00+09:01+false", "07:00+08:00+false", "09:00+10:00+false"}, delimiter = '+') + "09:00+09:01+false", "07:00+08:00+false", "09:00+10:00+false", "07:00+07:50+false"}, delimiter = '+') @DisplayName("겹치는 시간 정보가 주어지면 true, 예약 가능한 시간대면 false") void hasConflictWith(String startTime, String endTime, Boolean result) { LocalDateTime start = THE_DAY_AFTER_TOMORROW.atTime(LocalTime.parse(startTime, DateTimeFormatter.ofPattern("HH:mm"))); LocalDateTime end = THE_DAY_AFTER_TOMORROW.atTime(LocalTime.parse(endTime, DateTimeFormatter.ofPattern("HH:mm"))); assertThat(reservation.hasConflictWith(start, end)).isEqualTo(result); } + + @Test + @DisplayName("예약 비밀번호가 잘못되었으면 true, 정확하면 false") + void isWrongPassword() { + assertThat(reservation.isWrongPassword("1321")).isTrue(); + assertThat(reservation.isWrongPassword("1234")).isFalse(); + } } diff --git a/backend/src/test/java/com/woowacourse/zzimkkong/domain/SpaceTest.java b/backend/src/test/java/com/woowacourse/zzimkkong/domain/SpaceTest.java index fd2dfdbe5..88285baf9 100644 --- a/backend/src/test/java/com/woowacourse/zzimkkong/domain/SpaceTest.java +++ b/backend/src/test/java/com/woowacourse/zzimkkong/domain/SpaceTest.java @@ -18,7 +18,7 @@ class SpaceTest { @Test void update() { Member member = new Member(EMAIL, PW, ORGANIZATION); - Map map = new Map(LUTHER_NAME, MAP_DRAWING_DATA, MAP_IMAGE_URL, member); + Map map = new Map(LUTHER_NAME, MAP_DRAWING_DATA, MAP_SVG, member); Setting setting = Setting.builder() .availableStartTime(LocalTime.of(10, 0)) @@ -154,10 +154,9 @@ void isCorrectTimeUnitFail(int minute) { assertThat(actual).isTrue(); } - @ParameterizedTest - @ValueSource(ints = {10, 120}) - @DisplayName("예약 시간의 단위가 최소최대 예약시간단위 내에 있다면 false를 반환한다.") - void isCorrectMinimumMaximumTimeUnit(int durationMinutes) { + @Test + @DisplayName("예약 시간의 단위가 최소/최대 예약시간단위 내에 있다면 false를 반환한다.") + void isCorrectMinimumMaximumTimeUnit() { Setting availableTimeSetting = Setting.builder() .availableStartTime(FE_AVAILABLE_START_TIME) .availableEndTime(FE_AVAILABLE_END_TIME) @@ -169,15 +168,16 @@ void isCorrectMinimumMaximumTimeUnit(int durationMinutes) { .build(); Space availableTimeSpace = Space.builder().setting(availableTimeSetting).build(); - boolean actual = availableTimeSpace.isIncorrectMinimumMaximumTimeUnit(durationMinutes); + boolean minActual = availableTimeSpace.isIncorrectMinimumTimeUnit(10); + boolean maxActual = availableTimeSpace.isIncorrectMinimumTimeUnit(120); - assertThat(actual).isFalse(); + assertThat(minActual).isFalse(); + assertThat(maxActual).isFalse(); } - @ParameterizedTest - @ValueSource(ints = {9, 121}) - @DisplayName("예약 시간의 단위가 최소시간단위보다 작거나 최대시간단위보다 크다면 true를 반환한다.") - void isCorrectMinimumMaximumTimeUnitFail(int durationMinutes) { + @Test + @DisplayName("예약 시간의 단위가 최소/최대 시간단위보다 작다면 true를 반환한다.") + void isCorrectMinimumMaximumTimeUnitFail() { Setting availableTimeSetting = Setting.builder() .availableStartTime(FE_AVAILABLE_START_TIME) .availableEndTime(FE_AVAILABLE_END_TIME) @@ -189,9 +189,11 @@ void isCorrectMinimumMaximumTimeUnitFail(int durationMinutes) { .build(); Space availableTimeSpace = Space.builder().setting(availableTimeSetting).build(); - boolean actual = availableTimeSpace.isIncorrectMinimumMaximumTimeUnit(durationMinutes); + boolean minActual = availableTimeSpace.isIncorrectMinimumTimeUnit(9); + boolean maxActual = availableTimeSpace.isIncorrectMaximumTimeUnit(121); - assertThat(actual).isTrue(); + assertThat(minActual).isTrue(); + assertThat(maxActual).isTrue(); } @Test diff --git a/backend/src/test/java/com/woowacourse/zzimkkong/dto/ReservationCreateUpdateRequestTest.java b/backend/src/test/java/com/woowacourse/zzimkkong/dto/ReservationCreateUpdateRequestTest.java index 284812029..aa50b9ee9 100644 --- a/backend/src/test/java/com/woowacourse/zzimkkong/dto/ReservationCreateUpdateRequestTest.java +++ b/backend/src/test/java/com/woowacourse/zzimkkong/dto/ReservationCreateUpdateRequestTest.java @@ -10,9 +10,11 @@ import org.junit.jupiter.params.provider.ValueSource; import java.time.LocalDateTime; +import java.time.ZonedDateTime; import java.time.format.DateTimeParseException; import static com.woowacourse.zzimkkong.dto.ValidatorMessage.*; +import static com.woowacourse.zzimkkong.infrastructure.datetime.TimeZoneUtils.KST; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -20,16 +22,16 @@ class ReservationCreateUpdateRequestTest extends RequestTest { @ParameterizedTest @NullSource @DisplayName("예약 생성에 빈 dateTime이 들어오면 처리한다.") - void blankDateTime(LocalDateTime dateTime) { + void blankDateTime(ZonedDateTime zonedDateTime) { ReservationCreateUpdateRequest startTime = new ReservationCreateUpdateRequest( - dateTime, - LocalDateTime.now(), + zonedDateTime, + LocalDateTime.now().atZone(KST.toZoneId()), "name", "description"); ReservationCreateUpdateRequest endTime = new ReservationCreateUpdateRequest( - LocalDateTime.now(), - dateTime, + LocalDateTime.now().atZone(KST.toZoneId()), + zonedDateTime, "name", "description"); @@ -47,8 +49,8 @@ void blankDateTime(LocalDateTime dateTime) { @DisplayName("예약 생성에 빈 이름이 들어오면 처리한다.") void blankName(String data) { ReservationCreateUpdateRequest nameRequest = new ReservationCreateUpdateRequest( - LocalDateTime.now(), - LocalDateTime.now(), + LocalDateTime.now().atZone(KST.toZoneId()), + LocalDateTime.now().atZone(KST.toZoneId()), data, "description"); @@ -62,8 +64,8 @@ void blankName(String data) { @DisplayName("예약 생성의 이름에 옳지 않은 형식의 문자열이 들어오면 처리한다.") void invalidName(String name, boolean flag) { ReservationCreateUpdateRequest reservationCreateUpdateRequest = new ReservationCreateUpdateRequest( - LocalDateTime.now(), - LocalDateTime.now(), + LocalDateTime.now().atZone(KST.toZoneId()), + LocalDateTime.now().atZone(KST.toZoneId()), name, "description"); @@ -77,8 +79,8 @@ void invalidName(String name, boolean flag) { @DisplayName("예약 생성에 빈 이름이 들어오면 처리한다.") void blankDescription(String description) { ReservationCreateUpdateRequest descriptionRequest = new ReservationCreateUpdateRequest( - LocalDateTime.now(), - LocalDateTime.now(), + LocalDateTime.now().atZone(KST.toZoneId()), + LocalDateTime.now().atZone(KST.toZoneId()), "name", description); @@ -91,8 +93,8 @@ void blankDescription(String description) { @DisplayName("예약 생성의 이름에 옳지 않은 형식의 문자열이 들어오면 처리한다.") void invalidDescription() { ReservationCreateUpdateRequest reservationCreateUpdateRequest = new ReservationCreateUpdateRequest( - LocalDateTime.now(), - LocalDateTime.now(), + LocalDateTime.now().atZone(KST.toZoneId()), + LocalDateTime.now().atZone(KST.toZoneId()), "name", "iamtenwordiamtenwordiamtenwordiamtenwordiamtenwordiamtenwordiamtenwordiamtenwordiamtenwordiamtenword1"); diff --git a/backend/src/test/java/com/woowacourse/zzimkkong/dto/ReservationCreateUpdateWithPasswordRequestTest.java b/backend/src/test/java/com/woowacourse/zzimkkong/dto/ReservationCreateUpdateWithPasswordRequestTest.java index 0c0aa2723..acb67a807 100644 --- a/backend/src/test/java/com/woowacourse/zzimkkong/dto/ReservationCreateUpdateWithPasswordRequestTest.java +++ b/backend/src/test/java/com/woowacourse/zzimkkong/dto/ReservationCreateUpdateWithPasswordRequestTest.java @@ -10,6 +10,7 @@ import static com.woowacourse.zzimkkong.dto.ValidatorMessage.EMPTY_MESSAGE; import static com.woowacourse.zzimkkong.dto.ValidatorMessage.RESERVATION_PW_MESSAGE; +import static com.woowacourse.zzimkkong.infrastructure.datetime.TimeZoneUtils.KST; import static org.assertj.core.api.Assertions.assertThat; class ReservationCreateUpdateWithPasswordRequestTest extends RequestTest { @@ -18,8 +19,8 @@ class ReservationCreateUpdateWithPasswordRequestTest extends RequestTest { @DisplayName("예약 비밀번호에 빈 문자열이 들어오면 처리한다.") void blankReservationPassword(String password) { ReservationCreateUpdateWithPasswordRequest reservationCreateUpdateWithPasswordRequest = new ReservationCreateUpdateWithPasswordRequest( - LocalDateTime.now(), - LocalDateTime.now(), + LocalDateTime.now().atZone(KST.toZoneId()), + LocalDateTime.now().atZone(KST.toZoneId()), password, "name", "description"); @@ -34,8 +35,8 @@ void blankReservationPassword(String password) { @DisplayName("예약 비밀번호에 옳지 않은 형식의 비밀번호가 들어오면 처리한다.") void invalidEmail(String password, boolean flag) { ReservationCreateUpdateWithPasswordRequest reservationCreateUpdateWithPasswordRequest = new ReservationCreateUpdateWithPasswordRequest( - LocalDateTime.now(), - LocalDateTime.now(), + LocalDateTime.now().atZone(KST.toZoneId()), + LocalDateTime.now().atZone(KST.toZoneId()), password, "name", "description"); diff --git a/backend/src/test/java/com/woowacourse/zzimkkong/dto/SpaceCreateUpdateRequestTest.java b/backend/src/test/java/com/woowacourse/zzimkkong/dto/SpaceCreateUpdateRequestTest.java index aeb6246f0..e05d9567b 100644 --- a/backend/src/test/java/com/woowacourse/zzimkkong/dto/SpaceCreateUpdateRequestTest.java +++ b/backend/src/test/java/com/woowacourse/zzimkkong/dto/SpaceCreateUpdateRequestTest.java @@ -5,7 +5,6 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.NullAndEmptySource; -import static com.woowacourse.zzimkkong.Constants.MAP_SVG; import static com.woowacourse.zzimkkong.dto.ValidatorMessage.EMPTY_MESSAGE; import static org.assertj.core.api.Assertions.assertThat; diff --git a/backend/src/test/java/com/woowacourse/zzimkkong/infrastructure/auth/JwtUtilsTest.java b/backend/src/test/java/com/woowacourse/zzimkkong/infrastructure/auth/JwtUtilsTest.java index 16aa3c997..d7633183a 100644 --- a/backend/src/test/java/com/woowacourse/zzimkkong/infrastructure/auth/JwtUtilsTest.java +++ b/backend/src/test/java/com/woowacourse/zzimkkong/infrastructure/auth/JwtUtilsTest.java @@ -4,7 +4,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.woowacourse.zzimkkong.exception.authorization.InvalidTokenException; import com.woowacourse.zzimkkong.exception.authorization.TokenExpiredException; -import com.woowacourse.zzimkkong.infrastructure.auth.JwtUtils; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; diff --git a/backend/src/test/java/com/woowacourse/zzimkkong/infrastructure/thumbnail/SharingIdGeneratorTest.java b/backend/src/test/java/com/woowacourse/zzimkkong/infrastructure/sharingid/SharingIdGeneratorTest.java similarity index 91% rename from backend/src/test/java/com/woowacourse/zzimkkong/infrastructure/thumbnail/SharingIdGeneratorTest.java rename to backend/src/test/java/com/woowacourse/zzimkkong/infrastructure/sharingid/SharingIdGeneratorTest.java index bb8bd68ee..2da355387 100644 --- a/backend/src/test/java/com/woowacourse/zzimkkong/infrastructure/thumbnail/SharingIdGeneratorTest.java +++ b/backend/src/test/java/com/woowacourse/zzimkkong/infrastructure/sharingid/SharingIdGeneratorTest.java @@ -1,10 +1,8 @@ -package com.woowacourse.zzimkkong.infrastructure.thumbnail; +package com.woowacourse.zzimkkong.infrastructure.sharingid; import com.woowacourse.zzimkkong.domain.Map; import com.woowacourse.zzimkkong.domain.Member; import com.woowacourse.zzimkkong.exception.map.InvalidAccessLinkException; -import com.woowacourse.zzimkkong.infrastructure.sharingid.SharingIdGenerator; -import com.woowacourse.zzimkkong.infrastructure.sharingid.Transcoder; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -34,7 +32,7 @@ void setUp() { luther = new Map(1L, LUTHER_NAME, MAP_DRAWING_DATA, - MAP_IMAGE_URL, + MAP_SVG, pobi); } diff --git a/backend/src/test/java/com/woowacourse/zzimkkong/infrastructure/thumbnail/BatikConverterTest.java b/backend/src/test/java/com/woowacourse/zzimkkong/infrastructure/thumbnail/BatikConverterTest.java deleted file mode 100644 index 5dfae0c88..000000000 --- a/backend/src/test/java/com/woowacourse/zzimkkong/infrastructure/thumbnail/BatikConverterTest.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.woowacourse.zzimkkong.infrastructure.thumbnail; - -import com.woowacourse.zzimkkong.exception.infrastructure.SvgToPngConvertException; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.ActiveProfiles; - -import java.io.*; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; - -@SpringBootTest -@ActiveProfiles("test") -class BatikConverterTest { - private static final String LUTHER_SVG = "프론트 강의실1프론트 강의실 2회의실 3회의실 4회의실 51234방송실전화백엔드 강의실회의실 1회의실 25트랙방"; - - @Autowired - private BatikConverter batikConverter; - - private File testFile; - - @Test - @DisplayName("svg 데이터를 png 데이터로 변환한다.") - void convertAndWriteToOutputStream() { - // given - try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(LUTHER_SVG.getBytes()); - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) { - BufferedInputStream bufferedInputStream = new BufferedInputStream(byteArrayInputStream); - BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(byteArrayOutputStream); - - // when, then - assertDoesNotThrow(() -> batikConverter.convertSvgToPng(bufferedInputStream, bufferedOutputStream)); - } catch (IOException ignore) { - } - } - - @Test - @DisplayName("옳지 않은 svg 데이터가 들어오면 오류가 발생한다.") - void convertException() { - // given - String rawSvgData = "strangeSvgData"; - - // when, then - try (final BufferedInputStream bufferedInputStream = new BufferedInputStream(new ByteArrayInputStream(rawSvgData.getBytes())); - final BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new ByteArrayOutputStream())) { - - assertThatThrownBy(() -> batikConverter.convertSvgToPng(bufferedInputStream, bufferedOutputStream)) - .isInstanceOf(SvgToPngConvertException.class); - } catch (IOException exception) { - exception.printStackTrace(); - } - } - - @AfterEach - void deleteFile() { - if (testFile != null) { - testFile.delete(); - testFile = null; - } - } -} diff --git a/backend/src/test/java/com/woowacourse/zzimkkong/infrastructure/thumbnail/S3ProxyUploaderTest.java b/backend/src/test/java/com/woowacourse/zzimkkong/infrastructure/thumbnail/S3ProxyUploaderTest.java deleted file mode 100644 index e31f11cf2..000000000 --- a/backend/src/test/java/com/woowacourse/zzimkkong/infrastructure/thumbnail/S3ProxyUploaderTest.java +++ /dev/null @@ -1,115 +0,0 @@ -package com.woowacourse.zzimkkong.infrastructure.thumbnail; - -import com.woowacourse.zzimkkong.exception.infrastructure.S3ProxyRespondedFailException; -import io.restassured.RestAssured; -import io.restassured.response.ExtractableResponse; -import io.restassured.response.Response; -import okhttp3.mockwebserver.MockResponse; -import okhttp3.mockwebserver.MockWebServer; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.http.HttpStatus; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.web.reactive.function.client.WebClient; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -@SpringBootTest -@ActiveProfiles("test") -class S3ProxyUploaderTest { - private static final String URL_REGEX = "^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]"; - private static final Pattern URL_PATTERN = Pattern.compile(URL_REGEX); - - @Autowired - private S3ProxyUploader s3ProxyUploader; - - private final String testDirectoryName = "testDirectoryName"; - private File testFile; - - @Test - @DisplayName("데이터를 업로드한 후 파일의 url을 받아온다.") - void uploadInputStreamData() throws FileNotFoundException { - // given - final String fileName = "luther.png"; - String filePath = getClass().getClassLoader().getResource(fileName).getFile(); - testFile = new File(filePath); - - final FileInputStream fileInputStream = new FileInputStream(testFile); - - // when - String uri = s3ProxyUploader.upload(testDirectoryName, fileName, fileInputStream); - Matcher matcher = URL_PATTERN.matcher(uri); - - // then - assertThat(matcher.find()).isTrue(); - } - - @Test - @DisplayName("업로드된 파일을 삭제한다.") - void delete() throws FileNotFoundException { - final String fileName = "luther.png"; - String filePath = getClass().getClassLoader().getResource(fileName).getFile(); - testFile = new File(filePath); - - final FileInputStream fileInputStream = new FileInputStream(testFile); - - String uri = s3ProxyUploader.upload(testDirectoryName, fileName, fileInputStream); - - // when - s3ProxyUploader.delete(testDirectoryName, testFile.getName()); - RestAssured.port = RestAssured.UNDEFINED_PORT; - ExtractableResponse response = RestAssured - .given().log().all() - .accept("application/json") - .when().get(uri) - .then().log().all().extract(); - - // then - assertThat(response.statusCode()).isEqualTo(HttpStatus.FORBIDDEN.value()); - } - - @Test - @DisplayName("업로드시 서버측이 201 응답을 보내지 않으면 예외가 발생한다.") - void invalidUrl() { - // given - try (MockWebServer mockGithubServer = new MockWebServer()) { - mockGithubServer.start(); - mockGithubServer.enqueue(new MockResponse() - .setResponseCode(400)); - - String hostName = mockGithubServer.getHostName(); - int port = mockGithubServer.getPort(); - - S3ProxyUploader s3ProxyUploader = new S3ProxyUploader("http://" + hostName + ":" + port, "secretKey", WebClient.create()); - - String filePath = getClass().getClassLoader().getResource("luther.png").getFile(); - testFile = new File(filePath); - - // when, then - try (final FileInputStream fileInputStream = new FileInputStream(testFile)) { - assertThatThrownBy(() -> s3ProxyUploader.upload(testDirectoryName, testFile.getName(), fileInputStream)) - .isInstanceOf(S3ProxyRespondedFailException.class); - } - } catch (IOException ignored) { - } - } - - @AfterEach - void tearDown() { - if (testFile != null) { - s3ProxyUploader.delete(testDirectoryName, testFile.getName()); - testFile = null; - } - } -} diff --git a/backend/src/test/java/com/woowacourse/zzimkkong/infrastructure/thumbnail/ThumbnailManagerImplTest.java b/backend/src/test/java/com/woowacourse/zzimkkong/infrastructure/thumbnail/ThumbnailManagerImplTest.java deleted file mode 100644 index 3b68567cf..000000000 --- a/backend/src/test/java/com/woowacourse/zzimkkong/infrastructure/thumbnail/ThumbnailManagerImplTest.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.woowacourse.zzimkkong.infrastructure.thumbnail; - -import com.woowacourse.zzimkkong.domain.Map; -import org.junit.jupiter.api.DisplayName; -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.mock.mockito.MockBean; -import org.springframework.test.context.ActiveProfiles; - -import java.io.InputStream; -import java.util.Random; - -import static com.woowacourse.zzimkkong.Constants.MAP_IMAGE_URL; -import static com.woowacourse.zzimkkong.Constants.MAP_SVG; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; - -@SpringBootTest -@ActiveProfiles("test") -class ThumbnailManagerImplTest { - @Autowired - ThumbnailManagerImpl thumbnailManager; - - @MockBean - SvgConverter svgConverter; - - @MockBean - StorageUploader storageUploader; - - @Test - @DisplayName("Map의 svg 데이터와 Map을 받고 썸네일의 url을 받아온다.") - void uploadMapThumbnailInmemory() { - // given - Map mockMap = mock(Map.class); - long mapId = new Random().nextLong(); - given(mockMap.getId()) - .willReturn(mapId); - - given(storageUploader.upload(anyString(), anyString(), any(InputStream.class))) - .willReturn(MAP_IMAGE_URL); - - // when - String mapThumbnailUrl = thumbnailManager.uploadMapThumbnail(MAP_SVG, mockMap); - - assertThat(mapThumbnailUrl).isEqualTo(MAP_IMAGE_URL); - } -} diff --git a/backend/src/test/java/com/woowacourse/zzimkkong/repository/MapRepositoryTest.java b/backend/src/test/java/com/woowacourse/zzimkkong/repository/MapRepositoryTest.java index b74a9705e..8cad84689 100644 --- a/backend/src/test/java/com/woowacourse/zzimkkong/repository/MapRepositoryTest.java +++ b/backend/src/test/java/com/woowacourse/zzimkkong/repository/MapRepositoryTest.java @@ -1,5 +1,6 @@ package com.woowacourse.zzimkkong.repository; +import com.woowacourse.zzimkkong.Constants; import com.woowacourse.zzimkkong.domain.Map; import com.woowacourse.zzimkkong.domain.Member; import com.woowacourse.zzimkkong.domain.Setting; @@ -13,7 +14,6 @@ import org.springframework.data.domain.Sort; import java.util.List; -import java.util.Optional; import static com.woowacourse.zzimkkong.Constants.*; import static org.assertj.core.api.Assertions.assertThat; @@ -26,8 +26,8 @@ class MapRepositoryTest extends RepositoryTest { @BeforeEach void setUp() { pobi = new Member(EMAIL, PW, ORGANIZATION); - luther = new Map(LUTHER_NAME, MAP_DRAWING_DATA, MAP_IMAGE_URL, pobi); - smallHouse = new Map(SMALL_HOUSE_NAME, MAP_DRAWING_DATA, MAP_IMAGE_URL, pobi); + luther = new Map(LUTHER_NAME, MAP_DRAWING_DATA, MAP_SVG, pobi); + smallHouse = new Map(SMALL_HOUSE_NAME, MAP_DRAWING_DATA, MAP_SVG, pobi); members.save(pobi); } diff --git a/backend/src/test/java/com/woowacourse/zzimkkong/repository/ReservationRepositoryImplTest.java b/backend/src/test/java/com/woowacourse/zzimkkong/repository/ReservationRepositoryImplTest.java index 8d2ad8a53..20a22dbcf 100644 --- a/backend/src/test/java/com/woowacourse/zzimkkong/repository/ReservationRepositoryImplTest.java +++ b/backend/src/test/java/com/woowacourse/zzimkkong/repository/ReservationRepositoryImplTest.java @@ -10,6 +10,7 @@ import java.time.LocalDateTime; import static com.woowacourse.zzimkkong.Constants.*; +import static com.woowacourse.zzimkkong.infrastructure.datetime.TimeZoneUtils.UTC; class ReservationRepositoryImplTest extends RepositoryTest { @ParameterizedTest @@ -20,7 +21,7 @@ void existsReservationsByMember(boolean isReservationExists) { Member sakjung = new Member(NEW_EMAIL, PW, ORGANIZATION); Member savedMember = members.save(sakjung); - Map luther = new Map(LUTHER_NAME, MAP_DRAWING_DATA, MAP_IMAGE_URL, savedMember); + Map luther = new Map(LUTHER_NAME, MAP_DRAWING_DATA, MAP_SVG, savedMember); maps.save(luther); Setting beSetting = Setting.builder() @@ -58,9 +59,9 @@ void existsReservationsByMember(boolean isReservationExists) { if (isReservationExists) { Reservation beAmZeroOne = Reservation.builder() - .date(BE_AM_TEN_ELEVEN_START_TIME.toLocalDate()) - .startTime(BE_AM_TEN_ELEVEN_START_TIME) - .endTime(BE_AM_TEN_ELEVEN_END_TIME) + .date(BE_AM_TEN_ELEVEN_START_TIME_KST.toLocalDate()) + .startTime(BE_AM_TEN_ELEVEN_START_TIME_KST.withZoneSameInstant(UTC.toZoneId()).toLocalDateTime()) + .endTime(BE_AM_TEN_ELEVEN_END_TIME_KST.withZoneSameInstant(UTC.toZoneId()).toLocalDateTime()) .description(BE_AM_TEN_ELEVEN_DESCRIPTION) .userName(BE_AM_TEN_ELEVEN_USERNAME) .password(BE_AM_TEN_ELEVEN_PW) diff --git a/backend/src/test/java/com/woowacourse/zzimkkong/repository/ReservationRepositoryTest.java b/backend/src/test/java/com/woowacourse/zzimkkong/repository/ReservationRepositoryTest.java index da1438d76..bf87338d3 100644 --- a/backend/src/test/java/com/woowacourse/zzimkkong/repository/ReservationRepositoryTest.java +++ b/backend/src/test/java/com/woowacourse/zzimkkong/repository/ReservationRepositoryTest.java @@ -15,6 +15,8 @@ import java.util.List; import static com.woowacourse.zzimkkong.Constants.*; +import static com.woowacourse.zzimkkong.infrastructure.datetime.TimeZoneUtils.ONE_DAY_OFFSET; +import static com.woowacourse.zzimkkong.infrastructure.datetime.TimeZoneUtils.UTC; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; @@ -32,7 +34,7 @@ class ReservationRepositoryTest extends RepositoryTest { @BeforeEach void setUp() { pobi = new Member(EMAIL, PW, ORGANIZATION); - Map luther = new Map(LUTHER_NAME, MAP_DRAWING_DATA, MAP_IMAGE_URL, pobi); + Map luther = new Map(LUTHER_NAME, MAP_DRAWING_DATA, MAP_SVG, pobi); Setting beSetting = Setting.builder() .availableStartTime(BE_AVAILABLE_START_TIME) @@ -78,9 +80,9 @@ void setUp() { spaces.save(fe); beAmZeroOne = Reservation.builder() - .date(BE_AM_TEN_ELEVEN_START_TIME.toLocalDate()) - .startTime(BE_AM_TEN_ELEVEN_START_TIME) - .endTime(BE_AM_TEN_ELEVEN_END_TIME) + .date(BE_AM_TEN_ELEVEN_START_TIME_KST.toLocalDate()) + .startTime(BE_AM_TEN_ELEVEN_START_TIME_KST.withZoneSameInstant(UTC.toZoneId()).toLocalDateTime()) + .endTime(BE_AM_TEN_ELEVEN_END_TIME_KST.withZoneSameInstant(UTC.toZoneId()).toLocalDateTime()) .description(BE_AM_TEN_ELEVEN_DESCRIPTION) .userName(BE_AM_TEN_ELEVEN_USERNAME) .password(BE_AM_TEN_ELEVEN_PW) @@ -88,9 +90,9 @@ void setUp() { .build(); bePmOneTwo = Reservation.builder() - .date(BE_PM_ONE_TWO_START_TIME.toLocalDate()) - .startTime(BE_PM_ONE_TWO_START_TIME) - .endTime(BE_PM_ONE_TWO_END_TIME) + .date(BE_PM_ONE_TWO_START_TIME_KST.toLocalDate()) + .startTime(BE_PM_ONE_TWO_START_TIME_KST.withZoneSameInstant(UTC.toZoneId()).toLocalDateTime()) + .endTime(BE_PM_ONE_TWO_END_TIME_KST.withZoneSameInstant(UTC.toZoneId()).toLocalDateTime()) .description(BE_PM_ONE_TWO_DESCRIPTION) .userName(BE_PM_ONE_TWO_USERNAME) .password(BE_PM_ONE_TWO_PW) @@ -98,9 +100,9 @@ void setUp() { .build(); beNextDayAmSixTwelve = Reservation.builder() - .date(BE_NEXT_DAY_PM_FOUR_TO_SIX_START_TIME.toLocalDate()) - .startTime(BE_NEXT_DAY_PM_FOUR_TO_SIX_START_TIME) - .endTime(BE_NEXT_DAY_PM_FOUR_TO_SIX_END_TIME) + .date(BE_NEXT_DAY_PM_FOUR_TO_SIX_START_TIME_KST.toLocalDate()) + .startTime(BE_NEXT_DAY_PM_FOUR_TO_SIX_START_TIME_KST.withZoneSameInstant(UTC.toZoneId()).toLocalDateTime()) + .endTime(BE_NEXT_DAY_PM_FOUR_TO_SIX_END_TIME_KST.withZoneSameInstant(UTC.toZoneId()).toLocalDateTime()) .description(BE_NEXT_DAY_PM_FOUR_TO_SIX_DESCRIPTION) .userName(BE_NEXT_DAY_PM_FOUR_TO_SIX_USERNAME) .password(BE_NEXT_DAY_PM_FOUR_TO_SIX_PW) @@ -108,9 +110,9 @@ void setUp() { .build(); fe1ZeroOne = Reservation.builder() - .date(FE1_AM_TEN_ELEVEN_START_TIME.toLocalDate()) - .startTime(FE1_AM_TEN_ELEVEN_START_TIME) - .endTime(FE1_AM_TEN_ELEVEN_END_TIME) + .date(FE1_AM_TEN_ELEVEN_START_TIME_KST.toLocalDate()) + .startTime(FE1_AM_TEN_ELEVEN_START_TIME_KST.withZoneSameInstant(UTC.toZoneId()).toLocalDateTime()) + .endTime(FE1_AM_TEN_ELEVEN_END_TIME_KST.withZoneSameInstant(UTC.toZoneId()).toLocalDateTime()) .description(FE1_AM_TEN_ELEVEN_DESCRIPTION) .userName(FE1_AM_TEN_ELEVEN_USERNAME) .password(FE1_AM_TEN_ELEVEN_PW) @@ -146,8 +148,8 @@ void save() { } @Test - @DisplayName("map id, space id, 특정 시간이 주어질 때, 해당 spaceId와 해당 시간에 속하는 예약들만 찾아온다") - void findAllBySpaceIdInAndDate() { + @DisplayName("map id, space id, 특정 날짜가 주어질 때, 해당 spaceId와 해당 날짜 전,훗날에 속하는 예약들을 찾아온다") + void findAllBySpaceIdInAndDateGreaterThanEqualAndDateLessThanEqual() { // given, when List foundReservations = getReservations( List.of(be.getId(), fe.getId()), @@ -155,43 +157,24 @@ void findAllBySpaceIdInAndDate() { // then assertThat(foundReservations).usingRecursiveComparison() - .isEqualTo(List.of(beAmZeroOne, bePmOneTwo, fe1ZeroOne)); + .ignoringCollectionOrder() + .isEqualTo(List.of(beAmZeroOne, beNextDayAmSixTwelve, bePmOneTwo, fe1ZeroOne)); } @Test @DisplayName("특정 날짜에 부합하는 예약이 없으면 빈 리스트를 반환한다") - void findAllBySpaceIdInAndDate_noMatchingTime() { + void findAllBySpaceIdInAndDateGreaterThanEqualAndDateLessThanEqual_noMatchingTimeAndSpace() { // given, when - List foundReservations = getReservations( + List foundReservationsBe = getReservations( List.of(be.getId()), - THE_DAY_AFTER_TOMORROW.plusDays(2)); - - // then - assertThat(foundReservations).isEmpty(); - } - - @Test - @DisplayName("특정 공간에 부합하는 예약이 없으면 빈 리스트를 반환한다") - void findAllBySpaceIdInAndDate_noMatchingReservation() { - // given, when - List foundReservations = getReservations( + THE_DAY_AFTER_TOMORROW.plusDays(3)); + List foundReservationsFe = getReservations( List.of(fe.getId()), - THE_DAY_AFTER_TOMORROW.plusDays(1)); - - // then - assertThat(foundReservations).isEmpty(); - } - - @Test - @DisplayName("map id와 특정 날짜가 주어질 때, 해당 날짜에 속하는 해당 map의 모든 space들의 예약들을 찾아온다") - void findAllBySpaceIdInAndDate_allSpaces() { - // given, when - List foundReservations = getReservations( - List.of(be.getId(), fe.getId()), - THE_DAY_AFTER_TOMORROW); + THE_DAY_AFTER_TOMORROW.minusDays(3)); // then - assertThat(foundReservations).containsExactlyInAnyOrderElementsOf(List.of(beAmZeroOne, bePmOneTwo, fe1ZeroOne)); + assertThat(foundReservationsBe).isEmpty(); + assertThat(foundReservationsFe).isEmpty(); } @Test @@ -216,7 +199,9 @@ void existsBySpace() { @DisplayName("특정 시간 이후의 예약이 존재하는지 확인한다.") void existsBySpaceIdAndDateGreaterThanEqual(int minusMinute, boolean expected) { //given, when - Boolean actual = reservations.existsBySpaceIdAndEndTimeAfter(be.getId(), BE_NEXT_DAY_PM_FOUR_TO_SIX_END_TIME.minusMinutes(minusMinute)); + Boolean actual = reservations.existsBySpaceIdAndEndTimeAfter( + be.getId(), + BE_NEXT_DAY_PM_FOUR_TO_SIX_END_TIME_KST.withZoneSameInstant(UTC.toZoneId()).toLocalDateTime().minusMinutes(minusMinute)); //then assertThat(actual).isEqualTo(expected); @@ -237,6 +222,9 @@ void findAllByPaging() { } private List getReservations(List spaceIds, LocalDate date) { - return reservations.findAllBySpaceIdInAndDate(spaceIds, date); + return reservations.findAllBySpaceIdInAndDateGreaterThanEqualAndDateLessThanEqual( + spaceIds, + date.minusDays(ONE_DAY_OFFSET), + date.plusDays(ONE_DAY_OFFSET)); } } diff --git a/backend/src/test/java/com/woowacourse/zzimkkong/repository/SpaceRepositoryTest.java b/backend/src/test/java/com/woowacourse/zzimkkong/repository/SpaceRepositoryTest.java index f4b5d6475..6659d135d 100644 --- a/backend/src/test/java/com/woowacourse/zzimkkong/repository/SpaceRepositoryTest.java +++ b/backend/src/test/java/com/woowacourse/zzimkkong/repository/SpaceRepositoryTest.java @@ -1,5 +1,6 @@ package com.woowacourse.zzimkkong.repository; +import com.woowacourse.zzimkkong.Constants; import com.woowacourse.zzimkkong.domain.Map; import com.woowacourse.zzimkkong.domain.Member; import com.woowacourse.zzimkkong.domain.Setting; @@ -24,7 +25,7 @@ class SpaceRepositoryTest extends RepositoryTest { @BeforeEach void setUp() { Member pobi = new Member(EMAIL, PW, ORGANIZATION); - luther = new Map(LUTHER_NAME, MAP_DRAWING_DATA, MAP_IMAGE_URL, pobi); + luther = new Map(LUTHER_NAME, MAP_DRAWING_DATA, MAP_SVG, pobi); Setting beSetting = Setting.builder() .availableStartTime(BE_AVAILABLE_START_TIME) diff --git a/backend/src/test/java/com/woowacourse/zzimkkong/service/AdminServiceTest.java b/backend/src/test/java/com/woowacourse/zzimkkong/service/AdminServiceTest.java index bf28f7918..9ed8cbe78 100644 --- a/backend/src/test/java/com/woowacourse/zzimkkong/service/AdminServiceTest.java +++ b/backend/src/test/java/com/woowacourse/zzimkkong/service/AdminServiceTest.java @@ -22,6 +22,7 @@ import java.util.List; import static com.woowacourse.zzimkkong.Constants.*; +import static com.woowacourse.zzimkkong.infrastructure.datetime.TimeZoneUtils.UTC; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; @@ -84,7 +85,7 @@ void findMembers() { @DisplayName("모든 맵을 페이지네이션을 이용해 조회한다.") void findMaps() { //given - Map luther = new Map(LUTHER_NAME, MAP_DRAWING_DATA, MAP_IMAGE_URL, pobi); + Map luther = new Map(LUTHER_NAME, MAP_DRAWING_DATA, MAP_SVG, pobi); PageRequest pageRequest = PageRequest.of(0, 20, Sort.unsorted()); given(maps.findAllByFetch(any(Pageable.class))) .willReturn(new PageImpl<>(List.of(luther), pageRequest, 1)); @@ -106,7 +107,7 @@ void findMaps() { @DisplayName("모든 공간을 페이지네이션을 이용해 조회한다.") void findSpaces() { //given - Map luther = new Map(LUTHER_NAME, MAP_DRAWING_DATA, MAP_IMAGE_URL, pobi); + Map luther = new Map(LUTHER_NAME, MAP_DRAWING_DATA, MAP_SVG, pobi); Setting beSetting = Setting.builder() .availableStartTime(BE_AVAILABLE_START_TIME) .availableEndTime(BE_AVAILABLE_END_TIME) @@ -147,7 +148,7 @@ void findSpaces() { @DisplayName("모든 예약을 페이지네이션을 이용해 조회한다.") void findReservations() { //given - Map luther = new Map(1L, LUTHER_NAME, MAP_DRAWING_DATA, MAP_IMAGE_URL, pobi); + Map luther = new Map(1L, LUTHER_NAME, MAP_DRAWING_DATA, MAP_SVG, pobi); Setting beSetting = Setting.builder() .availableStartTime(BE_AVAILABLE_START_TIME) .availableEndTime(BE_AVAILABLE_END_TIME) @@ -168,9 +169,9 @@ void findReservations() { .build(); Reservation beAmZeroOne = Reservation.builder() - .date(BE_AM_TEN_ELEVEN_START_TIME.toLocalDate()) - .startTime(BE_AM_TEN_ELEVEN_START_TIME) - .endTime(BE_AM_TEN_ELEVEN_END_TIME) + .date(BE_AM_TEN_ELEVEN_START_TIME_KST.toLocalDate()) + .startTime(BE_AM_TEN_ELEVEN_START_TIME_KST.withZoneSameInstant(UTC.toZoneId()).toLocalDateTime()) + .endTime(BE_AM_TEN_ELEVEN_END_TIME_KST.withZoneSameInstant(UTC.toZoneId()).toLocalDateTime()) .description(BE_AM_TEN_ELEVEN_DESCRIPTION) .userName(BE_AM_TEN_ELEVEN_USERNAME) .password(BE_AM_TEN_ELEVEN_PW) diff --git a/backend/src/test/java/com/woowacourse/zzimkkong/service/GuestReservationServiceTest.java b/backend/src/test/java/com/woowacourse/zzimkkong/service/GuestReservationServiceTest.java index 503312444..931f61bfc 100644 --- a/backend/src/test/java/com/woowacourse/zzimkkong/service/GuestReservationServiceTest.java +++ b/backend/src/test/java/com/woowacourse/zzimkkong/service/GuestReservationServiceTest.java @@ -5,6 +5,7 @@ import com.woowacourse.zzimkkong.exception.map.NoSuchMapException; import com.woowacourse.zzimkkong.exception.reservation.*; import com.woowacourse.zzimkkong.exception.space.NoSuchSpaceException; +import com.woowacourse.zzimkkong.infrastructure.datetime.TimeZoneUtils; import com.woowacourse.zzimkkong.service.strategy.GuestReservationStrategy; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -23,6 +24,8 @@ import java.util.Optional; import static com.woowacourse.zzimkkong.Constants.*; +import static com.woowacourse.zzimkkong.infrastructure.datetime.TimeZoneUtils.KST; +import static com.woowacourse.zzimkkong.infrastructure.datetime.TimeZoneUtils.UTC; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; @@ -38,8 +41,8 @@ class GuestReservationServiceTest extends ServiceTest { private final GuestReservationStrategy guestReservationStrategy = new GuestReservationStrategy(); private ReservationCreateUpdateWithPasswordRequest reservationCreateUpdateWithPasswordRequest = new ReservationCreateUpdateWithPasswordRequest( - THE_DAY_AFTER_TOMORROW.atTime(13, 0), - THE_DAY_AFTER_TOMORROW.atTime(14, 0), + THE_DAY_AFTER_TOMORROW.atTime(11, 0).atZone(KST.toZoneId()), + THE_DAY_AFTER_TOMORROW.atTime(12, 0).atZone(KST.toZoneId()), RESERVATION_PW, USER_NAME, DESCRIPTION); @@ -60,7 +63,7 @@ class GuestReservationServiceTest extends ServiceTest { @BeforeEach void setUp() { Member pobi = new Member(EMAIL, PW, ORGANIZATION); - luther = new Map(1L, LUTHER_NAME, MAP_DRAWING_DATA, MAP_IMAGE_URL, pobi); + luther = new Map(1L, LUTHER_NAME, MAP_DRAWING_DATA, MAP_SVG, pobi); Setting beSetting = Setting.builder() .availableStartTime(BE_AVAILABLE_START_TIME) @@ -103,8 +106,8 @@ void setUp() { beAmZeroOne = Reservation.builder() .id(1L) - .startTime(BE_AM_TEN_ELEVEN_START_TIME) - .endTime(BE_AM_TEN_ELEVEN_END_TIME) + .startTime(BE_AM_TEN_ELEVEN_START_TIME_KST.withZoneSameInstant(UTC.toZoneId()).toLocalDateTime()) + .endTime(BE_AM_TEN_ELEVEN_END_TIME_KST.withZoneSameInstant(UTC.toZoneId()).toLocalDateTime()) .description(BE_AM_TEN_ELEVEN_DESCRIPTION) .userName(BE_AM_TEN_ELEVEN_USERNAME) .password(BE_AM_TEN_ELEVEN_PW) @@ -113,8 +116,8 @@ void setUp() { bePmOneTwo = Reservation.builder() .id(2L) - .startTime(BE_PM_ONE_TWO_START_TIME) - .endTime(BE_PM_ONE_TWO_END_TIME) + .startTime(BE_PM_ONE_TWO_START_TIME_KST.withZoneSameInstant(UTC.toZoneId()).toLocalDateTime()) + .endTime(BE_PM_ONE_TWO_END_TIME_KST.withZoneSameInstant(UTC.toZoneId()).toLocalDateTime()) .description(BE_PM_ONE_TWO_DESCRIPTION) .userName(BE_PM_ONE_TWO_USERNAME) .password(BE_PM_ONE_TWO_PW) @@ -122,8 +125,8 @@ void setUp() { .build(); reservation = makeReservation( - reservationCreateUpdateWithPasswordRequest.getStartDateTime(), - reservationCreateUpdateWithPasswordRequest.getEndDateTime(), + reservationCreateUpdateWithPasswordRequest.localStartDateTime(), + reservationCreateUpdateWithPasswordRequest.localEndDateTime(), be); lutherId = luther.getId(); @@ -204,8 +207,8 @@ void saveStartTimeBeforeNow() { // when reservationCreateUpdateWithPasswordRequest = new ReservationCreateUpdateWithPasswordRequest( - LocalDateTime.now().minusHours(3), - LocalDateTime.now().plusHours(3), + LocalDateTime.now().minusHours(3).atZone(KST.toZoneId()), + LocalDateTime.now().plusHours(3).atZone(KST.toZoneId()), RESERVATION_PW, USER_NAME, DESCRIPTION); @@ -230,8 +233,8 @@ void saveEndTimeBeforeNow() { // when reservationCreateUpdateWithPasswordRequest = new ReservationCreateUpdateWithPasswordRequest( - THE_DAY_AFTER_TOMORROW.atTime(14, 0), - THE_DAY_AFTER_TOMORROW.atTime(13, 0), + THE_DAY_AFTER_TOMORROW.atTime(14, 0).atZone(KST.toZoneId()), + THE_DAY_AFTER_TOMORROW.atTime(13, 0).atZone(KST.toZoneId()), RESERVATION_PW, USER_NAME, DESCRIPTION); @@ -256,8 +259,8 @@ void saveStartTimeEqualsEndTime() { //when reservationCreateUpdateWithPasswordRequest = new ReservationCreateUpdateWithPasswordRequest( - THE_DAY_AFTER_TOMORROW.atTime(10, 0), - THE_DAY_AFTER_TOMORROW.atTime(10, 0), + THE_DAY_AFTER_TOMORROW.atTime(10, 0).atZone(KST.toZoneId()), + THE_DAY_AFTER_TOMORROW.atTime(10, 0).atZone(KST.toZoneId()), RESERVATION_PW, USER_NAME, DESCRIPTION); @@ -282,8 +285,8 @@ void saveStartTimeDateNotEqualsEndTimeDate() { //when reservationCreateUpdateWithPasswordRequest = new ReservationCreateUpdateWithPasswordRequest( - THE_DAY_AFTER_TOMORROW.atTime(10, 0), - THE_DAY_AFTER_TOMORROW.atTime(10, 0).plusDays(1), + THE_DAY_AFTER_TOMORROW.atTime(10, 0).atZone(KST.toZoneId()), + THE_DAY_AFTER_TOMORROW.atTime(10, 0).plusDays(1).atZone(KST.toZoneId()), RESERVATION_PW, USER_NAME, DESCRIPTION); @@ -309,8 +312,8 @@ void saveInvalidTimeSetting(int startTime, int endTime) { //when reservationCreateUpdateWithPasswordRequest = new ReservationCreateUpdateWithPasswordRequest( - THE_DAY_AFTER_TOMORROW.atTime(startTime, 0), - THE_DAY_AFTER_TOMORROW.atTime(endTime, 30), + THE_DAY_AFTER_TOMORROW.atTime(startTime, 0).atZone(KST.toZoneId()), + THE_DAY_AFTER_TOMORROW.atTime(endTime, 30).atZone(KST.toZoneId()), RESERVATION_PW, USER_NAME, DESCRIPTION); @@ -334,12 +337,13 @@ void saveAvailabilityException(int startMinute, int endMinute) { //given, when given(maps.findByIdFetch(anyLong())) .willReturn(Optional.of(luther)); - given(reservations.findAllBySpaceIdInAndDate( + given(reservations.findAllBySpaceIdInAndDateGreaterThanEqualAndDateLessThanEqual( anyList(), + any(LocalDate.class), any(LocalDate.class))) .willReturn(List.of(makeReservation( - reservationCreateUpdateWithPasswordRequest.getStartDateTime().minusMinutes(startMinute), - reservationCreateUpdateWithPasswordRequest.getEndDateTime().plusMinutes(endMinute), + reservationCreateUpdateWithPasswordRequest.localStartDateTime().minusMinutes(startMinute), + reservationCreateUpdateWithPasswordRequest.localEndDateTime().plusMinutes(endMinute), be))); ReservationCreateDto reservationCreateDto = ReservationCreateDto.of( @@ -351,7 +355,7 @@ void saveAvailabilityException(int startMinute, int endMinute) { assertThatThrownBy(() -> reservationService.saveReservation( reservationCreateDto, guestReservationStrategy)) - .isInstanceOf(ImpossibleReservationTimeException.class); + .isInstanceOf(ReservationAlreadyExistsException.class); } @Test @@ -441,18 +445,20 @@ void saveSameThresholdTime(int duration) { //given, when given(maps.findByIdFetch(anyLong())) .willReturn(Optional.of(luther)); - given(reservations.findAllBySpaceIdInAndDate( + given(reservations.findAllBySpaceIdInAndDateGreaterThanEqualAndDateLessThanEqual( anyList(), + any(LocalDate.class), any(LocalDate.class))) .willReturn(List.of( makeReservation( - reservationCreateUpdateWithPasswordRequest.getStartDateTime().minusMinutes(duration), - reservationCreateUpdateWithPasswordRequest.getEndDateTime().minusMinutes(duration), + reservationCreateUpdateWithPasswordRequest.localStartDateTime().minusMinutes(duration), + reservationCreateUpdateWithPasswordRequest.localEndDateTime().minusMinutes(duration), be), makeReservation( - reservationCreateUpdateWithPasswordRequest.getStartDateTime().plusMinutes(duration), - reservationCreateUpdateWithPasswordRequest.getEndDateTime().plusMinutes(duration), + reservationCreateUpdateWithPasswordRequest.localStartDateTime().plusMinutes(duration), + reservationCreateUpdateWithPasswordRequest.localEndDateTime().plusMinutes(duration), be))); + given(reservations.save(any(Reservation.class))) .willReturn(reservation); @@ -481,8 +487,8 @@ void saveReservationTimeUnitException(int additionalStartMinute, int additionalE //when ReservationCreateUpdateWithPasswordRequest reservationCreateUpdateWithPasswordRequest = new ReservationCreateUpdateWithPasswordRequest( - theDayAfterTomorrowTen.plusMinutes(additionalStartMinute), - theDayAfterTomorrowTen.plusMinutes(additionalEndMinute), + theDayAfterTomorrowTen.plusMinutes(additionalStartMinute).atZone(KST.toZoneId()), + theDayAfterTomorrowTen.plusMinutes(additionalEndMinute).atZone(KST.toZoneId()), RESERVATION_PW, USER_NAME, DESCRIPTION); @@ -509,10 +515,9 @@ void saveReservationTimeUnitException(int additionalStartMinute, int additionalE .isInstanceOf(InvalidTimeUnitException.class); } - @ParameterizedTest - @ValueSource(ints = {50, 130}) - @DisplayName("예약 생성/수정 요청 시, space setting의 minimum, maximum 시간이 옳지 않으면 예외가 발생한다.") - void saveReservationMinimumMaximumTimeUnitException(int duration) { + @Test + @DisplayName("예약 생성/수정 요청 시, space setting의 minimum 시간이 옳지 않으면 예외가 발생한다.") + void saveReservationMinimumDurationTimeException() { //given given(maps.findByIdFetch(anyLong())) .willReturn(Optional.of(luther)); @@ -521,8 +526,8 @@ void saveReservationMinimumMaximumTimeUnitException(int duration) { //when ReservationCreateUpdateWithPasswordRequest reservationCreateUpdateWithPasswordRequest = new ReservationCreateUpdateWithPasswordRequest( - THE_DAY_AFTER_TOMORROW.atTime(10, 0), - THE_DAY_AFTER_TOMORROW.atTime(10, 0).plusMinutes(duration), + THE_DAY_AFTER_TOMORROW.atTime(10, 0).atZone(KST.toZoneId()), + THE_DAY_AFTER_TOMORROW.atTime(10, 0).plusMinutes(50).atZone(KST.toZoneId()), RESERVATION_PW, USER_NAME, DESCRIPTION); @@ -542,12 +547,42 @@ void saveReservationMinimumMaximumTimeUnitException(int duration) { assertThatThrownBy(() -> reservationService.saveReservation( reservationCreateDto, guestReservationStrategy)) - .isInstanceOf(InvalidDurationTimeException.class); + .isInstanceOf(InvalidMinimumDurationTimeException.class); + } - assertThatThrownBy(() -> reservationService.updateReservation( - reservationUpdateDto, + @Test + @DisplayName("예약 생성/수정 요청 시, space setting의 maximum 시간이 옳지 않으면 예외가 발생한다.") + void saveReservationMaximumTimeUnitException() { + //given + given(maps.findByIdFetch(anyLong())) + .willReturn(Optional.of(luther)); + given(reservations.findById(anyLong())) + .willReturn(Optional.of(reservation)); + + //when + ReservationCreateUpdateWithPasswordRequest reservationCreateUpdateWithPasswordRequest = new ReservationCreateUpdateWithPasswordRequest( + THE_DAY_AFTER_TOMORROW.atTime(10, 0).atZone(KST.toZoneId()), + THE_DAY_AFTER_TOMORROW.atTime(10, 0).plusMinutes(130).atZone(KST.toZoneId()), + RESERVATION_PW, + USER_NAME, + DESCRIPTION); + Long reservationId = reservation.getId(); + + ReservationCreateDto reservationCreateDto = ReservationCreateDto.of( + lutherId, + beId, + reservationCreateUpdateWithPasswordRequest); + ReservationUpdateDto reservationUpdateDto = ReservationUpdateDto.of( + lutherId, + beId, + reservationId, + reservationCreateUpdateWithPasswordRequest); + + //then + assertThatThrownBy(() -> reservationService.saveReservation( + reservationCreateDto, guestReservationStrategy)) - .isInstanceOf(InvalidDurationTimeException.class); + .isInstanceOf(InvalidMaximumDurationTimeException.class); } @Test @@ -561,8 +596,8 @@ void pastReservationSaveUpdateException() { //when ReservationCreateUpdateWithPasswordRequest reservationCreateUpdateWithPasswordRequest = new ReservationCreateUpdateWithPasswordRequest( - THE_DAY_AFTER_TOMORROW.atTime(10, 0).minusDays(5), - THE_DAY_AFTER_TOMORROW.atTime(10, 0).minusDays(5).plusMinutes(60), + THE_DAY_AFTER_TOMORROW.atTime(10, 0).minusDays(5).atZone(KST.toZoneId()), + THE_DAY_AFTER_TOMORROW.atTime(10, 0).minusDays(5).plusMinutes(60).atZone(KST.toZoneId()), RESERVATION_PW, USER_NAME, DESCRIPTION); @@ -597,18 +632,19 @@ void findReservations() { int duration = 30; List foundReservations = Arrays.asList( makeReservation( - reservationCreateUpdateWithPasswordRequest.getStartDateTime().minusMinutes(duration), - reservationCreateUpdateWithPasswordRequest.getEndDateTime().minusMinutes(duration), + reservationCreateUpdateWithPasswordRequest.localStartDateTime().minusMinutes(duration), + reservationCreateUpdateWithPasswordRequest.localEndDateTime().minusMinutes(duration), be), makeReservation( - reservationCreateUpdateWithPasswordRequest.getStartDateTime().plusMinutes(duration), - reservationCreateUpdateWithPasswordRequest.getEndDateTime().plusMinutes(duration), + reservationCreateUpdateWithPasswordRequest.localStartDateTime().plusMinutes(duration), + reservationCreateUpdateWithPasswordRequest.localEndDateTime().plusMinutes(duration), be)); given(maps.findByIdFetch(anyLong())) .willReturn(Optional.of(luther)); - given(reservations.findAllBySpaceIdInAndDate( + given(reservations.findAllBySpaceIdInAndDateGreaterThanEqualAndDateLessThanEqual( anyList(), + any(LocalDate.class), any(LocalDate.class))) .willReturn(foundReservations); @@ -674,8 +710,9 @@ void findEmptyReservations() { .willReturn(Optional.of(luther)); given(maps.existsById(anyLong())) .willReturn(true); - given(reservations.findAllBySpaceIdInAndDate( + given(reservations.findAllBySpaceIdInAndDateGreaterThanEqualAndDateLessThanEqual( anyList(), + any(LocalDate.class), any(LocalDate.class))) .willReturn(Collections.emptyList()); @@ -710,27 +747,28 @@ void findAllReservation() { int duration = 30; List foundReservations = List.of( makeReservation( - reservationCreateUpdateWithPasswordRequest.getStartDateTime().minusMinutes(duration), - reservationCreateUpdateWithPasswordRequest.getEndDateTime().minusMinutes(duration), + reservationCreateUpdateWithPasswordRequest.localStartDateTime().minusMinutes(duration), + reservationCreateUpdateWithPasswordRequest.localEndDateTime().minusMinutes(duration), be), makeReservation( - reservationCreateUpdateWithPasswordRequest.getStartDateTime().plusMinutes(duration), - reservationCreateUpdateWithPasswordRequest.getEndDateTime().plusMinutes(duration), + reservationCreateUpdateWithPasswordRequest.localStartDateTime().plusMinutes(duration), + reservationCreateUpdateWithPasswordRequest.localEndDateTime().plusMinutes(duration), be), makeReservation( - reservationCreateUpdateWithPasswordRequest.getStartDateTime().minusMinutes(duration), - reservationCreateUpdateWithPasswordRequest.getEndDateTime().minusMinutes(duration), + reservationCreateUpdateWithPasswordRequest.localStartDateTime().minusMinutes(duration), + reservationCreateUpdateWithPasswordRequest.localEndDateTime().minusMinutes(duration), fe), makeReservation( - reservationCreateUpdateWithPasswordRequest.getStartDateTime().plusMinutes(duration), - reservationCreateUpdateWithPasswordRequest.getEndDateTime().plusMinutes(duration), + reservationCreateUpdateWithPasswordRequest.localStartDateTime().plusMinutes(duration), + reservationCreateUpdateWithPasswordRequest.localEndDateTime().plusMinutes(duration), fe)); List findSpaces = List.of(be, fe); given(maps.findByIdFetch(anyLong())) .willReturn(Optional.of(luther)); - given(reservations.findAllBySpaceIdInAndDate( + given(reservations.findAllBySpaceIdInAndDateGreaterThanEqualAndDateLessThanEqual( anyList(), + any(LocalDate.class), any(LocalDate.class))) .willReturn(foundReservations); @@ -830,8 +868,8 @@ void update() { .willReturn(Optional.of(reservation)); Long reservationId = reservation.getId(); ReservationCreateUpdateWithPasswordRequest reservationCreateUpdateWithPasswordRequest = new ReservationCreateUpdateWithPasswordRequest( - THE_DAY_AFTER_TOMORROW.atTime(10, 0), - THE_DAY_AFTER_TOMORROW.atTime(11, 0), + THE_DAY_AFTER_TOMORROW.atTime(10, 0).atZone(KST.toZoneId()), + THE_DAY_AFTER_TOMORROW.atTime(11, 0).atZone(KST.toZoneId()), reservation.getPassword(), CHANGED_NAME, CHANGED_DESCRIPTION); @@ -861,8 +899,8 @@ void updateInvalidEndTimeException(int endTime) { //when ReservationCreateUpdateWithPasswordRequest reservationCreateUpdateWithPasswordRequest = new ReservationCreateUpdateWithPasswordRequest( - THE_DAY_AFTER_TOMORROW.atTime(12, 0), - THE_DAY_AFTER_TOMORROW.atTime(12, 0).minusHours(endTime), + THE_DAY_AFTER_TOMORROW.atTime(12, 0).atZone(KST.toZoneId()), + THE_DAY_AFTER_TOMORROW.atTime(12, 0).minusHours(endTime).atZone(KST.toZoneId()), reservation.getPassword(), CHANGED_NAME, CHANGED_DESCRIPTION); @@ -890,8 +928,8 @@ void updateInvalidDateException() { //when ReservationCreateUpdateWithPasswordRequest reservationCreateUpdateWithPasswordRequest = new ReservationCreateUpdateWithPasswordRequest( - LocalDateTime.now().plusDays(1), - LocalDateTime.now().plusDays(2), + LocalDateTime.now().plusDays(1).atZone(KST.toZoneId()), + LocalDateTime.now().plusDays(2).atZone(KST.toZoneId()), reservation.getPassword(), CHANGED_NAME, CHANGED_DESCRIPTION); @@ -921,8 +959,8 @@ void updateIncorrectPasswordException() { //when ReservationCreateUpdateWithPasswordRequest reservationCreateUpdateWithPasswordRequest = new ReservationCreateUpdateWithPasswordRequest( - reservation.getStartTime(), - reservation.getEndTime(), + TimeZoneUtils.convert(reservation.getStartTime(), UTC, KST).atZone(KST.toZoneId()), + TimeZoneUtils.convert(reservation.getEndTime(), UTC, KST).atZone(KST.toZoneId()), "1231", CHANGED_NAME, CHANGED_DESCRIPTION); @@ -950,15 +988,18 @@ void updateImpossibleTimeException(int startTime, int endTime) { .willReturn(Optional.of(luther)); given(reservations.findById(anyLong())) .willReturn(Optional.of(reservation)); - given(reservations.findAllBySpaceIdInAndDate(anyList(), any())) + given(reservations.findAllBySpaceIdInAndDateGreaterThanEqualAndDateLessThanEqual( + anyList(), + any(LocalDate.class), + any(LocalDate.class))) .willReturn(Arrays.asList( beAmZeroOne, bePmOneTwo)); //when ReservationCreateUpdateWithPasswordRequest reservationCreateUpdateWithPasswordRequest = new ReservationCreateUpdateWithPasswordRequest( - bePmOneTwo.getStartTime().plusMinutes(startTime), - bePmOneTwo.getEndTime().plusMinutes(endTime), + TimeZoneUtils.convert(bePmOneTwo.getStartTime().plusMinutes(startTime), UTC, KST).atZone(KST.toZoneId()), + TimeZoneUtils.convert(bePmOneTwo.getEndTime().plusMinutes(endTime), UTC, KST).atZone(KST.toZoneId()), reservation.getPassword(), reservation.getUserName(), reservation.getDescription()); @@ -974,7 +1015,7 @@ void updateImpossibleTimeException(int startTime, int endTime) { assertThatThrownBy(() -> reservationService.updateReservation( reservationUpdateDto, guestReservationStrategy)) - .isInstanceOf(ImpossibleReservationTimeException.class); + .isInstanceOf(ReservationAlreadyExistsException.class); } @ParameterizedTest @@ -989,8 +1030,8 @@ void updateInvalidTimeSetting(int startTime, int endTime) { //when ReservationCreateUpdateWithPasswordRequest reservationCreateUpdateWithPasswordRequest = new ReservationCreateUpdateWithPasswordRequest( - THE_DAY_AFTER_TOMORROW.atTime(startTime, 0), - THE_DAY_AFTER_TOMORROW.atTime(endTime, 30), + THE_DAY_AFTER_TOMORROW.atTime(startTime, 0).atZone(KST.toZoneId()), + THE_DAY_AFTER_TOMORROW.atTime(endTime, 30).atZone(KST.toZoneId()), RESERVATION_PW, CHANGED_NAME, CHANGED_DESCRIPTION); @@ -1104,8 +1145,8 @@ void deleteReservation() { .willReturn(Optional.of(luther)); given(reservations.findById(anyLong())) .willReturn(Optional.of(makeReservation( - reservationCreateUpdateWithPasswordRequest.getStartDateTime(), - reservationCreateUpdateWithPasswordRequest.getEndDateTime(), + reservationCreateUpdateWithPasswordRequest.localStartDateTime(), + reservationCreateUpdateWithPasswordRequest.localEndDateTime(), be))); Long reservationId = reservation.getId(); @@ -1155,8 +1196,8 @@ void deleteReservationPasswordException() { .willReturn(Optional.of(luther)); given(reservations.findById(anyLong())) .willReturn(Optional.of(makeReservation( - reservationCreateUpdateWithPasswordRequest.getStartDateTime(), - reservationCreateUpdateWithPasswordRequest.getEndDateTime(), + reservationCreateUpdateWithPasswordRequest.localStartDateTime(), + reservationCreateUpdateWithPasswordRequest.localEndDateTime(), be))); ReservationPasswordAuthenticationRequest reservationPasswordAuthenticationRequest @@ -1185,8 +1226,8 @@ void deletePastReservationException() { .willReturn(Optional.of(luther)); given(reservations.findById(anyLong())) .willReturn(Optional.of(makeReservation( - reservationCreateUpdateWithPasswordRequest.getStartDateTime().minusDays(5), - reservationCreateUpdateWithPasswordRequest.getEndDateTime().minusDays(5), + reservationCreateUpdateWithPasswordRequest.localStartDateTime().minusDays(5), + reservationCreateUpdateWithPasswordRequest.localEndDateTime().minusDays(5), be))); ReservationPasswordAuthenticationRequest reservationPasswordAuthenticationRequest diff --git a/backend/src/test/java/com/woowacourse/zzimkkong/service/ManagerReservationServiceTest.java b/backend/src/test/java/com/woowacourse/zzimkkong/service/ManagerReservationServiceTest.java index 2af8fba0a..831fda344 100644 --- a/backend/src/test/java/com/woowacourse/zzimkkong/service/ManagerReservationServiceTest.java +++ b/backend/src/test/java/com/woowacourse/zzimkkong/service/ManagerReservationServiceTest.java @@ -7,7 +7,7 @@ import com.woowacourse.zzimkkong.exception.map.NoSuchMapException; import com.woowacourse.zzimkkong.exception.reservation.*; import com.woowacourse.zzimkkong.exception.space.NoSuchSpaceException; -import com.woowacourse.zzimkkong.dto.member.LoginEmailDto; +import com.woowacourse.zzimkkong.infrastructure.datetime.TimeZoneUtils; import com.woowacourse.zzimkkong.service.strategy.ManagerReservationStrategy; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -20,12 +20,15 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; +import java.time.ZonedDateTime; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Optional; import static com.woowacourse.zzimkkong.Constants.*; +import static com.woowacourse.zzimkkong.infrastructure.datetime.TimeZoneUtils.KST; +import static com.woowacourse.zzimkkong.infrastructure.datetime.TimeZoneUtils.UTC; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; @@ -41,14 +44,14 @@ class ManagerReservationServiceTest extends ServiceTest { private final ManagerReservationStrategy managerReservationStrategy = new ManagerReservationStrategy(); private ReservationCreateUpdateWithPasswordRequest reservationCreateUpdateWithPasswordRequest = new ReservationCreateUpdateWithPasswordRequest( - THE_DAY_AFTER_TOMORROW.atTime(13, 0), - THE_DAY_AFTER_TOMORROW.atTime(14, 0), + THE_DAY_AFTER_TOMORROW.atTime(11, 0).atZone(KST.toZoneId()), + THE_DAY_AFTER_TOMORROW.atTime(12, 0).atZone(KST.toZoneId()), RESERVATION_PW, USER_NAME, DESCRIPTION); private final ReservationCreateUpdateRequest reservationCreateUpdateRequest = new ReservationCreateUpdateRequest( - THE_DAY_AFTER_TOMORROW.atTime(13, 0), - THE_DAY_AFTER_TOMORROW.atTime(14, 0), + THE_DAY_AFTER_TOMORROW.atTime(11, 0).atZone(KST.toZoneId()), + THE_DAY_AFTER_TOMORROW.atTime(12, 0).atZone(KST.toZoneId()), CHANGED_NAME, CHANGED_DESCRIPTION); @@ -74,7 +77,7 @@ void setUp() { sakjung = new Member(NEW_EMAIL, PW, ORGANIZATION); pobiEmail = LoginEmailDto.from(EMAIL); sakjungEmail = LoginEmailDto.from(NEW_EMAIL); - luther = new Map(1L, LUTHER_NAME, MAP_DRAWING_DATA, MAP_IMAGE_URL, pobi); + luther = new Map(1L, LUTHER_NAME, MAP_DRAWING_DATA, MAP_SVG, pobi); Setting beSetting = Setting.builder() .availableStartTime(BE_AVAILABLE_START_TIME) @@ -117,8 +120,8 @@ void setUp() { beAmZeroOne = Reservation.builder() .id(1L) - .startTime(BE_AM_TEN_ELEVEN_START_TIME) - .endTime(BE_AM_TEN_ELEVEN_END_TIME) + .startTime(BE_AM_TEN_ELEVEN_START_TIME_KST.withZoneSameInstant(UTC.toZoneId()).toLocalDateTime()) + .endTime(BE_AM_TEN_ELEVEN_END_TIME_KST.withZoneSameInstant(UTC.toZoneId()).toLocalDateTime()) .description(BE_AM_TEN_ELEVEN_DESCRIPTION) .userName(BE_AM_TEN_ELEVEN_USERNAME) .password(BE_AM_TEN_ELEVEN_PW) @@ -127,8 +130,8 @@ void setUp() { bePmOneTwo = Reservation.builder() .id(2L) - .startTime(BE_PM_ONE_TWO_START_TIME) - .endTime(BE_PM_ONE_TWO_END_TIME) + .startTime(BE_PM_ONE_TWO_START_TIME_KST.withZoneSameInstant(UTC.toZoneId()).toLocalDateTime()) + .endTime(BE_PM_ONE_TWO_END_TIME_KST.withZoneSameInstant(UTC.toZoneId()).toLocalDateTime()) .description(BE_PM_ONE_TWO_DESCRIPTION) .userName(BE_PM_ONE_TWO_USERNAME) .password(BE_PM_ONE_TWO_PW) @@ -136,8 +139,8 @@ void setUp() { .build(); reservation = makeReservation( - reservationCreateUpdateWithPasswordRequest.getStartDateTime(), - reservationCreateUpdateWithPasswordRequest.getEndDateTime(), + reservationCreateUpdateWithPasswordRequest.localStartDateTime(), + reservationCreateUpdateWithPasswordRequest.localEndDateTime(), be); lutherId = luther.getId(); @@ -177,8 +180,8 @@ void savePastReservation() { //given Reservation pastReservation = Reservation.builder() .id(1L) - .startTime(BE_AM_TEN_ELEVEN_START_TIME.minusDays(5)) - .endTime(BE_AM_TEN_ELEVEN_END_TIME.minusDays(5)) + .startTime(BE_AM_TEN_ELEVEN_START_TIME_KST.minusDays(5).withZoneSameInstant(UTC.toZoneId()).toLocalDateTime()) + .endTime(BE_AM_TEN_ELEVEN_END_TIME_KST.minusDays(5).withZoneSameInstant(UTC.toZoneId()).toLocalDateTime()) .description(BE_AM_TEN_ELEVEN_DESCRIPTION) .userName(BE_AM_TEN_ELEVEN_USERNAME) .password(BE_AM_TEN_ELEVEN_PW) @@ -190,9 +193,11 @@ void savePastReservation() { .willReturn(pastReservation); //when + ZonedDateTime pastReservationStartTimeKST = TimeZoneUtils.convert(pastReservation.getStartTime(), UTC, KST).atZone(KST.toZoneId()); + ZonedDateTime pastReservationEndTimeKST = TimeZoneUtils.convert(pastReservation.getEndTime(), UTC, KST).atZone(KST.toZoneId()); ReservationCreateUpdateWithPasswordRequest pastReservationCreateUpdateWithPasswordRequest = new ReservationCreateUpdateWithPasswordRequest( - pastReservation.getStartTime(), - pastReservation.getEndTime(), + pastReservationStartTimeKST, + pastReservationEndTimeKST, pastReservation.getPassword(), pastReservation.getUserName(), pastReservation.getDescription()); @@ -281,8 +286,8 @@ void saveEndTimeBeforeNow() { .willReturn(Optional.of(luther)); reservationCreateUpdateWithPasswordRequest = new ReservationCreateUpdateWithPasswordRequest( - THE_DAY_AFTER_TOMORROW.atTime(14, 0), - THE_DAY_AFTER_TOMORROW.atTime(13, 0), + THE_DAY_AFTER_TOMORROW.atTime(14, 0).atZone(KST.toZoneId()), + THE_DAY_AFTER_TOMORROW.atTime(13, 0).atZone(KST.toZoneId()), RESERVATION_PW, USER_NAME, DESCRIPTION); @@ -309,8 +314,8 @@ void saveStartTimeEqualsEndTime() { .willReturn(Optional.of(luther)); reservationCreateUpdateWithPasswordRequest = new ReservationCreateUpdateWithPasswordRequest( - THE_DAY_AFTER_TOMORROW.atTime(10, 0), - THE_DAY_AFTER_TOMORROW.atTime(10, 0), + THE_DAY_AFTER_TOMORROW.atTime(10, 0).atZone(KST.toZoneId()), + THE_DAY_AFTER_TOMORROW.atTime(10, 0).atZone(KST.toZoneId()), RESERVATION_PW, USER_NAME, DESCRIPTION); @@ -337,8 +342,8 @@ void saveStartTimeDateNotEqualsEndTimeDate() { .willReturn(Optional.of(luther)); reservationCreateUpdateWithPasswordRequest = new ReservationCreateUpdateWithPasswordRequest( - THE_DAY_AFTER_TOMORROW.atTime(10, 0), - THE_DAY_AFTER_TOMORROW.atTime(10, 0).plusDays(1), + THE_DAY_AFTER_TOMORROW.atTime(10, 0).atZone(KST.toZoneId()), + THE_DAY_AFTER_TOMORROW.atTime(10, 0).plusDays(1).atZone(KST.toZoneId()), RESERVATION_PW, USER_NAME, DESCRIPTION); @@ -366,8 +371,8 @@ void saveInvalidTimeSetting(int startTime, int endTime) { .willReturn(Optional.of(luther)); reservationCreateUpdateWithPasswordRequest = new ReservationCreateUpdateWithPasswordRequest( - THE_DAY_AFTER_TOMORROW.atTime(startTime, 0), - THE_DAY_AFTER_TOMORROW.atTime(endTime, 30), + THE_DAY_AFTER_TOMORROW.atTime(startTime, 0).atZone(KST.toZoneId()), + THE_DAY_AFTER_TOMORROW.atTime(endTime, 30).atZone(KST.toZoneId()), RESERVATION_PW, USER_NAME, DESCRIPTION); @@ -393,12 +398,13 @@ void saveAvailabilityException(int startMinute, int endMinute) { //given, when given(maps.findByIdFetch(anyLong())) .willReturn(Optional.of(luther)); - given(reservations.findAllBySpaceIdInAndDate( + given(reservations.findAllBySpaceIdInAndDateGreaterThanEqualAndDateLessThanEqual( anyList(), + any(LocalDate.class), any(LocalDate.class))) .willReturn(List.of(makeReservation( - reservationCreateUpdateWithPasswordRequest.getStartDateTime().minusMinutes(startMinute), - reservationCreateUpdateWithPasswordRequest.getEndDateTime().plusMinutes(endMinute), + reservationCreateUpdateWithPasswordRequest.localStartDateTime().minusMinutes(startMinute), + reservationCreateUpdateWithPasswordRequest.localEndDateTime().plusMinutes(endMinute), be))); //when @@ -412,7 +418,7 @@ void saveAvailabilityException(int startMinute, int endMinute) { assertThatThrownBy(() -> reservationService.saveReservation( reservationCreateDto, managerReservationStrategy)) - .isInstanceOf(ImpossibleReservationTimeException.class); + .isInstanceOf(ReservationAlreadyExistsException.class); } @Test @@ -505,18 +511,20 @@ void saveSameThresholdTime(int duration) { //given, when given(maps.findByIdFetch(anyLong())) .willReturn(Optional.of(luther)); - given(reservations.findAllBySpaceIdInAndDate( + given(reservations.findAllBySpaceIdInAndDateGreaterThanEqualAndDateLessThanEqual( anyList(), + any(LocalDate.class), any(LocalDate.class))) .willReturn(List.of( makeReservation( - reservationCreateUpdateWithPasswordRequest.getStartDateTime().minusMinutes(duration), - reservationCreateUpdateWithPasswordRequest.getEndDateTime().minusMinutes(duration), + reservationCreateUpdateWithPasswordRequest.localStartDateTime().minusMinutes(duration), + reservationCreateUpdateWithPasswordRequest.localEndDateTime().minusMinutes(duration), be), makeReservation( - reservationCreateUpdateWithPasswordRequest.getStartDateTime().plusMinutes(duration), - reservationCreateUpdateWithPasswordRequest.getEndDateTime().plusMinutes(duration), + reservationCreateUpdateWithPasswordRequest.localStartDateTime().plusMinutes(duration), + reservationCreateUpdateWithPasswordRequest.localEndDateTime().plusMinutes(duration), be))); + given(reservations.save(any(Reservation.class))) .willReturn(reservation); @@ -546,14 +554,14 @@ void saveReservationTimeUnitException(int additionalStartMinute, int additionalE //when ReservationCreateUpdateWithPasswordRequest reservationCreateUpdateWithPasswordRequest = new ReservationCreateUpdateWithPasswordRequest( - theDayAfterTomorrowTen.plusMinutes(additionalStartMinute), - theDayAfterTomorrowTen.plusMinutes(additionalEndMinute), + theDayAfterTomorrowTen.plusMinutes(additionalStartMinute).atZone(KST.toZoneId()), + theDayAfterTomorrowTen.plusMinutes(additionalEndMinute).atZone(KST.toZoneId()), RESERVATION_PW, USER_NAME, DESCRIPTION); ReservationCreateUpdateRequest reservationCreateUpdateRequest = new ReservationCreateUpdateRequest( - theDayAfterTomorrowTen.plusMinutes(additionalStartMinute), - theDayAfterTomorrowTen.plusMinutes(additionalEndMinute), + theDayAfterTomorrowTen.plusMinutes(additionalStartMinute).atZone(KST.toZoneId()), + theDayAfterTomorrowTen.plusMinutes(additionalEndMinute).atZone(KST.toZoneId()), USER_NAME, DESCRIPTION); @@ -583,12 +591,10 @@ void saveReservationTimeUnitException(int additionalStartMinute, int additionalE .isInstanceOf(InvalidTimeUnitException.class); } - @ParameterizedTest - @ValueSource(ints = {50, 130}) - @DisplayName("예약 생성/수정 요청 시, space setting의 minimum, maximum 시간이 옳지 않으면 예외가 발생한다.") - void saveReservationMinimumMaximumTimeUnitException(int duration) { + @Test + @DisplayName("예약 생성 요청 시, space setting의 minimum 시간이 옳지 않으면 예외가 발생한다.") + void saveReservationMinimumTimeUnitException() { //given - given(maps.findByIdFetch(anyLong())) .willReturn(Optional.of(luther)); given(reservations.findById(anyLong())) @@ -596,25 +602,73 @@ void saveReservationMinimumMaximumTimeUnitException(int duration) { //when ReservationCreateUpdateWithPasswordRequest reservationCreateUpdateWithPasswordRequest = new ReservationCreateUpdateWithPasswordRequest( - THE_DAY_AFTER_TOMORROW.atTime(10, 0), - THE_DAY_AFTER_TOMORROW.atTime(10, 0).plusMinutes(duration), + THE_DAY_AFTER_TOMORROW.atTime(10, 0).atZone(KST.toZoneId()), + THE_DAY_AFTER_TOMORROW.atTime(10, 0).plusMinutes(50).atZone(KST.toZoneId()), RESERVATION_PW, USER_NAME, DESCRIPTION); - ReservationCreateUpdateRequest reservationCreateUpdateRequest = new ReservationCreateUpdateRequest( - THE_DAY_AFTER_TOMORROW.atTime(10, 0), - THE_DAY_AFTER_TOMORROW.atTime(10, 0).plusMinutes(duration), + + ReservationCreateDto reservationCreateDto = ReservationCreateDto.of( + lutherId, + beId, + reservationCreateUpdateWithPasswordRequest, + pobiEmail); + + //then + assertThatThrownBy(() -> reservationService.saveReservation( + reservationCreateDto, + managerReservationStrategy)) + .isInstanceOf(InvalidMinimumDurationTimeException.class); + } + + @Test + @DisplayName("예약 생성 요청 시, space setting의 maximum 시간이 옳지 않으면 예외가 발생한다.") + void saveReservationMaximumTimeUnitException() { + //given + given(maps.findByIdFetch(anyLong())) + .willReturn(Optional.of(luther)); + given(reservations.findById(anyLong())) + .willReturn(Optional.of(reservation)); + + //when + ReservationCreateUpdateWithPasswordRequest reservationCreateUpdateWithPasswordRequest = new ReservationCreateUpdateWithPasswordRequest( + THE_DAY_AFTER_TOMORROW.atTime(10, 0).atZone(KST.toZoneId()), + THE_DAY_AFTER_TOMORROW.atTime(10, 0).plusMinutes(130).atZone(KST.toZoneId()), + RESERVATION_PW, USER_NAME, DESCRIPTION); - Long reservationId = reservation.getId(); - ReservationCreateDto reservationCreateDto = ReservationCreateDto.of( lutherId, beId, reservationCreateUpdateWithPasswordRequest, pobiEmail); + //then + assertThatThrownBy(() -> reservationService.saveReservation( + reservationCreateDto, + managerReservationStrategy)) + .isInstanceOf(InvalidMaximumDurationTimeException.class); + } + + @Test + @DisplayName("예약 수정 요청 시, space setting의 minimum 시간이 옳지 않으면 예외가 발생한다.") + void updateReservationMinimumDurationTimeException() { + //given + given(maps.findByIdFetch(anyLong())) + .willReturn(Optional.of(luther)); + given(reservations.findById(anyLong())) + .willReturn(Optional.of(reservation)); + + //when + ReservationCreateUpdateRequest reservationCreateUpdateRequest = new ReservationCreateUpdateRequest( + THE_DAY_AFTER_TOMORROW.atTime(10, 0).atZone(KST.toZoneId()), + THE_DAY_AFTER_TOMORROW.atTime(10, 0).plusMinutes(50).atZone(KST.toZoneId()), + USER_NAME, + DESCRIPTION); + + Long reservationId = reservation.getId(); + ReservationUpdateDto reservationUpdateDto = ReservationUpdateDto.of( lutherId, beId, @@ -623,15 +677,42 @@ void saveReservationMinimumMaximumTimeUnitException(int duration) { pobiEmail); //then - assertThatThrownBy(() -> reservationService.saveReservation( - reservationCreateDto, + assertThatThrownBy(() -> reservationService.updateReservation( + reservationUpdateDto, managerReservationStrategy)) - .isInstanceOf(InvalidDurationTimeException.class); + .isInstanceOf(InvalidMinimumDurationTimeException.class); + } + + @Test + @DisplayName("예약 수정 요청 시, space setting의 maximum 시간이 옳지 않으면 예외가 발생한다.") + void updateReservationMaximumDurationTimeException() { + //given + given(maps.findByIdFetch(anyLong())) + .willReturn(Optional.of(luther)); + given(reservations.findById(anyLong())) + .willReturn(Optional.of(reservation)); + + //when + ReservationCreateUpdateRequest reservationCreateUpdateRequest = new ReservationCreateUpdateRequest( + THE_DAY_AFTER_TOMORROW.atTime(10, 0).atZone(KST.toZoneId()), + THE_DAY_AFTER_TOMORROW.atTime(10, 0).plusMinutes(130).atZone(KST.toZoneId()), + USER_NAME, + DESCRIPTION); + + Long reservationId = reservation.getId(); + + ReservationUpdateDto reservationUpdateDto = ReservationUpdateDto.of( + lutherId, + beId, + reservationId, + reservationCreateUpdateRequest, + pobiEmail); + //then assertThatThrownBy(() -> reservationService.updateReservation( reservationUpdateDto, managerReservationStrategy)) - .isInstanceOf(InvalidDurationTimeException.class); + .isInstanceOf(InvalidMaximumDurationTimeException.class); } @Test @@ -641,18 +722,19 @@ void findReservations() { int duration = 30; List foundReservations = Arrays.asList( makeReservation( - reservationCreateUpdateWithPasswordRequest.getStartDateTime().minusMinutes(duration), - reservationCreateUpdateWithPasswordRequest.getEndDateTime().minusMinutes(duration), + reservationCreateUpdateWithPasswordRequest.localStartDateTime().minusMinutes(duration), + reservationCreateUpdateWithPasswordRequest.localEndDateTime().minusMinutes(duration), be), makeReservation( - reservationCreateUpdateWithPasswordRequest.getStartDateTime().plusMinutes(duration), - reservationCreateUpdateWithPasswordRequest.getEndDateTime().plusMinutes(duration), + reservationCreateUpdateWithPasswordRequest.localStartDateTime().plusMinutes(duration), + reservationCreateUpdateWithPasswordRequest.localEndDateTime().plusMinutes(duration), be)); given(maps.findByIdFetch(anyLong())) .willReturn(Optional.of(luther)); - given(reservations.findAllBySpaceIdInAndDate( + given(reservations.findAllBySpaceIdInAndDateGreaterThanEqualAndDateLessThanEqual( anyList(), + any(LocalDate.class), any(LocalDate.class))) .willReturn(foundReservations); @@ -747,8 +829,9 @@ void findEmptyReservations() { .willReturn(Optional.of(luther)); given(maps.existsById(anyLong())) .willReturn(true); - given(reservations.findAllBySpaceIdInAndDate( + given(reservations.findAllBySpaceIdInAndDateGreaterThanEqualAndDateLessThanEqual( anyList(), + any(LocalDate.class), any(LocalDate.class))) .willReturn(Collections.emptyList()); @@ -783,20 +866,20 @@ void findAllReservation() { int duration = 30; List foundReservations = List.of( makeReservation( - reservationCreateUpdateWithPasswordRequest.getStartDateTime().minusMinutes(duration), - reservationCreateUpdateWithPasswordRequest.getEndDateTime().minusMinutes(duration), + reservationCreateUpdateWithPasswordRequest.localStartDateTime().minusMinutes(duration), + reservationCreateUpdateWithPasswordRequest.localEndDateTime().minusMinutes(duration), be), makeReservation( - reservationCreateUpdateWithPasswordRequest.getStartDateTime().plusMinutes(duration), - reservationCreateUpdateWithPasswordRequest.getEndDateTime().plusMinutes(duration), + reservationCreateUpdateWithPasswordRequest.localStartDateTime().plusMinutes(duration), + reservationCreateUpdateWithPasswordRequest.localEndDateTime().plusMinutes(duration), be), makeReservation( - reservationCreateUpdateWithPasswordRequest.getStartDateTime().minusMinutes(duration), - reservationCreateUpdateWithPasswordRequest.getEndDateTime().minusMinutes(duration), + reservationCreateUpdateWithPasswordRequest.localStartDateTime().minusMinutes(duration), + reservationCreateUpdateWithPasswordRequest.localEndDateTime().minusMinutes(duration), fe), makeReservation( - reservationCreateUpdateWithPasswordRequest.getStartDateTime().plusMinutes(duration), - reservationCreateUpdateWithPasswordRequest.getEndDateTime().plusMinutes(duration), + reservationCreateUpdateWithPasswordRequest.localStartDateTime().plusMinutes(duration), + reservationCreateUpdateWithPasswordRequest.localEndDateTime().plusMinutes(duration), fe)); List findSpaces = List.of(be, fe); @@ -804,8 +887,9 @@ void findAllReservation() { given(maps.findByIdFetch(anyLong())) .willReturn(Optional.of(luther)); - given(reservations.findAllBySpaceIdInAndDate( + given(reservations.findAllBySpaceIdInAndDateGreaterThanEqualAndDateLessThanEqual( anyList(), + any(LocalDate.class), any(LocalDate.class))) .willReturn(foundReservations); @@ -831,8 +915,9 @@ void findAllReservationsNotOwner() { .willReturn(Optional.of(sakjung)); given(maps.findByIdFetch(anyLong())) .willReturn(Optional.of(luther)); - given(reservations.findAllBySpaceIdInAndDate( + given(reservations.findAllBySpaceIdInAndDateGreaterThanEqualAndDateLessThanEqual( anyList(), + any(LocalDate.class), any(LocalDate.class))) .willReturn(List.of(beAmZeroOne, bePmOneTwo)); @@ -857,8 +942,9 @@ void findReservationsNotOwner() { .willReturn(Optional.of(sakjung)); given(maps.findByIdFetch(anyLong())) .willReturn(Optional.of(luther)); - given(reservations.findAllBySpaceIdInAndDate( + given(reservations.findAllBySpaceIdInAndDateGreaterThanEqualAndDateLessThanEqual( anyList(), + any(LocalDate.class), any(LocalDate.class))) .willReturn(List.of(beAmZeroOne, bePmOneTwo)); @@ -964,8 +1050,8 @@ void update(int beforeDay) { // when ReservationCreateUpdateRequest reservationCreateUpdateRequest = new ReservationCreateUpdateRequest( - THE_DAY_AFTER_TOMORROW.atTime(10, 0).minusDays(beforeDay), - THE_DAY_AFTER_TOMORROW.atTime(11, 0).minusDays(beforeDay), + THE_DAY_AFTER_TOMORROW.atTime(10, 0).minusDays(beforeDay).atZone(KST.toZoneId()), + THE_DAY_AFTER_TOMORROW.atTime(11, 0).minusDays(beforeDay).atZone(KST.toZoneId()), CHANGED_NAME, CHANGED_DESCRIPTION); Long reservationId = reservation.getId(); @@ -996,8 +1082,8 @@ void updateNoAuthorityException() { //when ReservationCreateUpdateRequest reservationCreateUpdateRequest = new ReservationCreateUpdateRequest( - THE_DAY_AFTER_TOMORROW.atTime(20, 0), - THE_DAY_AFTER_TOMORROW.atTime(21, 0), + THE_DAY_AFTER_TOMORROW.atTime(20, 0).atZone(KST.toZoneId()), + THE_DAY_AFTER_TOMORROW.atTime(21, 0).atZone(KST.toZoneId()), CHANGED_NAME, CHANGED_DESCRIPTION); Long reservationId = reservation.getId(); @@ -1027,8 +1113,8 @@ void updateInvalidEndTimeException(int endTime) { //when ReservationCreateUpdateRequest reservationCreateUpdateRequest = new ReservationCreateUpdateRequest( - THE_DAY_AFTER_TOMORROW.atTime(12, 0), - THE_DAY_AFTER_TOMORROW.atTime(12, 0).minusHours(endTime), + THE_DAY_AFTER_TOMORROW.atTime(12, 0).atZone(KST.toZoneId()), + THE_DAY_AFTER_TOMORROW.atTime(12, 0).minusHours(endTime).atZone(KST.toZoneId()), CHANGED_NAME, CHANGED_DESCRIPTION); Long reservationId = reservation.getId(); @@ -1057,8 +1143,8 @@ void updateInvalidDateException() { //when ReservationCreateUpdateRequest reservationCreateUpdateRequest = new ReservationCreateUpdateRequest( - THE_DAY_AFTER_TOMORROW.atTime(10, 0), - THE_DAY_AFTER_TOMORROW.atTime(10, 0).plusDays(1), + THE_DAY_AFTER_TOMORROW.atTime(10, 0).atZone(KST.toZoneId()), + THE_DAY_AFTER_TOMORROW.atTime(10, 0).plusDays(1).atZone(KST.toZoneId()), CHANGED_NAME, CHANGED_DESCRIPTION); Long reservationId = reservation.getId(); @@ -1087,15 +1173,18 @@ void updateImpossibleTimeException(int startTime, int endTime) { .willReturn(Optional.of(luther)); given(reservations.findById(anyLong())) .willReturn(Optional.of(reservation)); - given(reservations.findAllBySpaceIdInAndDate(anyList(), any())) + given(reservations.findAllBySpaceIdInAndDateGreaterThanEqualAndDateLessThanEqual( + anyList(), + any(LocalDate.class), + any(LocalDate.class))) .willReturn(Arrays.asList( beAmZeroOne, bePmOneTwo)); //when ReservationCreateUpdateRequest reservationCreateUpdateRequest = new ReservationCreateUpdateRequest( - bePmOneTwo.getStartTime().plusMinutes(startTime), - bePmOneTwo.getEndTime().plusMinutes(endTime), + TimeZoneUtils.convert(bePmOneTwo.getStartTime(), UTC, KST).plusMinutes(startTime).atZone(KST.toZoneId()), + TimeZoneUtils.convert(bePmOneTwo.getEndTime(), UTC, KST).plusMinutes(endTime).atZone(KST.toZoneId()), reservation.getUserName(), reservation.getDescription()); Long reservationId = reservation.getId(); @@ -1111,7 +1200,7 @@ void updateImpossibleTimeException(int startTime, int endTime) { assertThatThrownBy(() -> reservationService.updateReservation( reservationUpdateDto, managerReservationStrategy)) - .isInstanceOf(ImpossibleReservationTimeException.class); + .isInstanceOf(ReservationAlreadyExistsException.class); } @ParameterizedTest @@ -1127,8 +1216,8 @@ void updateInvalidTimeSetting(int startTime, int endTime) { //when ReservationCreateUpdateRequest reservationCreateUpdateRequest = new ReservationCreateUpdateRequest( - THE_DAY_AFTER_TOMORROW.atTime(startTime, 0), - THE_DAY_AFTER_TOMORROW.atTime(endTime, 30), + THE_DAY_AFTER_TOMORROW.atTime(startTime, 0).atZone(KST.toZoneId()), + THE_DAY_AFTER_TOMORROW.atTime(endTime, 30).atZone(KST.toZoneId()), CHANGED_NAME, CHANGED_DESCRIPTION); Long reservationId = reservation.getId(); diff --git a/backend/src/test/java/com/woowacourse/zzimkkong/service/MapServiceTest.java b/backend/src/test/java/com/woowacourse/zzimkkong/service/MapServiceTest.java index 2f43fad5e..ad9c2cf02 100644 --- a/backend/src/test/java/com/woowacourse/zzimkkong/service/MapServiceTest.java +++ b/backend/src/test/java/com/woowacourse/zzimkkong/service/MapServiceTest.java @@ -1,5 +1,6 @@ package com.woowacourse.zzimkkong.service; +import com.woowacourse.zzimkkong.Constants; import com.woowacourse.zzimkkong.domain.Map; import com.woowacourse.zzimkkong.domain.Member; import com.woowacourse.zzimkkong.domain.Setting; @@ -18,8 +19,6 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import java.io.File; -import java.io.InputStream; import java.time.LocalDateTime; import java.util.List; import java.util.Optional; @@ -46,8 +45,8 @@ class MapServiceTest extends ServiceTest { void setUp() { pobi = new Member(EMAIL, PW, ORGANIZATION); pobiEmail = LoginEmailDto.from(EMAIL); - luther = new Map(1L, LUTHER_NAME, MAP_DRAWING_DATA, MAP_IMAGE_URL, pobi); - smallHouse = new Map(2L, SMALL_HOUSE_NAME, MAP_DRAWING_DATA, MAP_IMAGE_URL, pobi); + luther = new Map(1L, LUTHER_NAME, MAP_DRAWING_DATA, MAP_SVG, pobi); + smallHouse = new Map(2L, SMALL_HOUSE_NAME, MAP_DRAWING_DATA, MAP_SVG, pobi); lutherId = luther.getId(); Setting beSetting = Setting.builder() @@ -102,8 +101,6 @@ void create() { .willReturn(Optional.of(pobi)); given(maps.save(any(Map.class))) .willReturn(luther); - given(storageUploader.upload(anyString(), anyString(), any(InputStream.class))) - .willReturn(MAP_IMAGE_URL); //then MapCreateResponse mapCreateResponse = mapService.saveMap(mapCreateUpdateRequest, pobiEmail); @@ -151,8 +148,6 @@ void update() { MapCreateUpdateRequest mapCreateUpdateRequest = new MapCreateUpdateRequest("이름을 바꿔요", luther.getMapDrawing(), MAP_SVG); given(maps.findById(anyLong())) .willReturn(Optional.of(luther)); - given(storageUploader.upload(anyString(), anyString(), any(InputStream.class))) - .willReturn(MAP_IMAGE_URL); //when, then assertDoesNotThrow(() -> mapService.updateMap(luther.getId(), mapCreateUpdateRequest, pobiEmail)); diff --git a/backend/src/test/java/com/woowacourse/zzimkkong/service/ServiceTest.java b/backend/src/test/java/com/woowacourse/zzimkkong/service/ServiceTest.java index 7208c5759..162dad607 100644 --- a/backend/src/test/java/com/woowacourse/zzimkkong/service/ServiceTest.java +++ b/backend/src/test/java/com/woowacourse/zzimkkong/service/ServiceTest.java @@ -1,6 +1,5 @@ package com.woowacourse.zzimkkong.service; -import com.woowacourse.zzimkkong.infrastructure.thumbnail.StorageUploader; import com.woowacourse.zzimkkong.repository.MapRepository; import com.woowacourse.zzimkkong.repository.MemberRepository; import com.woowacourse.zzimkkong.repository.ReservationRepository; @@ -28,9 +27,6 @@ class ServiceTest { @MockBean protected SpaceRepository spaces; - @MockBean - protected StorageUploader storageUploader; - @Autowired protected PasswordEncoder passwordEncoder; } diff --git a/backend/src/test/java/com/woowacourse/zzimkkong/service/SpaceServiceTest.java b/backend/src/test/java/com/woowacourse/zzimkkong/service/SpaceServiceTest.java index 16f1ace91..603607c99 100644 --- a/backend/src/test/java/com/woowacourse/zzimkkong/service/SpaceServiceTest.java +++ b/backend/src/test/java/com/woowacourse/zzimkkong/service/SpaceServiceTest.java @@ -4,19 +4,17 @@ import com.woowacourse.zzimkkong.domain.Member; import com.woowacourse.zzimkkong.domain.Setting; import com.woowacourse.zzimkkong.domain.Space; +import com.woowacourse.zzimkkong.dto.member.LoginEmailDto; import com.woowacourse.zzimkkong.dto.space.*; import com.woowacourse.zzimkkong.exception.authorization.NoAuthorityOnMapException; import com.woowacourse.zzimkkong.exception.map.NoSuchMapException; import com.woowacourse.zzimkkong.exception.space.NoSuchSpaceException; import com.woowacourse.zzimkkong.exception.space.ReservationExistOnSpaceException; -import com.woowacourse.zzimkkong.dto.member.LoginEmailDto; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import java.io.File; -import java.io.InputStream; import java.time.LocalDateTime; import java.util.List; import java.util.Optional; @@ -25,7 +23,8 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.mockito.ArgumentMatchers.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.BDDMockito.given; class SpaceServiceTest extends ServiceTest { @@ -79,7 +78,7 @@ void setUp() { sakjung = new Member(NEW_EMAIL, PW, ORGANIZATION); pobiEmail = LoginEmailDto.from(EMAIL); sakjungEmail = LoginEmailDto.from(NEW_EMAIL); - luther = new Map(1L, LUTHER_NAME, MAP_DRAWING_DATA, MAP_IMAGE_URL, pobi); + luther = new Map(1L, LUTHER_NAME, MAP_DRAWING_DATA, MAP_SVG, pobi); Setting beSetting = Setting.builder() .availableStartTime(BE_AVAILABLE_START_TIME) @@ -153,8 +152,6 @@ void save() { .willReturn(Optional.of(luther)); given(spaces.save(any(Space.class))) .willReturn(newSpace); - given(storageUploader.upload(anyString(), anyString(), any(InputStream.class))) - .willReturn(MAP_IMAGE_URL); // when SpaceCreateResponse spaceCreateResponse = spaceService.saveSpace(luther.getId(), spaceCreateUpdateRequest, pobiEmail); @@ -278,8 +275,6 @@ void update() { // given, when given(maps.findByIdFetch(anyLong())) .willReturn(Optional.of(luther)); - given(storageUploader.upload(anyString(), anyString(), any(InputStream.class))) - .willReturn(MAP_IMAGE_URL); // then assertDoesNotThrow(() -> spaceService.updateSpace( diff --git a/frontend/package.json b/frontend/package.json index ed59e0383..4a33dd3f4 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -29,12 +29,13 @@ "build:dev": "cross-env NODE_ENV=production DEPLOY_ENV=development webpack", "storybook": "start-storybook -s ./src/assets -p 6006", "build-storybook": "build-storybook -s ./src/assets", - "test": "TZ=UTC+9 jest --watch --passWithNoTests", - "test:ci": "TZ=UTC+9 jest --passWithNoTests", + "test": "TZ=Asia/Seoul jest --watch --passWithNoTests", + "test:ci": "TZ=Asia/Seoul jest --passWithNoTests", "prepare": "cd .. && husky install frontend/.husky && cd frontend" }, "dependencies": { "axios": "^0.21.1", + "dayjs": "^1.10.7", "react": "^17.0.2", "react-dom": "^17.0.2", "react-query": "^3.18.1", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 748a7abaa..6f9ef17e9 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,3 +1,6 @@ +import dayjs from 'dayjs'; +import timezone from 'dayjs/plugin/timezone'; +import utc from 'dayjs/plugin/utc'; import { createBrowserHistory } from 'history'; import { Suspense } from 'react'; import { QueryClient, QueryClientProvider } from 'react-query'; @@ -12,6 +15,10 @@ import AccessTokenProvider from 'providers/AccessTokenProvider'; import { GlobalStyle, theme } from './App.styles'; import NotFound from './pages/NotFound/NotFound'; +dayjs.extend(utc); +dayjs.extend(timezone); +dayjs.tz.setDefault('Asia/Seoul'); + export const history = createBrowserHistory(); export const queryClient = new QueryClient(); diff --git a/frontend/src/__mocks__/mockData.ts b/frontend/src/__mocks__/mockData.ts index 07a6e910c..c440be244 100644 --- a/frontend/src/__mocks__/mockData.ts +++ b/frontend/src/__mocks__/mockData.ts @@ -30,7 +30,7 @@ export const guestMaps: GuestMaps = { mapName: 'GUEST_TEST_MAP', mapDrawing: '{"width":800,"height":600,"mapElements":[{"id":2,"type":"rect","stroke":"#333333","points":["210,90","650,230"]},{"id":3,"type":"rect","stroke":"#333333","width":440,"height":140,"x":210,"y":90,"points":["210, 90","650, 230"]}]}', - mapImageUrl: '', + thumbnail: '', sharingMapId: 'JMTGR', }, }; diff --git a/frontend/src/api/guestReservation.ts b/frontend/src/api/guestReservation.ts index cc3c6febf..b4cd62bce 100644 --- a/frontend/src/api/guestReservation.ts +++ b/frontend/src/api/guestReservation.ts @@ -15,8 +15,8 @@ export interface QuerySpaceReservationsParams extends QueryMapReservationsParams export interface ReservationParams { reservation: { - startDateTime: Date; - endDateTime: Date; + startDateTime: string; + endDateTime: string; password: string; name: string; description: string; diff --git a/frontend/src/api/managerMap.ts b/frontend/src/api/managerMap.ts index 40ae685d8..a6f3243c1 100644 --- a/frontend/src/api/managerMap.ts +++ b/frontend/src/api/managerMap.ts @@ -1,6 +1,10 @@ import { AxiosResponse } from 'axios'; import { QueryFunction, QueryKey } from 'react-query'; -import { QueryManagerMapSuccess, QueryManagerMapsSuccess } from 'types/response'; +import { + QueryManagerMapSuccess, + QueryManagerMapsSuccess, + QuerySlackWebhookUrlSuccess, +} from 'types/response'; import api from './api'; export interface QueryManagerMapParams { @@ -10,20 +14,29 @@ export interface QueryManagerMapParams { interface PostMapParams { mapName: string; mapDrawing: string; - mapImageSvg: string; + thumbnail: string; } interface PutMapParams { mapId: number; mapName: string; mapDrawing: string; - mapImageSvg: string; + thumbnail: string; } interface DeleteMapParams { mapId: number; } +export interface QuerySlackWebhookURLParams { + mapId: number; +} + +interface PostSlackWebhookURLParams { + mapId: number; + slackUrl: string; +} + export const queryManagerMaps: QueryFunction> = () => api.get('/managers/maps'); @@ -40,17 +53,33 @@ export const queryManagerMap: QueryFunction< export const postMap = ({ mapName, mapDrawing, - mapImageSvg, + thumbnail, }: PostMapParams): Promise> => - api.post('/managers/maps', { mapName, mapDrawing, mapImageSvg }); + api.post('/managers/maps', { mapName, mapDrawing, thumbnail }); export const putMap = ({ mapId, mapName, mapDrawing, - mapImageSvg, + thumbnail, }: PutMapParams): Promise> => - api.put(`/managers/maps/${mapId}`, { mapName, mapDrawing, mapImageSvg }); + api.put(`/managers/maps/${mapId}`, { mapName, mapDrawing, thumbnail }); export const deleteMap = ({ mapId }: DeleteMapParams): Promise> => api.delete(`/managers/maps/${mapId}`); + +export const querySlackWebhookUrl: QueryFunction< + AxiosResponse, + [QueryKey, QuerySlackWebhookURLParams] +> = ({ queryKey }) => { + const [, data] = queryKey; + const { mapId } = data; + + return api.get(`/managers/maps/${mapId}/slack`); +}; + +export const postSlackWebhookUrl = ({ + mapId, + slackUrl, +}: PostSlackWebhookURLParams): Promise> => + api.post(`/managers/maps/${mapId}/slack`, { slackUrl }); diff --git a/frontend/src/api/managerReservation.ts b/frontend/src/api/managerReservation.ts index e93c631ba..fb1f44ae4 100644 --- a/frontend/src/api/managerReservation.ts +++ b/frontend/src/api/managerReservation.ts @@ -19,8 +19,8 @@ export interface PostReservationParams { mapId: number; spaceId: number; reservation: { - startDateTime: Date; - endDateTime: Date; + startDateTime: string; + endDateTime: string; name: string; description: string; password: string; @@ -32,8 +32,8 @@ export interface PutReservationParams { spaceId: number; reservationId: number; reservation: { - startDateTime: Date; - endDateTime: Date; + startDateTime: string; + endDateTime: string; name: string; description: string; }; diff --git a/frontend/src/api/managerSpace.ts b/frontend/src/api/managerSpace.ts index d5e3cf39d..deafb44ec 100644 --- a/frontend/src/api/managerSpace.ts +++ b/frontend/src/api/managerSpace.ts @@ -21,7 +21,7 @@ export interface PostManagerSpaceParams { description: string; area: string; settingsRequest: ReservationSettings; - mapImageSvg: string; + thumbnail: string; }; } @@ -32,7 +32,7 @@ export interface PutManagerSpaceParams extends PostManagerSpaceParams { export interface DeleteManagerSpaceParams { mapId: number; spaceId: number; - mapImageSvg: string; + thumbnail: string; } export const queryManagerSpaces: QueryFunction< @@ -71,6 +71,6 @@ export const putManagerSpace = ({ export const deleteManagerSpace = ({ mapId, spaceId, - mapImageSvg, + thumbnail, }: DeleteManagerSpaceParams): Promise> => - api.delete(`/managers/maps/${mapId}/spaces/${spaceId}`, { data: { mapImageSvg } }); + api.delete(`/managers/maps/${mapId}/spaces/${spaceId}`, { data: { thumbnail } }); diff --git a/frontend/src/assets/svg/slack.svg b/frontend/src/assets/svg/slack.svg new file mode 100644 index 000000000..3d91bc358 --- /dev/null +++ b/frontend/src/assets/svg/slack.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/frontend/src/components/DateInput/DateInput.stories.tsx b/frontend/src/components/DateInput/DateInput.stories.tsx index dd1561ec5..379df812e 100644 --- a/frontend/src/components/DateInput/DateInput.stories.tsx +++ b/frontend/src/components/DateInput/DateInput.stories.tsx @@ -1,4 +1,5 @@ import { Story } from '@storybook/react'; +import dayjs from 'dayjs'; import DateInput, { Props } from './DateInput'; export default { @@ -10,5 +11,5 @@ const Template: Story = (args) => ; export const Default = Template.bind({}); Default.args = { - date: new Date(), + date: dayjs().tz(), }; diff --git a/frontend/src/components/DateInput/DateInput.tsx b/frontend/src/components/DateInput/DateInput.tsx index 09d85bf96..ce9c81f1a 100644 --- a/frontend/src/components/DateInput/DateInput.tsx +++ b/frontend/src/components/DateInput/DateInput.tsx @@ -1,24 +1,25 @@ +import dayjs, { Dayjs } from 'dayjs'; import { ChangeEventHandler, Dispatch, InputHTMLAttributes, SetStateAction } from 'react'; import IconButton from 'components/IconButton/IconButton'; import { formatDate, formatDateWithDay } from 'utils/datetime'; import * as Styled from './DateInput.styles'; export interface Props extends Omit, 'type' | 'value'> { - date: Date; - setDate: Dispatch>; + date: Dayjs; + setDate: Dispatch>; } const DateInput = ({ date, setDate, ...props }: Props): JSX.Element => { const onChange: ChangeEventHandler = (event) => { - setDate(new Date(event.target.value)); + setDate(dayjs(event.target.value).tz()); }; const onClickPrev = () => { - setDate(new Date(date.setDate(date.getDate() - 1))); + setDate(dayjs(date).subtract(1, 'day')); }; const onClickNext = () => { - setDate(new Date(date.setDate(date.getDate() + 1))); + setDate(dayjs(date).add(1, 'day')); }; return ( diff --git a/frontend/src/components/MapListItem/MapListItem.stories.tsx b/frontend/src/components/MapListItem/MapListItem.stories.tsx index 75b8dc3a3..a6023ef2e 100644 --- a/frontend/src/components/MapListItem/MapListItem.stories.tsx +++ b/frontend/src/components/MapListItem/MapListItem.stories.tsx @@ -10,10 +10,7 @@ export default { const Template: Story = (args) => ; -const thumbnail = { - src: './images/luther.png', - alt: '루터회관 14F 공간', -}; +const thumbnail = ` `; export const Default = Template.bind({}); Default.args = { diff --git a/frontend/src/components/MapListItem/MapListItem.styles.ts b/frontend/src/components/MapListItem/MapListItem.styles.ts index 3d1246522..897d6ad23 100644 --- a/frontend/src/components/MapListItem/MapListItem.styles.ts +++ b/frontend/src/components/MapListItem/MapListItem.styles.ts @@ -28,11 +28,10 @@ export const ImageInner = styled.div` display: flex; justify-content: center; align-items: center; -`; -export const Image = styled.img` - max-width: 100%; - max-height: 100%; + svg { + z-index: -1; + } `; export const TitleWrapper = styled.div` diff --git a/frontend/src/components/MapListItem/MapListItem.tsx b/frontend/src/components/MapListItem/MapListItem.tsx index 02971cd47..b6bf4956a 100644 --- a/frontend/src/components/MapListItem/MapListItem.tsx +++ b/frontend/src/components/MapListItem/MapListItem.tsx @@ -1,12 +1,8 @@ -import { ReactNode, useState } from 'react'; -import MapDefault from 'assets/images/map-default.jpg'; +import { ReactNode } from 'react'; import * as Styled from './MapListItem.styles'; export interface Props { - thumbnail: { - src: string; - alt: string; - }; + thumbnail: string; title: string; control?: ReactNode; selected?: boolean; @@ -20,23 +16,10 @@ const MapListItem = ({ selected = false, onClick = () => null, }: Props): JSX.Element => { - const [thumbnailSrc, setThumbnailSrc] = useState(thumbnail.src); - - const onImgError = () => { - setThumbnailSrc(MapDefault); - }; - return ( - - - + {title} diff --git a/frontend/src/components/ReservationListItem/ReservationListItem.tsx b/frontend/src/components/ReservationListItem/ReservationListItem.tsx index ada608f1f..fc18aa890 100644 --- a/frontend/src/components/ReservationListItem/ReservationListItem.tsx +++ b/frontend/src/components/ReservationListItem/ReservationListItem.tsx @@ -1,3 +1,4 @@ +import dayjs from 'dayjs'; import { ReactNode } from 'react'; import { Reservation, ReservationStatus } from 'types/common'; import { formatTime } from 'utils/datetime'; @@ -12,8 +13,8 @@ export interface Props { const ReservationListItem = ({ reservation, control, status, ...props }: Props): JSX.Element => { const { name, description, startDateTime, endDateTime } = reservation; - const start = formatTime(new Date(startDateTime)); - const end = formatTime(new Date(endDateTime)); + const start = formatTime(dayjs(startDateTime).tz()); + const end = formatTime(dayjs(endDateTime).tz()); return ( diff --git a/frontend/src/constants/api.ts b/frontend/src/constants/api.ts index f15132b21..e6fed7d24 100644 --- a/frontend/src/constants/api.ts +++ b/frontend/src/constants/api.ts @@ -1,4 +1,4 @@ export const BASE_URL = { - DEV: 'https://dev.zzimkkong-proxy.o-r.kr/api', + DEV: 'https://api.zzimkkong.com/api', PROD: 'https://k8s.zzimkkong.com/api', }; diff --git a/frontend/src/constants/date.ts b/frontend/src/constants/date.ts index ba0024931..2b8f0a2e1 100644 --- a/frontend/src/constants/date.ts +++ b/frontend/src/constants/date.ts @@ -3,6 +3,7 @@ const DATE = { MIN_DATE_STRING: '2000-01-01', MAX_DATE: new Date('2100-12-31'), MAX_DATE_STRING: '2100-12-31', + TIMEZONE_OFFSET: '+09:00', }; export default DATE; diff --git a/frontend/src/constants/message.ts b/frontend/src/constants/message.ts index 818aaf52e..ba62a54dc 100644 --- a/frontend/src/constants/message.ts +++ b/frontend/src/constants/message.ts @@ -39,6 +39,9 @@ const MESSAGE = { SELECT_MAP: '맵을 선택해주세요.', COPIED_SHARE_LINK: '맵의 공유링크가 클립보드에 복사되었습니다!', UNEXPECTED_COPY_SHARE_LINK: '공유링크를 복사하는 데 문제가 발생했습니다.', + SLACK_WEBHOOK_CREATE_SUCCESS: 'Slack 알림이 설정되었습니다.', + UNEXPECTED_SLACK_WEBHOOK_CREATE_ERROR: + 'Slack 알림을 설정하는데 문제가 발생했습니다. 잠시 후에 다시 시도해주세요.', }, MANAGER_SPACE: { GET_UNEXPECTED_ERROR: diff --git a/frontend/src/hooks/useTimePicker.ts b/frontend/src/hooks/useTimePicker.ts index 192c455b0..6b5e64b22 100644 --- a/frontend/src/hooks/useTimePicker.ts +++ b/frontend/src/hooks/useTimePicker.ts @@ -1,3 +1,4 @@ +import dayjs, { Dayjs, isDayjs } from 'dayjs'; import { MouseEventHandler, useState } from 'react'; import { Step } from 'components/TimePicker/TimePicker'; import { Midday, Range, Time } from 'types/time'; @@ -16,9 +17,19 @@ const generateTo12Hour = (hour: number) => { return result === 0 ? 12 : result; }; -const generateDateToTime = (time: Date, step: Props['step'] = 1): Time => { - const minute = Math.ceil(time.getMinutes() / step) * step; - const hour = minute < 60 ? time.getHours() : time.getHours() + 1; +const generateDateToTime = (time: Date | Dayjs, step: Props['step'] = 1): Time => { + const minute = isDayjs(time) + ? Math.ceil(time.minute() / step) * step + : Math.ceil(time.getMinutes() / step) * step; + + const hour = (() => { + if (isDayjs(time)) { + return minute < 60 ? time.hour() : time.hour() + 1; + } + + return minute < 60 ? time.getHours() : time.getHours() + 1; + })(); + const midday = hour < 12 ? Midday.AM : Midday.PM; return { @@ -41,13 +52,13 @@ const useTimePicker = ({ } => { const [selectedTime, setSelectedTime] = useState(null); const [range, setRange] = useState({ - start: initialStartTime ? generateDateToTime(initialStartTime, step) : null, - end: initialEndTime ? generateDateToTime(initialEndTime, step) : null, + start: initialStartTime ? generateDateToTime(dayjs(initialStartTime).tz(), step) : null, + end: initialEndTime ? generateDateToTime(dayjs(initialEndTime).tz(), step) : null, }); const setInitialTime = (key: keyof Range) => { if (key === 'start' || (key === 'end' && range.start === null)) { - const now = new Date(); + const now = dayjs().tz(); setRange((prev) => ({ ...prev, diff --git a/frontend/src/pages/GuestMap/GuestMap.tsx b/frontend/src/pages/GuestMap/GuestMap.tsx index 4ec95d4e1..e864175fe 100644 --- a/frontend/src/pages/GuestMap/GuestMap.tsx +++ b/frontend/src/pages/GuestMap/GuestMap.tsx @@ -1,4 +1,5 @@ import { AxiosError } from 'axios'; +import dayjs, { Dayjs } from 'dayjs'; import { FormEventHandler, useEffect, useMemo, useRef, useState } from 'react'; import { useMutation } from 'react-query'; import { useHistory, useLocation, useParams } from 'react-router-dom'; @@ -29,7 +30,7 @@ import ReservationDrawer from './units/ReservationDrawer'; export interface GuestMapState { spaceId?: Space['id']; - targetDate?: Date; + targetDate?: Dayjs; scrollPosition?: ScrollPosition; } @@ -76,7 +77,7 @@ const GuestMap = (): JSX.Element => { const [spaceList, setSpaceList] = useState([]); const [selectedSpaceId, setSelectedSpaceId] = useState(spaceId ?? null); - const [date, setDate] = useState(targetDate ? new Date(targetDate) : new Date()); + const [date, setDate] = useState(targetDate ? dayjs(targetDate).tz() : dayjs().tz()); const spaces = useMemo(() => { const result: { [key: string]: Space } = {}; @@ -186,7 +187,7 @@ const GuestMap = (): JSX.Element => { useEffect(() => { if (targetDate) { - setDate(new Date(targetDate)); + setDate(dayjs(targetDate).tz()); } }, [targetDate]); diff --git a/frontend/src/pages/GuestMap/units/ReservationDrawer.tsx b/frontend/src/pages/GuestMap/units/ReservationDrawer.tsx index 0842dca7f..45116f728 100644 --- a/frontend/src/pages/GuestMap/units/ReservationDrawer.tsx +++ b/frontend/src/pages/GuestMap/units/ReservationDrawer.tsx @@ -1,3 +1,4 @@ +import { Dayjs } from 'dayjs'; import { ReactComponent as DeleteIcon } from 'assets/svg/delete.svg'; import { ReactComponent as EditIcon } from 'assets/svg/edit.svg'; import Drawer from 'components/Drawer/Drawer'; @@ -11,7 +12,7 @@ import * as Styled from './ReservationDrawer.styles'; interface Props { reservations: Reservation[]; space: Space; - date: Date; + date: Dayjs; open: boolean; isSuccess: boolean; isLoadingError: boolean; diff --git a/frontend/src/pages/GuestReservation/GuestReservation.tsx b/frontend/src/pages/GuestReservation/GuestReservation.tsx index 6cc3a3280..384851199 100644 --- a/frontend/src/pages/GuestReservation/GuestReservation.tsx +++ b/frontend/src/pages/GuestReservation/GuestReservation.tsx @@ -1,4 +1,5 @@ import { AxiosError } from 'axios'; +import dayjs from 'dayjs'; import React, { useEffect } from 'react'; import { useMutation } from 'react-query'; import { useHistory, useLocation, useParams } from 'react-router-dom'; @@ -51,7 +52,7 @@ const GuestReservation = (): JSX.Element => { const getReservations = useGuestReservations( { mapId, spaceId: space.id, date }, { - enabled: !isPastDate(new Date(date), DATE.MIN_DATE) && !!date, + enabled: !isPastDate(dayjs(date).tz(), DATE.MIN_DATE) && !!date, } ); const reservations = getReservations.data?.data?.reservations ?? []; @@ -70,7 +71,7 @@ const GuestReservation = (): JSX.Element => { name, description, }, - targetDate: new Date(date), + targetDate: dayjs(date).tz(), }, }); }, @@ -85,7 +86,7 @@ const GuestReservation = (): JSX.Element => { pathname: HREF.GUEST_MAP(sharingMapId), state: { spaceId: space.id, - targetDate: new Date(date), + targetDate: dayjs(date).tz(), }, }); }, @@ -121,12 +122,12 @@ const GuestReservation = (): JSX.Element => { target: { value }, } = event; - if (isPastDate(new Date(date), DATE.MIN_DATE)) { + if (isPastDate(dayjs(date).tz(), DATE.MIN_DATE)) { setDate(DATE.MIN_DATE_STRING); return; } - if (isFutureDate(new Date(date), DATE.MAX_DATE)) { + if (isFutureDate(dayjs(date).tz(), DATE.MAX_DATE)) { setDate(DATE.MAX_DATE_STRING); return; } @@ -153,7 +154,7 @@ const GuestReservation = (): JSX.Element => { ) { location.state = { spaceId: space.id, - targetDate: new Date(date), + targetDate: dayjs(date).tz(), scrollPosition, }; } @@ -187,16 +188,16 @@ const GuestReservation = (): JSX.Element => { )} {getReservations.isSuccess && reservations.length === 0 && - !isPastDate(new Date(date)) && ( + !isPastDate(dayjs(date).tz()) && ( {MESSAGE.RESERVATION.SUGGESTION} )} {getReservations.isSuccess && reservations.length === 0 && - isPastDate(new Date(date)) && ( + isPastDate(dayjs(date).tz()) && ( {MESSAGE.RESERVATION.NOT_EXIST} )} - {(isPastDate(new Date(date), DATE.MIN_DATE) || - isFutureDate(new Date(date), DATE.MAX_DATE)) && ( + {(isPastDate(dayjs(date).tz(), DATE.MIN_DATE) || + isFutureDate(dayjs(date).tz(), DATE.MAX_DATE)) && ( {MESSAGE.RESERVATION.NOT_EXIST} )} {getReservations.isSuccess && reservations.length > 0 && ( diff --git a/frontend/src/pages/GuestReservation/GuestReservationSuccess.tsx b/frontend/src/pages/GuestReservation/GuestReservationSuccess.tsx index bca9ee792..a641961df 100644 --- a/frontend/src/pages/GuestReservation/GuestReservationSuccess.tsx +++ b/frontend/src/pages/GuestReservation/GuestReservationSuccess.tsx @@ -1,3 +1,4 @@ +import dayjs from 'dayjs'; import { Redirect, useLocation, useParams } from 'react-router'; import AnimatedLogo from 'components/AnimatedLogo/AnimatedLogo'; import Header from 'components/Header/Header'; @@ -14,8 +15,8 @@ export interface GuestReservationSuccessState { reservation: { name: string; description: string; - startDateTime: Date; - endDateTime: Date; + startDateTime: string; + endDateTime: string; }; } @@ -27,8 +28,8 @@ const GuestReservationSuccess = (): JSX.Element => { const { space, reservation, targetDate } = location.state; - const startDateTimeObject = new Date(reservation.startDateTime.toISOString().slice(0, -1)); - const endDateTimeObject = new Date(reservation.endDateTime.toISOString().slice(0, -1)); + const startDateTimeObject = dayjs(reservation.startDateTime).tz(); + const endDateTimeObject = dayjs(reservation.endDateTime).tz(); const reservationDate = formatDateWithDay(startDateTimeObject); const reservationStartTime = formatTime(startDateTimeObject); diff --git a/frontend/src/pages/GuestReservation/units/GuestReservationForm.tsx b/frontend/src/pages/GuestReservation/units/GuestReservationForm.tsx index fb0a2de45..a8f85ada7 100644 --- a/frontend/src/pages/GuestReservation/units/GuestReservationForm.tsx +++ b/frontend/src/pages/GuestReservation/units/GuestReservationForm.tsx @@ -10,7 +10,13 @@ import useInputs from 'hooks/useInputs'; import useScrollToTop from 'hooks/useScrollToTop'; import useTimePicker from 'hooks/useTimePicker'; import { Reservation, Space } from 'types/common'; -import { formatDate, formatTime, formatTimePrettier, isPastDate } from 'utils/datetime'; +import { + formatDate, + formatTime, + formatTimePrettier, + formatTimeWithSecond, + isPastDate, +} from 'utils/datetime'; import { EditReservationParams } from '../GuestReservation'; import * as Styled from './GuestReservationForm.styles'; @@ -66,8 +72,8 @@ const GuestReservationForm = ({ if (range.start === null || range.end === null) return; - const startDateTime = new Date(`${date}T${formatTime(range.start)}Z`); - const endDateTime = new Date(`${date}T${formatTime(range.end)}Z`); + const startDateTime = `${date}T${formatTimeWithSecond(range.start)}${DATE.TIMEZONE_OFFSET}`; + const endDateTime = `${date}T${formatTimeWithSecond(range.end)}${DATE.TIMEZONE_OFFSET}`; onSubmit(event, { reservation: { diff --git a/frontend/src/pages/ManagerMain/ManagerMain.styles.ts b/frontend/src/pages/ManagerMain/ManagerMain.styles.ts index eff05f3b3..0c9fc8c61 100644 --- a/frontend/src/pages/ManagerMain/ManagerMain.styles.ts +++ b/frontend/src/pages/ManagerMain/ManagerMain.styles.ts @@ -28,3 +28,13 @@ export const VerticalBar = styled.div` export const DateInputWrapper = styled.div` margin: 1rem 0; `; + +export const ButtonText = styled.span` + margin-left: 0.25rem; +`; + +export const SlackModalContainer = styled.div` + display: flex; + justify-content: flex-end; + margin-top: 1.5rem; +`; diff --git a/frontend/src/pages/ManagerMain/ManagerMain.tsx b/frontend/src/pages/ManagerMain/ManagerMain.tsx index 658f7e276..331852904 100644 --- a/frontend/src/pages/ManagerMain/ManagerMain.tsx +++ b/frontend/src/pages/ManagerMain/ManagerMain.tsx @@ -1,33 +1,40 @@ import { AxiosError } from 'axios'; -import { useEffect, useMemo, useState } from 'react'; +import dayjs, { Dayjs } from 'dayjs'; +import React, { useEffect, useMemo, useState } from 'react'; import { useMutation } from 'react-query'; import { useHistory, useLocation } from 'react-router-dom'; -import { deleteMap } from 'api/managerMap'; +import { deleteMap, postSlackWebhookUrl } from 'api/managerMap'; import { deleteManagerReservation } from 'api/managerReservation'; import { ReactComponent as MapEditorIcon } from 'assets/svg/map-editor.svg'; import { ReactComponent as MenuIcon } from 'assets/svg/menu.svg'; +import { ReactComponent as SlackIcon } from 'assets/svg/slack.svg'; import { ReactComponent as SpaceEditorIcon } from 'assets/svg/space-editor.svg'; +import Button from 'components/Button/Button'; import DateInput from 'components/DateInput/DateInput'; import Header from 'components/Header/Header'; import IconButton from 'components/IconButton/IconButton'; +import Input from 'components/Input/Input'; import Layout from 'components/Layout/Layout'; +import Modal from 'components/Modal/Modal'; import PageHeader from 'components/PageHeader/PageHeader'; import MESSAGE from 'constants/message'; import PATH, { HREF } from 'constants/path'; import useManagerMapReservations from 'hooks/query/useManagerMapReservations'; import useManagerMaps from 'hooks/query/useManagerMaps'; import useManagerSpaces from 'hooks/query/useManagerSpaces'; +import useInput from 'hooks/useInput'; import { Reservation } from 'types/common'; import { ErrorResponse, MapItemResponse } from 'types/response'; import { formatDate } from 'utils/datetime'; import { isNullish } from 'utils/type'; import * as Styled from './ManagerMain.styles'; +import useSlackWebhookUrl from './hooks/useSlackWebhookUrl'; import MapDrawer from './units/MapDrawer'; import ReservationList from './units/ReservationList'; export interface ManagerMainState { mapId?: number; - targetDate?: Date; + targetDate?: Dayjs; } const ManagerMain = (): JSX.Element => { @@ -37,12 +44,15 @@ const ManagerMain = (): JSX.Element => { const mapId = location.state?.mapId; const targetDate = location.state?.targetDate; - const [date, setDate] = useState(targetDate ?? new Date()); + const [date, setDate] = useState(targetDate ?? dayjs().tz()); const [open, setOpen] = useState(false); + const [slackModalOpen, setSlackModalOpen] = useState(false); const [selectedMapId, setSelectedMapId] = useState(mapId ?? null); const [selectedMapName, setSelectedMapName] = useState(''); + const [slackUrl, onChangeSlackUrl, setSlackUrl] = useInput(); + const onRequestError = (error: AxiosError) => { alert(error.response?.data?.message ?? MESSAGE.MANAGER_MAIN.UNEXPECTED_GET_DATA_ERROR); }; @@ -100,6 +110,30 @@ const ManagerMain = (): JSX.Element => { }, }); + const getSlackWebhookUrl = useSlackWebhookUrl( + { mapId: selectedMapId as number }, + { + refetchOnWindowFocus: false, + onSuccess: (response) => { + if (!slackUrl) setSlackUrl(response.data.slackUrl); + }, + } + ); + + const createSlackWebhookUrl = useMutation(postSlackWebhookUrl, { + onSuccess: () => { + alert(MESSAGE.MANAGER_MAIN.SLACK_WEBHOOK_CREATE_SUCCESS); + getSlackWebhookUrl.refetch(); + setSlackModalOpen(false); + }, + + onError: (error: AxiosError) => { + alert( + error.response?.data.message ?? MESSAGE.MANAGER_MAIN.UNEXPECTED_SLACK_WEBHOOK_CREATE_ERROR + ); + }, + }); + const handleDeleteMap = (mapId: number) => { if (window.confirm(MESSAGE.MANAGER_MAIN.MAP_DELETE_CONFIRM)) { removeMap.mutate({ mapId }); @@ -156,7 +190,7 @@ const ManagerMain = (): JSX.Element => { }; const handleCreateReservation = (spaceId: number) => { - if (!selectedMapId) return; + if (selectedMapId === null) return; history.push({ pathname: PATH.MANAGER_RESERVATION, @@ -169,7 +203,7 @@ const ManagerMain = (): JSX.Element => { }; const handleEditReservation = (reservation: Reservation, spaceId: number) => { - if (!selectedMapId) return; + if (selectedMapId === null) return; history.push({ pathname: PATH.MANAGER_RESERVATION_EDIT, @@ -183,7 +217,7 @@ const ManagerMain = (): JSX.Element => { }; const handleDeleteReservation = (reservationId: number, spaceId: number) => { - if (!selectedMapId) return; + if (selectedMapId === null) return; if (!window.confirm(MESSAGE.MANAGER_MAIN.RESERVATION_DELETE_CONFIRM)) return; @@ -194,6 +228,17 @@ const ManagerMain = (): JSX.Element => { }); }; + const handleSubmitSlackUrl = (event: React.FormEvent) => { + event.preventDefault(); + + if (selectedMapId === null) return; + + createSlackWebhookUrl.mutate({ + mapId: selectedMapId, + slackUrl, + }); + }; + useEffect(() => { const prevMapId = location.state?.mapId ?? null; const prevMapName = maps.find(({ mapId }) => mapId === prevMapId)?.mapName ?? ''; @@ -223,6 +268,14 @@ const ManagerMain = (): JSX.Element => { rightButtons={ selectedMapId !== null && ( <> + setSlackModalOpen(true)} + > + + + { onDeleteMap={handleDeleteMap} /> )} + + {slackModalOpen && ( + setSlackModalOpen(false)} + > + 알림을 받을 슬랙 웹훅 URL을 입력해주세요 + +
+ + + + + +
+
+
+ )} ); }; diff --git a/frontend/src/pages/ManagerMain/hooks/useSlackWebhookUrl.ts b/frontend/src/pages/ManagerMain/hooks/useSlackWebhookUrl.ts new file mode 100644 index 000000000..ec62f4549 --- /dev/null +++ b/frontend/src/pages/ManagerMain/hooks/useSlackWebhookUrl.ts @@ -0,0 +1,17 @@ +import { AxiosError, AxiosResponse } from 'axios'; +import { QueryKey, useQuery, UseQueryOptions, UseQueryResult } from 'react-query'; +import { querySlackWebhookUrl, QuerySlackWebhookURLParams } from 'api/managerMap'; +import { ErrorResponse, QuerySlackWebhookUrlSuccess } from 'types/response'; + +const useSlackWebhookUrl = >( + { mapId }: QuerySlackWebhookURLParams, + options?: UseQueryOptions< + AxiosResponse, + AxiosError, + TData, + [QueryKey, QuerySlackWebhookURLParams] + > +): UseQueryResult> => + useQuery(['getSlackWebhookUrl', { mapId }], querySlackWebhookUrl, options); + +export default useSlackWebhookUrl; diff --git a/frontend/src/pages/ManagerMain/units/MapDrawer.tsx b/frontend/src/pages/ManagerMain/units/MapDrawer.tsx index e8a3a67a2..74a925d69 100644 --- a/frontend/src/pages/ManagerMain/units/MapDrawer.tsx +++ b/frontend/src/pages/ManagerMain/units/MapDrawer.tsx @@ -32,11 +32,11 @@ const MapDrawer = ({ {organization} - {maps.map(({ mapId, mapName, mapImageUrl }) => ( + {maps.map(({ mapId, mapName, thumbnail }) => ( onSelectMap(mapId, mapName)} - thumbnail={{ src: mapImageUrl, alt: mapName }} + thumbnail={thumbnail} title={mapName} selected={mapId === selectedMapId} control={ diff --git a/frontend/src/pages/ManagerMapEditor/ManagerMapEditor.tsx b/frontend/src/pages/ManagerMapEditor/ManagerMapEditor.tsx index 9f497811f..d7663f202 100644 --- a/frontend/src/pages/ManagerMapEditor/ManagerMapEditor.tsx +++ b/frontend/src/pages/ManagerMapEditor/ManagerMapEditor.tsx @@ -129,7 +129,7 @@ const ManagerMapEditor = (): JSX.Element => { mapElements: mapElements.map(({ ref, ...props }) => props), }); - const mapImageSvg = createMapImageSvg({ + const thumbnail = createMapImageSvg({ mapElements, spaces, width, @@ -137,12 +137,12 @@ const ManagerMapEditor = (): JSX.Element => { }); if (isEdit) { - updateMap.mutate({ mapId: Number(mapId), mapName: name, mapDrawing, mapImageSvg }); + updateMap.mutate({ mapId: Number(mapId), mapName: name, mapDrawing, thumbnail }); return; } - createMap.mutate({ mapName: name, mapDrawing, mapImageSvg }); + createMap.mutate({ mapName: name, mapDrawing, thumbnail }); }; useListenManagerMainState({ mapId: Number(mapId) }, { enabled: isEdit }); diff --git a/frontend/src/pages/ManagerReservation/ManagerReservation.tsx b/frontend/src/pages/ManagerReservation/ManagerReservation.tsx index 8b3c23da7..08cfafbaa 100644 --- a/frontend/src/pages/ManagerReservation/ManagerReservation.tsx +++ b/frontend/src/pages/ManagerReservation/ManagerReservation.tsx @@ -1,4 +1,5 @@ import { AxiosError } from 'axios'; +import dayjs from 'dayjs'; import { useEffect } from 'react'; import { useMutation } from 'react-query'; import { useHistory, useLocation } from 'react-router-dom'; @@ -50,7 +51,7 @@ const ManagerReservation = (): JSX.Element => { const getReservations = useManagerSpaceReservations( { mapId, spaceId: space.id, date }, { - enabled: !isPastDate(new Date(date), DATE.MIN_DATE) && !!date, + enabled: !isPastDate(dayjs(date), DATE.MIN_DATE) && !!date, } ); const reservations = getReservations.data?.data?.reservations ?? []; @@ -61,7 +62,7 @@ const ManagerReservation = (): JSX.Element => { pathname: PATH.MANAGER_MAIN, state: { mapId, - targetDate: new Date(date), + targetDate: dayjs(date), }, }); }, @@ -76,7 +77,7 @@ const ManagerReservation = (): JSX.Element => { pathname: PATH.MANAGER_MAIN, state: { mapId, - targetDate: new Date(date), + targetDate: dayjs(date), }, }); }, @@ -112,12 +113,12 @@ const ManagerReservation = (): JSX.Element => { target: { value }, } = event; - if (isPastDate(new Date(date), DATE.MIN_DATE)) { + if (isPastDate(dayjs(date), DATE.MIN_DATE)) { setDate(DATE.MIN_DATE_STRING); return; } - if (isFutureDate(new Date(date), DATE.MAX_DATE)) { + if (isFutureDate(dayjs(date), DATE.MAX_DATE)) { setDate(DATE.MAX_DATE_STRING); return; } @@ -133,7 +134,7 @@ const ManagerReservation = (): JSX.Element => { ) { location.state = { mapId, - targetDate: new Date(date), + targetDate: dayjs(date), }; } }); @@ -165,18 +166,14 @@ const ManagerReservation = (): JSX.Element => { {getReservations.isLoading && !getReservations.isLoadingError && ( {MESSAGE.RESERVATION.PENDING} )} - {getReservations.isSuccess && - reservations.length === 0 && - !isPastDate(new Date(date)) && ( - {MESSAGE.RESERVATION.SUGGESTION} - )} - {getReservations.isSuccess && - reservations.length === 0 && - isPastDate(new Date(date)) && ( - {MESSAGE.RESERVATION.NOT_EXIST} - )} - {(isPastDate(new Date(date), DATE.MIN_DATE) || - isFutureDate(new Date(date), DATE.MAX_DATE)) && ( + {getReservations.isSuccess && reservations.length === 0 && !isPastDate(dayjs(date)) && ( + {MESSAGE.RESERVATION.SUGGESTION} + )} + {getReservations.isSuccess && reservations.length === 0 && isPastDate(dayjs(date)) && ( + {MESSAGE.RESERVATION.NOT_EXIST} + )} + {(isPastDate(dayjs(date), DATE.MIN_DATE) || + isFutureDate(dayjs(date), DATE.MAX_DATE)) && ( {MESSAGE.RESERVATION.NOT_EXIST} )} {getReservations.isSuccess && reservations.length > 0 && ( diff --git a/frontend/src/pages/ManagerReservation/units/ManagerReservationForm.tsx b/frontend/src/pages/ManagerReservation/units/ManagerReservationForm.tsx index f591ade31..3fb2cdac9 100644 --- a/frontend/src/pages/ManagerReservation/units/ManagerReservationForm.tsx +++ b/frontend/src/pages/ManagerReservation/units/ManagerReservationForm.tsx @@ -11,7 +11,7 @@ import useInputs from 'hooks/useInputs'; import useScrollToTop from 'hooks/useScrollToTop'; import useTimePicker from 'hooks/useTimePicker'; import { ManagerSpaceAPI, Reservation } from 'types/common'; -import { formatDate, formatTime, formatTimePrettier } from 'utils/datetime'; +import { formatDate, formatTime, formatTimePrettier, formatTimeWithSecond } from 'utils/datetime'; import { CreateReservationParams, EditReservationParams } from '../ManagerReservation'; import * as Styled from './ManagerReservationForm.styles'; @@ -67,8 +67,8 @@ const ManagerReservationForm = ({ if (range.start === null || range.end === null) return; - const startDateTime = new Date(`${date}T${formatTime(range.start)}Z`); - const endDateTime = new Date(`${date}T${formatTime(range.end)}Z`); + const startDateTime = `${date}T${formatTimeWithSecond(range.start)}${DATE.TIMEZONE_OFFSET}`; + const endDateTime = `${date}T${formatTimeWithSecond(range.end)}${DATE.TIMEZONE_OFFSET}`; if (!reservation) { onCreateReservation({ diff --git a/frontend/src/pages/ManagerSpaceEditor/units/Form.tsx b/frontend/src/pages/ManagerSpaceEditor/units/Form.tsx index bde12ddb3..869a9f025 100644 --- a/frontend/src/pages/ManagerSpaceEditor/units/Form.tsx +++ b/frontend/src/pages/ManagerSpaceEditor/units/Form.tsx @@ -67,13 +67,13 @@ const Form = ({ const handleSubmit: FormEventHandler = (event) => { event.preventDefault(); - const mapImageSvg = generateSvg({ ...mapData, spaces: getSpacesForSvg() }); + const thumbnail = generateSvg({ ...mapData, spaces: getSpacesForSvg() }); const valuesForRequest = getRequestValues(); if (selectedSpaceId === null) { onCreateSpace({ space: { - mapImageSvg, + thumbnail, ...valuesForRequest.space, settingsRequest: { ...valuesForRequest.space.settings }, }, @@ -85,7 +85,7 @@ const Form = ({ onUpdateSpace({ spaceId: selectedSpaceId, space: { - mapImageSvg, + thumbnail, ...valuesForRequest.space, settingsRequest: { ...valuesForRequest.space.settings }, }, @@ -97,11 +97,11 @@ const Form = ({ if (!window.confirm(MESSAGE.MANAGER_SPACE.DELETE_SPACE_CONFIRM)) return; const filteredSpaces = spaces.filter(({ id }) => id !== selectedSpaceId); - const mapImageSvg = generateSvg({ ...mapData, spaces: filteredSpaces }); + const thumbnail = generateSvg({ ...mapData, spaces: filteredSpaces }); onDeleteSpace({ spaceId: selectedSpaceId, - mapImageSvg, + thumbnail, }); resetForm(); diff --git a/frontend/src/types/common.ts b/frontend/src/types/common.ts index 0978e0a75..db230c4c5 100644 --- a/frontend/src/types/common.ts +++ b/frontend/src/types/common.ts @@ -40,7 +40,7 @@ export interface MapItem { mapId: number; mapName: string; mapDrawing: MapDrawing; - mapImageUrl: string; + thumbnail: string; sharingMapId: string; } diff --git a/frontend/src/types/response.ts b/frontend/src/types/response.ts index 841da3d3b..8654aeea8 100644 --- a/frontend/src/types/response.ts +++ b/frontend/src/types/response.ts @@ -62,3 +62,7 @@ export interface QueryManagerSpacesSuccess { export interface QueryPresetsSuccess { presets: Preset[]; } + +export interface QuerySlackWebhookUrlSuccess { + slackUrl: string; +} diff --git a/frontend/src/utils/datetime.ts b/frontend/src/utils/datetime.ts index f898aa431..4703b3593 100644 --- a/frontend/src/utils/datetime.ts +++ b/frontend/src/utils/datetime.ts @@ -1,7 +1,12 @@ +import dayjs, { Dayjs, isDayjs } from 'dayjs'; import { Midday, Time } from 'types/time'; // Note: YYYY-MM-DD 형식으로 변환함 -export const formatDate = (value: Date): string => { +export const formatDate = (value: Date | Dayjs): string => { + if (isDayjs(value)) { + return value.format('YYYY-MM-DD'); + } + const year = value.getFullYear(); const month = `${value.getMonth() + 1}`.padStart(2, '0'); const date = `${value.getDate()}`.padStart(2, '0'); @@ -12,7 +17,11 @@ export const formatDate = (value: Date): string => { const DAY = ['SUN', 'MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT']; // Note: YYYY/MM/DD (MON) 형식으로 변환함 -export const formatDateWithDay = (value: Date): string => { +export const formatDateWithDay = (value: Date | Dayjs): string => { + if (isDayjs(value)) { + return value.format('YYYY-MM-DD (ddd)').toUpperCase(); + } + const year = value.getFullYear(); const month = `${value.getMonth() + 1}`.padStart(2, '0'); const date = `${value.getDate()}`.padStart(2, '0'); @@ -21,8 +30,12 @@ export const formatDateWithDay = (value: Date): string => { return `${year}/${month}/${date} (${DAY[day]})`; }; -// Note: HH:MM 형태로 변환함 -export const formatTime = (time: Date | Time): string => { +// Note: HH:MM (24시간 기준) 형태로 변환함 +export const formatTime = (time: Date | Time | Dayjs): string => { + if (isDayjs(time)) { + return time.format('HH:mm'); + } + if (time instanceof Date) { const hour = time.getHours(); const minute = time.getMinutes(); @@ -30,17 +43,41 @@ export const formatTime = (time: Date | Time): string => { return `${hour < 10 ? `0${hour}` : `${hour}`}:${minute < 10 ? `0${minute}` : `${minute}`}`; } - const hour = time.midday === Midday.AM ? `${time.hour}`.padStart(2, '0') : `${time.hour + 12}`; const minute = `${time.minute}`.padStart(2, '0'); + if (time.hour === 12) { + return `${time.midday === Midday.AM ? '00' : '12'}:${minute}`; + } + + const hour = time.midday === Midday.AM ? `${time.hour}`.padStart(2, '0') : `${time.hour + 12}`; + return `${hour}:${minute}`; }; -// Note: HH:MM:SS 형태로 변환함 -export const formatTimeWithSecond = (time: Date): string => { - const hour = `${time.getHours()}`.padStart(2, '0'); - const minute = `${time.getMinutes()}`.padStart(2, '0'); - const second = `${time.getSeconds()}`.padStart(2, '0'); +// Note: HH:MM:SS (24시간 기준) 형태로 변환함 +export const formatTimeWithSecond = (time: Date | Time | Dayjs): string => { + if (isDayjs(time)) { + return time.format('HH:mm:ss'); + } + + if (time instanceof Date) { + const hour = time.getHours(); + const minute = time.getMinutes(); + const second = `${time.getSeconds()}`.padStart(2, '0'); + + return `${hour < 10 ? `0${hour}` : `${hour}`}:${ + minute < 10 ? `0${minute}` : `${minute}` + }:${second}`; + } + + const minute = `${time.minute}`.padStart(2, '0'); + const second = '00'; + + if (time.hour === 12) { + return `${time.midday === Midday.AM ? '00' : '12'}:${minute}:${second}`; + } + + const hour = time.midday === Midday.AM ? `${time.hour}`.padStart(2, '0') : `${time.hour + 12}`; return `${hour}:${minute}:${second}`; }; @@ -53,14 +90,26 @@ export const formatTimePrettier = (minutes: number): string => { return `${hour ? `${hour}시간` : ''}${minute ? ' ' : ''}${minute ? `${minute}분` : ''}`; }; -export const isPastTime = (time: Date, baseDate: Date = new Date()): boolean => { +export const isPastTime = (time: Date | Dayjs, baseDate: Date = new Date()): boolean => { + if (isDayjs(time)) { + return +time < +dayjs(baseDate).tz(); + } + return time.getTime() < baseDate.getTime(); }; -export const isPastDate = (time: Date, baseDate: Date = new Date()): boolean => { +export const isPastDate = (time: Date | Dayjs, baseDate: Date = new Date()): boolean => { + if (isDayjs(time)) { + return +time < +dayjs(baseDate).tz() - 1000 * 60 * 60 * 24; + } + return time.getTime() < baseDate.getTime() - 1000 * 60 * 60 * 24; }; -export const isFutureDate = (time: Date, baseDate: Date = new Date()): boolean => { +export const isFutureDate = (time: Date | Dayjs, baseDate: Date = new Date()): boolean => { + if (isDayjs(time)) { + return +time > +dayjs(baseDate).tz() + 1000 * 60 * 60 * 24; + } + return time.getTime() > baseDate.getTime() + 1000 * 60 * 60 * 24; }; diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 1fe14b0dd..e175ea737 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -5899,6 +5899,11 @@ data-urls@^2.0.0: whatwg-mimetype "^2.3.0" whatwg-url "^8.0.0" +dayjs@^1.10.7: + version "1.10.7" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.7.tgz#2cf5f91add28116748440866a0a1d26f3a6ce468" + integrity sha512-P6twpd70BcPK34K26uJ1KT3wlhpuOAPoMwJzpsIWUxHZ7wpmbdZL/hQqBDfz7hGurYSa5PhzdhDHtt319hL3ig== + debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" diff --git a/s3proxy/.gitignore b/s3proxy/.gitignore deleted file mode 100644 index c2065bc26..000000000 --- a/s3proxy/.gitignore +++ /dev/null @@ -1,37 +0,0 @@ -HELP.md -.gradle -build/ -!gradle/wrapper/gradle-wrapper.jar -!**/src/main/**/build/ -!**/src/test/**/build/ - -### STS ### -.apt_generated -.classpath -.factorypath -.project -.settings -.springBeans -.sts4-cache -bin/ -!**/src/main/**/bin/ -!**/src/test/**/bin/ - -### IntelliJ IDEA ### -.idea -*.iws -*.iml -*.ipr -out/ -!**/src/main/**/out/ -!**/src/test/**/out/ - -### NetBeans ### -/nbproject/private/ -/nbbuild/ -/dist/ -/nbdist/ -/.nb-gradle/ - -### VS Code ### -.vscode/ diff --git a/s3proxy/build.gradle b/s3proxy/build.gradle deleted file mode 100644 index 2b1206883..000000000 --- a/s3proxy/build.gradle +++ /dev/null @@ -1,81 +0,0 @@ -plugins { - id 'org.springframework.boot' version '2.5.4' - id 'io.spring.dependency-management' version '1.0.11.RELEASE' - id "org.asciidoctor.convert" version "1.5.10" - id 'java' -} - -group = 'com.woowacourse' -version = '0.0.1-SNAPSHOT' -sourceCompatibility = '11' - -configurations { - compileOnly { - extendsFrom annotationProcessor - } -} - -repositories { - mavenCentral() -} - -ext { - snippetsDir = file('build/generated-snippets') -} - -dependencies { - implementation 'org.springframework.boot:spring-boot-starter-web' - implementation 'org.springframework.boot:spring-boot-starter-webflux' - - // Lombok - compileOnly 'org.projectlombok:lombok' - annotationProcessor 'org.projectlombok:lombok' - - // Multi-Part - implementation 'commons-io:commons-io:2.11.0' - implementation 'commons-fileupload:commons-fileupload:1.4' - - // AWS - implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' - - // Rest Docs - asciidoctor 'org.springframework.restdocs:spring-restdocs-asciidoctor' - testImplementation 'org.springframework.restdocs:spring-restdocs-restassured' - - testImplementation 'io.rest-assured:rest-assured' - testImplementation 'org.springframework.boot:spring-boot-starter-test' - testImplementation 'io.projectreactor:reactor-test' - - // Logstash - implementation 'net.logstash.logback:logstash-logback-encoder:6.6' - - // Kafka Appender - implementation 'com.github.danielwegener:logback-kafka-appender:0.2.0-RC2' - - implementation 'javax.xml.bind:jaxb-api:2.3.1' -} - -test { - outputs.dir snippetsDir - useJUnitPlatform() -} - -asciidoctor { - inputs.dir snippetsDir - dependsOn test -} - -task createDocument(type: Copy) { - dependsOn asciidoctor - from file("build/asciidoc/html5/index.html") - into file("src/main/resources/static/docs") -} - -bootJar { - dependsOn createDocument - from("${asciidoctor.outputDir}/html5") { - into 'static/docs' - } -} - - diff --git a/s3proxy/docker/main/Dockerfile b/s3proxy/docker/main/Dockerfile deleted file mode 100644 index 02320bc06..000000000 --- a/s3proxy/docker/main/Dockerfile +++ /dev/null @@ -1,17 +0,0 @@ -FROM ubuntu:18.04 - -LABEL email="ssangyu123@gmail.com" -LABEL name="sakjung" -LABEL description="zzimkkong s3proxy application" - -RUN apt-get -y update -RUN apt-get install -y openjdk-11-jdk - -# run application -WORKDIR /home/ubuntu -COPY build/libs/s3proxy-0.0.1-SNAPSHOT.jar app.jar -RUN mkdir zzimkkong && mkdir zzimkkong/tmp - -EXPOSE 8080 - -ENTRYPOINT ["java", "-Dspring.profiles.active=prod", "-jar", "app.jar", "--spring.config.location=classpath:s3proxy-config/application-prod.yml"] diff --git a/s3proxy/gradle/wrapper/gradle-wrapper.jar b/s3proxy/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index 7454180f2..000000000 Binary files a/s3proxy/gradle/wrapper/gradle-wrapper.jar and /dev/null differ diff --git a/s3proxy/gradle/wrapper/gradle-wrapper.properties b/s3proxy/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index da9702f9e..000000000 --- a/s3proxy/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,5 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.8-bin.zip -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/s3proxy/gradlew b/s3proxy/gradlew deleted file mode 100755 index 744e882ed..000000000 --- a/s3proxy/gradlew +++ /dev/null @@ -1,185 +0,0 @@ -#!/usr/bin/env sh - -# -# Copyright 2015 the original author or authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -############################################################################## -## -## Gradle start up script for UN*X -## -############################################################################## - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" - -warn () { - echo "$*" -} - -die () { - echo - echo "$*" - echo - exit 1 -} - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MSYS* | MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." -fi - -# Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=`expr $i + 1` - done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac -fi - -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` - -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" - -exec "$JAVACMD" "$@" diff --git a/s3proxy/gradlew.bat b/s3proxy/gradlew.bat deleted file mode 100644 index ac1b06f93..000000000 --- a/s3proxy/gradlew.bat +++ /dev/null @@ -1,89 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/s3proxy/script/deploy.sh b/s3proxy/script/deploy.sh deleted file mode 100644 index 7ac72174a..000000000 --- a/s3proxy/script/deploy.sh +++ /dev/null @@ -1,20 +0,0 @@ -PROFILE=$1 - -JAR_FILE_NAME=s3proxy-0.0.1-SNAPSHOT.jar - -echo "Checking currently running process id..." -RUNNING_PROCESS_ID=$(pgrep -fl java | awk '{print $1}') - -if [ -z "$RUNNING_PROCESS_ID" ]; then - echo "No s3Proxy server is running." -else - echo "Killing process whose id is $RUNNING_PROCESS_ID" - kill -15 $RUNNING_PROCESS_ID - sleep 5 -fi - -echo "Running jar file..." -nohup java -jar -Dspring.profiles.active=$PROFILE $JAR_FILE_NAME > ~/nohup.out 2>&1 & - -CURRENT_PROCESS_ID=$(pgrep -fl java | awk '{print $1}') -echo "Application is running as pid: $CURRENT_PROCESS_ID" diff --git a/s3proxy/settings.gradle b/s3proxy/settings.gradle deleted file mode 100644 index 78517cd75..000000000 --- a/s3proxy/settings.gradle +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = 's3proxy' diff --git a/s3proxy/src/docs/asciidoc/index.adoc b/s3proxy/src/docs/asciidoc/index.adoc deleted file mode 100644 index d4d7b0d2b..000000000 --- a/s3proxy/src/docs/asciidoc/index.adoc +++ /dev/null @@ -1,24 +0,0 @@ -= ZZIMKKONG S3 Proxy Server Application API Document -:doctype: book -:icons: font -:source-highlighter: highlightjs -:toc: left -:toclevels: 3 -:sectlinks: - -== Multi-Part Upload - -=== Upload -==== Request -include::{snippets}/s3/post/path-parameters.adoc[] -include::{snippets}/s3/post/http-request.adoc[] - -==== Response -include::{snippets}/s3/post/http-response.adoc[] - -=== Delete -==== Request -include::{snippets}/s3/delete/path-parameters.adoc[] -include::{snippets}/s3/delete/http-request.adoc[] -==== Response -include::{snippets}/s3/delete/http-response.adoc[] diff --git a/s3proxy/src/main/java/com/woowacourse/s3proxy/S3proxyApplication.java b/s3proxy/src/main/java/com/woowacourse/s3proxy/S3proxyApplication.java deleted file mode 100644 index 1ff565504..000000000 --- a/s3proxy/src/main/java/com/woowacourse/s3proxy/S3proxyApplication.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.woowacourse.s3proxy; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication -public class S3proxyApplication { - - public static void main(String[] args) { - SpringApplication.run(S3proxyApplication.class, args); - } - -} diff --git a/s3proxy/src/main/java/com/woowacourse/s3proxy/config/AuthInterceptorConfig.java b/s3proxy/src/main/java/com/woowacourse/s3proxy/config/AuthInterceptorConfig.java deleted file mode 100644 index 0c285aac0..000000000 --- a/s3proxy/src/main/java/com/woowacourse/s3proxy/config/AuthInterceptorConfig.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.woowacourse.s3proxy.config; - -import com.woowacourse.s3proxy.infrastructure.AuthInterceptor; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Profile; -import org.springframework.context.annotation.PropertySource; - -@Configuration -@PropertySource("classpath:config/s3proxy.properties") -public class AuthInterceptorConfig { - @Bean(name = "authInterceptor") - @Profile("prod") - public AuthInterceptor authInterceptorProd( - @Value("${s3proxy.secret-key.prod}") String secretKey) { - return new AuthInterceptor(secretKey); - } - - @Bean(name = "authInterceptor") - @Profile({"dev", "local", "test"}) - public AuthInterceptor authInterceptorDev( - @Value("${s3proxy.secret-key.dev}") String secretKey) { - return new AuthInterceptor(secretKey); - } -} diff --git a/s3proxy/src/main/java/com/woowacourse/s3proxy/config/AuthenticationPrincipalConfig.java b/s3proxy/src/main/java/com/woowacourse/s3proxy/config/AuthenticationPrincipalConfig.java deleted file mode 100644 index 2fbca605b..000000000 --- a/s3proxy/src/main/java/com/woowacourse/s3proxy/config/AuthenticationPrincipalConfig.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.woowacourse.s3proxy.config; - -import com.woowacourse.s3proxy.infrastructure.AuthInterceptor; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.servlet.config.annotation.InterceptorRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -@Configuration -public class AuthenticationPrincipalConfig implements WebMvcConfigurer { - private final AuthInterceptor authInterceptor; - - public AuthenticationPrincipalConfig(AuthInterceptor authInterceptor) { - this.authInterceptor = authInterceptor; - } - - @Override - public void addInterceptors(InterceptorRegistry registry) { - registry.addInterceptor(authInterceptor) - .addPathPatterns("/api/storage/*"); - } -} diff --git a/s3proxy/src/main/java/com/woowacourse/s3proxy/config/MultipartConfig.java b/s3proxy/src/main/java/com/woowacourse/s3proxy/config/MultipartConfig.java deleted file mode 100644 index da2013334..000000000 --- a/s3proxy/src/main/java/com/woowacourse/s3proxy/config/MultipartConfig.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.woowacourse.s3proxy.config; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.multipart.commons.CommonsMultipartResolver; - -@Configuration -public class MultipartConfig { - @Bean(name = "multipartResolver") - public CommonsMultipartResolver multipartResolver() { - CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver(); - multipartResolver.setMaxUploadSize(1048576); // 1MB - return multipartResolver; - } -} diff --git a/s3proxy/src/main/java/com/woowacourse/s3proxy/config/StorageConfig.java b/s3proxy/src/main/java/com/woowacourse/s3proxy/config/StorageConfig.java deleted file mode 100644 index 6cd5f6262..000000000 --- a/s3proxy/src/main/java/com/woowacourse/s3proxy/config/StorageConfig.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.woowacourse.s3proxy.config; - -import com.amazonaws.services.s3.AmazonS3; -import com.amazonaws.services.s3.AmazonS3ClientBuilder; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Profile; - -@Configuration -public class StorageConfig { - - @Bean - @Profile({"prod", "dev"}) - public AmazonS3 amazonS3() { - return AmazonS3ClientBuilder - .standard() - .build(); - } - - @Bean(name = "amazonS3") - @Profile({"test", "local"}) - public AmazonS3 amazonS3Local(@Value("${cloud.aws.region.static}") String region) { - - return AmazonS3ClientBuilder - .standard() - .withRegion(region) - .build(); - } -} diff --git a/s3proxy/src/main/java/com/woowacourse/s3proxy/controller/ControllerAdvice.java b/s3proxy/src/main/java/com/woowacourse/s3proxy/controller/ControllerAdvice.java deleted file mode 100644 index 8e3c40efd..000000000 --- a/s3proxy/src/main/java/com/woowacourse/s3proxy/controller/ControllerAdvice.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.woowacourse.s3proxy.controller; - -import com.woowacourse.s3proxy.dto.ErrorResponse; -import com.woowacourse.s3proxy.exception.S3ProxyException; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.RestControllerAdvice; - -@Slf4j -@RestControllerAdvice -public class ControllerAdvice { - @ExceptionHandler(S3ProxyException.class) - public ResponseEntity s3ProxyExceptionHandler(final S3ProxyException exception) { - log.warn(exception.getMessage(), exception); - return ResponseEntity - .status(exception.getStatus()) - .body(ErrorResponse.from(exception)); - } -} diff --git a/s3proxy/src/main/java/com/woowacourse/s3proxy/controller/S3ProxyController.java b/s3proxy/src/main/java/com/woowacourse/s3proxy/controller/S3ProxyController.java deleted file mode 100644 index d1337db4a..000000000 --- a/s3proxy/src/main/java/com/woowacourse/s3proxy/controller/S3ProxyController.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.woowacourse.s3proxy.controller; - -import com.woowacourse.s3proxy.service.S3Service; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.multipart.MultipartFile; - -import java.net.URI; - -@Slf4j -@RestController -@RequestMapping("/api/storage") -public class S3ProxyController { - private final S3Service s3Service; - - public S3ProxyController(final S3Service s3Service) { - this.s3Service = s3Service; - } - - @PostMapping("/{directoryPath}") - public ResponseEntity submit( - @RequestParam("file") MultipartFile file, - @PathVariable("directoryPath") String directoryPath) { - URI location = s3Service.upload(file, directoryPath); - return ResponseEntity.created(location).build(); - } - - @DeleteMapping("/{directoryPath}/{fileName}") - public ResponseEntity delete( - @PathVariable("directoryPath") String directoryPath, - @PathVariable("fileName") String fileName) { - s3Service.delete(directoryPath, fileName); - return ResponseEntity.noContent().build(); - } -} diff --git a/s3proxy/src/main/java/com/woowacourse/s3proxy/dto/ErrorResponse.java b/s3proxy/src/main/java/com/woowacourse/s3proxy/dto/ErrorResponse.java deleted file mode 100644 index 41582fe6c..000000000 --- a/s3proxy/src/main/java/com/woowacourse/s3proxy/dto/ErrorResponse.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.woowacourse.s3proxy.dto; - -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Getter -@NoArgsConstructor -public class ErrorResponse { - private String message; - - protected ErrorResponse(final String message) { - this.message = message; - } - - public static ErrorResponse from(final RuntimeException exception) { - return new ErrorResponse(exception.getMessage()); - } - -} diff --git a/s3proxy/src/main/java/com/woowacourse/s3proxy/exception/AuthorizationHeaderUninvolvedException.java b/s3proxy/src/main/java/com/woowacourse/s3proxy/exception/AuthorizationHeaderUninvolvedException.java deleted file mode 100644 index c82f87d68..000000000 --- a/s3proxy/src/main/java/com/woowacourse/s3proxy/exception/AuthorizationHeaderUninvolvedException.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.woowacourse.s3proxy.exception; - -import org.springframework.http.HttpStatus; - -public class AuthorizationHeaderUninvolvedException extends S3ProxyException { - private static final String MESSAGE = "인가에 실패했습니다."; - public AuthorizationHeaderUninvolvedException() { - super(MESSAGE, HttpStatus.UNAUTHORIZED); - } -} diff --git a/s3proxy/src/main/java/com/woowacourse/s3proxy/exception/S3ProxyException.java b/s3proxy/src/main/java/com/woowacourse/s3proxy/exception/S3ProxyException.java deleted file mode 100644 index 5b783b2c6..000000000 --- a/s3proxy/src/main/java/com/woowacourse/s3proxy/exception/S3ProxyException.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.woowacourse.s3proxy.exception; - -import lombok.Getter; -import org.springframework.http.HttpStatus; - -@Getter -public abstract class S3ProxyException extends RuntimeException { - protected final HttpStatus status; - - public S3ProxyException(String message, HttpStatus status) { - super(message); - this.status = status; - } - - public S3ProxyException(String message, Throwable cause, HttpStatus status) { - super(message, cause); - this.status = status; - } -} diff --git a/s3proxy/src/main/java/com/woowacourse/s3proxy/exception/S3UploadException.java b/s3proxy/src/main/java/com/woowacourse/s3proxy/exception/S3UploadException.java deleted file mode 100644 index 6084d6379..000000000 --- a/s3proxy/src/main/java/com/woowacourse/s3proxy/exception/S3UploadException.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.woowacourse.s3proxy.exception; - -import org.springframework.http.HttpStatus; - -public class S3UploadException extends S3ProxyException { - private static final String MESSAGE = "이미지 버킷 업로드에 실패했습니다."; - - public S3UploadException(final Throwable cause) { - super(MESSAGE, cause, HttpStatus.INTERNAL_SERVER_ERROR); - } -} - diff --git a/s3proxy/src/main/java/com/woowacourse/s3proxy/exception/UnsupportedFileExtensionException.java b/s3proxy/src/main/java/com/woowacourse/s3proxy/exception/UnsupportedFileExtensionException.java deleted file mode 100644 index d8176c0d0..000000000 --- a/s3proxy/src/main/java/com/woowacourse/s3proxy/exception/UnsupportedFileExtensionException.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.woowacourse.s3proxy.exception; - -import org.springframework.http.HttpStatus; - -public class UnsupportedFileExtensionException extends S3ProxyException { - private static final String MESSAGE = "지원하지 않는 확장자입니다."; - - public UnsupportedFileExtensionException() { - super(MESSAGE, HttpStatus.BAD_REQUEST); - } -} diff --git a/s3proxy/src/main/java/com/woowacourse/s3proxy/infrastructure/AuthInterceptor.java b/s3proxy/src/main/java/com/woowacourse/s3proxy/infrastructure/AuthInterceptor.java deleted file mode 100644 index 39f9157f5..000000000 --- a/s3proxy/src/main/java/com/woowacourse/s3proxy/infrastructure/AuthInterceptor.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.woowacourse.s3proxy.infrastructure; - -import com.woowacourse.s3proxy.exception.AuthorizationHeaderUninvolvedException; -import org.springframework.http.HttpMethod; -import org.springframework.web.servlet.HandlerInterceptor; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - - -public class AuthInterceptor implements HandlerInterceptor { - private final String secretKey; - - public AuthInterceptor(String secretKey) { - this.secretKey = secretKey; - } - - @Override - public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { - if (isPreflight(request)) { - return true; - } - - String secretKey = AuthorizationExtractor.extractAccessToken(request); - if (secretKey.equals(this.secretKey)) { - return true; - } - - throw new AuthorizationHeaderUninvolvedException(); - } - - private boolean isPreflight(HttpServletRequest request) { - return request.getMethod().equals(HttpMethod.OPTIONS.toString()); - } -} diff --git a/s3proxy/src/main/java/com/woowacourse/s3proxy/infrastructure/AuthorizationExtractor.java b/s3proxy/src/main/java/com/woowacourse/s3proxy/infrastructure/AuthorizationExtractor.java deleted file mode 100644 index eb2abdc7e..000000000 --- a/s3proxy/src/main/java/com/woowacourse/s3proxy/infrastructure/AuthorizationExtractor.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.woowacourse.s3proxy.infrastructure; - -import com.woowacourse.s3proxy.exception.AuthorizationHeaderUninvolvedException; - -import javax.servlet.http.HttpServletRequest; -import java.util.Enumeration; - -public class AuthorizationExtractor { - private static final String AUTHORIZATION_HEADER_KEY = "Authorization"; - - private AuthorizationExtractor() { - } - public static String extractAccessToken(HttpServletRequest request) { - Enumeration headers = request.getHeaders(AUTHORIZATION_HEADER_KEY); - if (headers.hasMoreElements()) { - return headers.nextElement(); - } - throw new AuthorizationHeaderUninvolvedException(); - } -} diff --git a/s3proxy/src/main/java/com/woowacourse/s3proxy/infrastructure/S3Uploader.java b/s3proxy/src/main/java/com/woowacourse/s3proxy/infrastructure/S3Uploader.java deleted file mode 100644 index cb729b248..000000000 --- a/s3proxy/src/main/java/com/woowacourse/s3proxy/infrastructure/S3Uploader.java +++ /dev/null @@ -1,82 +0,0 @@ -package com.woowacourse.s3proxy.infrastructure; - -import com.amazonaws.AmazonClientException; -import com.amazonaws.services.s3.AmazonS3; -import com.amazonaws.services.s3.model.DeleteObjectRequest; -import com.amazonaws.services.s3.model.ObjectMetadata; -import com.woowacourse.s3proxy.exception.S3UploadException; -import com.woowacourse.s3proxy.exception.UnsupportedFileExtensionException; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.MediaType; -import org.springframework.http.MediaTypeFactory; -import org.springframework.stereotype.Component; -import org.springframework.web.multipart.MultipartFile; - -import java.io.IOException; -import java.io.InputStream; -import java.net.URI; -import java.net.URL; - -@Component -public class S3Uploader { - public static final String PATH_DELIMITER = "/"; - private static final String S3_HOST_URL_SUFFIX = "amazonaws.com"; - private static final int RESOURCE_URL_INDEX = 1; - - private final AmazonS3 amazonS3; - private final String bucketName; - private final String cloudFrontUrl; - - public S3Uploader( - final AmazonS3 amazonS3, - @Value("${aws.s3.bucket-name}") final String bucketName, - @Value("${aws.s3.mapped-cloudfront}") final String cloudFrontUrl) { - this.amazonS3 = amazonS3; - this.bucketName = bucketName; - this.cloudFrontUrl = cloudFrontUrl; - } - - public URI upload(MultipartFile multipartFile, String directoryPath) { - ObjectMetadata objectMetadata = createObjectMetadata(multipartFile); - - String fileName = multipartFile.getOriginalFilename(); - String fileFullPath = generateFullPath(directoryPath, fileName); - - try(InputStream inputStream = multipartFile.getInputStream()) { - - amazonS3.putObject(this.bucketName, fileFullPath, inputStream, objectMetadata); - - URL fileUrl = amazonS3.getUrl(this.bucketName, fileFullPath); - - return makeAccessibleUrl(fileUrl, cloudFrontUrl); - } catch (AmazonClientException | IOException exception) { - throw new S3UploadException(exception); - } - } - - private ObjectMetadata createObjectMetadata(MultipartFile multipartFile) { - String filename = multipartFile.getOriginalFilename(); - MediaType mediaType = MediaTypeFactory.getMediaType(filename) - .orElseThrow(UnsupportedFileExtensionException::new); - - ObjectMetadata objectMetadata = new ObjectMetadata(); - objectMetadata.setContentType(mediaType.toString()); - objectMetadata.setContentLength(multipartFile.getSize()); - - return objectMetadata; - } - - private String generateFullPath(String directoryPath, String fileName) { - return directoryPath + PATH_DELIMITER + fileName; - } - - private URI makeAccessibleUrl(final URL origin, final String cloudFrontUrl) { - String uriWithoutHost = origin.toString().split(S3_HOST_URL_SUFFIX)[RESOURCE_URL_INDEX]; - String replacedUrl = cloudFrontUrl + uriWithoutHost; - return URI.create(replacedUrl); - } - - public void delete(final String fullPathOfFile) { - amazonS3.deleteObject(new DeleteObjectRequest(bucketName, fullPathOfFile)); - } -} diff --git a/s3proxy/src/main/java/com/woowacourse/s3proxy/service/S3Service.java b/s3proxy/src/main/java/com/woowacourse/s3proxy/service/S3Service.java deleted file mode 100644 index ab41ed023..000000000 --- a/s3proxy/src/main/java/com/woowacourse/s3proxy/service/S3Service.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.woowacourse.s3proxy.service; - -import com.woowacourse.s3proxy.infrastructure.S3Uploader; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; -import org.springframework.web.multipart.MultipartFile; - -import java.net.URI; - -@Slf4j -@Service -public class S3Service { - private final S3Uploader s3Uploader; - - public S3Service(final S3Uploader s3Uploader) { - this.s3Uploader = s3Uploader; - } - - public URI upload(MultipartFile multipartFile, String directoryPath) { - return s3Uploader.upload(multipartFile, directoryPath); - } - - public void delete(String directoryPath, String fileName) { - s3Uploader.delete(directoryPath + S3Uploader.PATH_DELIMITER + fileName); - } -} diff --git a/s3proxy/src/main/resources/appenders/console-appender.xml b/s3proxy/src/main/resources/appenders/console-appender.xml deleted file mode 100644 index a9c4fb53c..000000000 --- a/s3proxy/src/main/resources/appenders/console-appender.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - ${CONSOLE_LOG_PATTERN} - - - - diff --git a/s3proxy/src/main/resources/appenders/file-appender-debug.xml b/s3proxy/src/main/resources/appenders/file-appender-debug.xml deleted file mode 100644 index d46254437..000000000 --- a/s3proxy/src/main/resources/appenders/file-appender-debug.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - ${FILE_PATH}/${DATE_FORMAT}/debug/debug.log - - - DEBUG - ACCEPT - DENY - - - - ${FILE_LOG_PATTERN} - - - - ${FILE_PATH}/%d{yyyy-MM-dd}/debug/debug_%i.log - 100MB - 100 - 10GB - - - - diff --git a/s3proxy/src/main/resources/appenders/file-appender-error.xml b/s3proxy/src/main/resources/appenders/file-appender-error.xml deleted file mode 100644 index 1c6d064dc..000000000 --- a/s3proxy/src/main/resources/appenders/file-appender-error.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - ${FILE_PATH}/${DATE_FORMAT}/error/error.log - - - ERROR - ACCEPT - DENY - - - - ${FILE_LOG_PATTERN} - - - - ${FILE_PATH}/%d{yyyy-MM-dd}/error/error_%i.log - 100MB - 100 - 10GB - - - - diff --git a/s3proxy/src/main/resources/appenders/file-appender-info.xml b/s3proxy/src/main/resources/appenders/file-appender-info.xml deleted file mode 100644 index abd5116ff..000000000 --- a/s3proxy/src/main/resources/appenders/file-appender-info.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - ${FILE_PATH}/${DATE_FORMAT}/info/info.log - - - INFO - ACCEPT - DENY - - - - ${FILE_LOG_PATTERN} - - - - ${FILE_PATH}/%d{yyyy-MM-dd}/info/info_%i.log - 100MB - 100 - 10GB - - - - diff --git a/s3proxy/src/main/resources/appenders/file-appender-trace.xml b/s3proxy/src/main/resources/appenders/file-appender-trace.xml deleted file mode 100644 index 6f24e2375..000000000 --- a/s3proxy/src/main/resources/appenders/file-appender-trace.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - ${FILE_PATH}/${DATE_FORMAT}/trace/trace.log - - - TRACE - ACCEPT - DENY - - - - ${FILE_LOG_PATTERN} - - - - ${FILE_PATH}/%d{yyyy-MM-dd}/trace/trace_%i.log - 100MB - 100 - 10GB - - - - diff --git a/s3proxy/src/main/resources/appenders/file-appender-warn.xml b/s3proxy/src/main/resources/appenders/file-appender-warn.xml deleted file mode 100644 index 9908dd63d..000000000 --- a/s3proxy/src/main/resources/appenders/file-appender-warn.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - ${FILE_PATH}/${DATE_FORMAT}/warn/warn.log - - - WARN - ACCEPT - DENY - - - - ${FILE_LOG_PATTERN} - - - - ${FILE_PATH}/%d{yyyy-MM-dd}/warn/warn_%i.log - 100MB - 100 - 10GB - - - - diff --git a/s3proxy/src/main/resources/appenders/kafka-appender-dev.xml b/s3proxy/src/main/resources/appenders/kafka-appender-dev.xml deleted file mode 100644 index 1f771dfd9..000000000 --- a/s3proxy/src/main/resources/appenders/kafka-appender-dev.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - logstash-s3proxy - - - - bootstrap.servers=192.168.2.139:8080,192.168.2.44:8080,192.168.2.24:8080 - - diff --git a/s3proxy/src/main/resources/appenders/kafka-appender-prod.xml b/s3proxy/src/main/resources/appenders/kafka-appender-prod.xml deleted file mode 100644 index 5a139a88a..000000000 --- a/s3proxy/src/main/resources/appenders/kafka-appender-prod.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - logstash-s3proxy - - - - bootstrap.servers=192.168.2.242:8080,192.168.2.150:8080,192.168.2.152:8080 - - diff --git a/s3proxy/src/main/resources/application-dev.yml b/s3proxy/src/main/resources/application-dev.yml deleted file mode 100644 index 4d1ee6e7c..000000000 --- a/s3proxy/src/main/resources/application-dev.yml +++ /dev/null @@ -1,9 +0,0 @@ -cloud: - aws: - stack: - auto: false -aws: - s3: - bucket-name: zzimkkong-thumbnail-dev - mapped-cloudfront: https://d3tdpsdxqmqd52.cloudfront.net - region: ap-northeast-2 diff --git a/s3proxy/src/main/resources/application-local.yml b/s3proxy/src/main/resources/application-local.yml deleted file mode 100644 index bbbbb87dc..000000000 --- a/s3proxy/src/main/resources/application-local.yml +++ /dev/null @@ -1,14 +0,0 @@ -cloud: - aws: - stack: - auto: false - credentials: - instance-profile: false - region: - static: ap-northeast-2 - -aws: - s3: - bucket-name: zzimkkong-personal - mapped-cloudfront: https://zzimkkong-personal.s3.ap-northeast-2.amazonaws.com - region: ap-northeast-2 diff --git a/s3proxy/src/main/resources/application-test.yml b/s3proxy/src/main/resources/application-test.yml deleted file mode 100644 index bbbbb87dc..000000000 --- a/s3proxy/src/main/resources/application-test.yml +++ /dev/null @@ -1,14 +0,0 @@ -cloud: - aws: - stack: - auto: false - credentials: - instance-profile: false - region: - static: ap-northeast-2 - -aws: - s3: - bucket-name: zzimkkong-personal - mapped-cloudfront: https://zzimkkong-personal.s3.ap-northeast-2.amazonaws.com - region: ap-northeast-2 diff --git a/s3proxy/src/main/resources/application.yml b/s3proxy/src/main/resources/application.yml deleted file mode 100644 index d74c444c1..000000000 --- a/s3proxy/src/main/resources/application.yml +++ /dev/null @@ -1,3 +0,0 @@ -spring: - profiles: - active: local diff --git a/s3proxy/src/main/resources/config b/s3proxy/src/main/resources/config deleted file mode 160000 index a2d168ac3..000000000 --- a/s3proxy/src/main/resources/config +++ /dev/null @@ -1 +0,0 @@ -Subproject commit a2d168ac3adfb324408ad8afa2bd3e4d3ac35220 diff --git a/s3proxy/src/main/resources/logback-spring.xml b/s3proxy/src/main/resources/logback-spring.xml deleted file mode 100644 index a23731343..000000000 --- a/s3proxy/src/main/resources/logback-spring.xml +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/s3proxy/src/main/resources/s3proxy-config b/s3proxy/src/main/resources/s3proxy-config deleted file mode 160000 index e33c8a691..000000000 --- a/s3proxy/src/main/resources/s3proxy-config +++ /dev/null @@ -1 +0,0 @@ -Subproject commit e33c8a691c32dfdc9e8f5ce4b33c753363b8a054 diff --git a/s3proxy/src/test/java/com/woowacourse/s3proxy/Constants.java b/s3proxy/src/test/java/com/woowacourse/s3proxy/Constants.java deleted file mode 100644 index 439c4ae2e..000000000 --- a/s3proxy/src/test/java/com/woowacourse/s3proxy/Constants.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.woowacourse.s3proxy; - -public class Constants { - public static final String LUTHER_IMAGE_URI_CLOUDFRONT = "https://d3tdpsdxqmqd52.cloudfront.net/testdir/luther.png"; - public static final String LUTHER_IMAGE_URI_S3 = "https://zzimkkong-thumbnail-dev.s3.ap-northeast-2.amazonaws.com/testdir/luther.png"; -} diff --git a/s3proxy/src/test/java/com/woowacourse/s3proxy/DocumentUtils.java b/s3proxy/src/test/java/com/woowacourse/s3proxy/DocumentUtils.java deleted file mode 100644 index 4503786f0..000000000 --- a/s3proxy/src/test/java/com/woowacourse/s3proxy/DocumentUtils.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.woowacourse.s3proxy; - -import io.restassured.specification.RequestSpecification; -import org.springframework.restdocs.operation.preprocess.OperationRequestPreprocessor; -import org.springframework.restdocs.operation.preprocess.OperationResponsePreprocessor; - -import static org.springframework.restdocs.operation.preprocess.Preprocessors.*; -import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint; - -public final class DocumentUtils { - private static RequestSpecification preConfiguredRequestSpecification; - - private DocumentUtils() { - } - - public static RequestSpecification getRequestSpecification() { - return preConfiguredRequestSpecification; - } - - public static void setRequestSpecification(RequestSpecification preConfiguredRequestSpecification) { - DocumentUtils.preConfiguredRequestSpecification = preConfiguredRequestSpecification; - } - - public static OperationRequestPreprocessor getRequestPreprocessor() { - return preprocessRequest(prettyPrint()); - } - - public static OperationResponsePreprocessor getResponsePreprocessor() { - return preprocessResponse(prettyPrint()); - } -} diff --git a/s3proxy/src/test/java/com/woowacourse/s3proxy/controller/AcceptanceTest.java b/s3proxy/src/test/java/com/woowacourse/s3proxy/controller/AcceptanceTest.java deleted file mode 100644 index d927f021f..000000000 --- a/s3proxy/src/test/java/com/woowacourse/s3proxy/controller/AcceptanceTest.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.woowacourse.s3proxy.controller; - -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.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.web.server.LocalServerPort; -import org.springframework.restdocs.RestDocumentationContextProvider; -import org.springframework.restdocs.RestDocumentationExtension; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.junit.jupiter.SpringExtension; - -import static com.woowacourse.s3proxy.DocumentUtils.setRequestSpecification; -import static org.springframework.restdocs.restassured3.RestAssuredRestDocumentation.documentationConfiguration; - -@ExtendWith({RestDocumentationExtension.class, SpringExtension.class}) -@AutoConfigureRestDocs -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@ActiveProfiles("test") -public class AcceptanceTest { - @LocalServerPort - int port; - - @BeforeEach - void setUp(RestDocumentationContextProvider restDocumentation) { - RestAssured.port = this.port; - RequestSpecification spec = new RequestSpecBuilder() - .addFilter(documentationConfiguration(restDocumentation)) - .build(); - setRequestSpecification(spec); - } -} - - diff --git a/s3proxy/src/test/java/com/woowacourse/s3proxy/controller/S3ProxyControllerTest.java b/s3proxy/src/test/java/com/woowacourse/s3proxy/controller/S3ProxyControllerTest.java deleted file mode 100644 index eccc5aa55..000000000 --- a/s3proxy/src/test/java/com/woowacourse/s3proxy/controller/S3ProxyControllerTest.java +++ /dev/null @@ -1,102 +0,0 @@ -package com.woowacourse.s3proxy.controller; - -import com.woowacourse.s3proxy.infrastructure.S3Uploader; -import io.restassured.RestAssured; -import io.restassured.response.ExtractableResponse; -import io.restassured.response.Response; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.context.annotation.PropertySource; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.web.multipart.MultipartFile; - -import java.io.File; -import java.net.URI; - -import static com.woowacourse.s3proxy.Constants.LUTHER_IMAGE_URI_CLOUDFRONT; -import static com.woowacourse.s3proxy.DocumentUtils.*; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.BDDMockito.given; -import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; -import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; -import static org.springframework.restdocs.restassured3.RestAssuredRestDocumentation.document; - -@PropertySource("classpath:config/s3proxy.properties") -class S3ProxyControllerTest extends AcceptanceTest { - @MockBean - S3Uploader s3Uploader; - - @Value("${s3proxy.secret-key.prod}") - private String secretKey; - - @BeforeEach - void setUp() { - given(s3Uploader.upload(any(MultipartFile.class), anyString())) - .willReturn(URI.create(LUTHER_IMAGE_URI_CLOUDFRONT)); - } - - @Test - @DisplayName("스토리지에 파일을 업로드한다.") - void upload() { - // given - String directory = "testdir"; - String filePath = getClass().getClassLoader().getResource("luther.png").getFile(); - File file = new File(filePath); - - // when - ExtractableResponse response = uploadFile(directory, file); - - // then - assertThat(response.statusCode()).isEqualTo(HttpStatus.CREATED.value()); - } - - @Test - @DisplayName("스토리지의 파일을 삭제한다.") - void delete() { - // given - String fileName = "filename.png"; - String directory = "testdir"; - - // when - ExtractableResponse response = deleteFile(directory, fileName); - - // then - assertThat(response.statusCode()).isEqualTo(HttpStatus.NO_CONTENT.value()); - } - - private ExtractableResponse uploadFile(String directory, File file) { - return RestAssured.given(getRequestSpecification()) - .log().all() - .filter(document( - "s3/post", getRequestPreprocessor(), getResponsePreprocessor(), - pathParameters(parameterWithName("directory").description("저장하고자 하는 스토리지 내의 디렉토리 이름")))) - .contentType(MediaType.MULTIPART_FORM_DATA_VALUE) - .multiPart("file", file) - .header(HttpHeaders.AUTHORIZATION, secretKey) - .pathParam("directory", directory) - .when().post("/api/storage/{directory}") - .then().log().all().extract(); - } - - private ExtractableResponse deleteFile(String directory, String fileName) { - return RestAssured.given(getRequestSpecification()) - .log().all() - .filter(document("s3/delete", getRequestPreprocessor(), getResponsePreprocessor(), - pathParameters( - parameterWithName("directory").description("저장하고자 하는 스토리지 내의 디렉토리 이름"), - parameterWithName("filename").description("삭제하고자 하는 파일의 이름(확장자 포함)")))) - .when() - .header(HttpHeaders.AUTHORIZATION, secretKey) - .pathParam("directory", directory) - .pathParam("filename", fileName) - .delete("/api/storage/{directory}/{filename}") - .then().log().all().extract(); - } -} diff --git a/s3proxy/src/test/java/com/woowacourse/s3proxy/infrastructure/S3UploaderTest.java b/s3proxy/src/test/java/com/woowacourse/s3proxy/infrastructure/S3UploaderTest.java deleted file mode 100644 index 7bf98f5a2..000000000 --- a/s3proxy/src/test/java/com/woowacourse/s3proxy/infrastructure/S3UploaderTest.java +++ /dev/null @@ -1,71 +0,0 @@ -package com.woowacourse.s3proxy.infrastructure; - -import com.amazonaws.services.s3.AmazonS3; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.web.multipart.MultipartFile; - -import java.io.IOException; -import java.io.InputStream; -import java.net.URI; -import java.net.URL; -import java.util.Random; - -import static com.woowacourse.s3proxy.Constants.LUTHER_IMAGE_URI_S3; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; - -class S3UploaderTest { - - @Test - @DisplayName("멀티 파트 파일을 업로드하고 URI를 얻어, 접근 가능한 URI(CloudFront)로 변경해 리턴한다.") - void upload() throws IOException { - // given - AmazonS3 amazonS3 = mock(AmazonS3.class); - String bucketName = "testBucketName"; - String cloudFrontUrl = "https://expectedCloudFrontUrl.net"; - - S3Uploader s3Uploader = new S3Uploader(amazonS3, bucketName, cloudFrontUrl); - - given(amazonS3.getUrl(anyString(), anyString())) - .willReturn(new URL(LUTHER_IMAGE_URI_S3)); - - MultipartFile mockMultipartFile = mock(MultipartFile.class); - given(mockMultipartFile.getOriginalFilename()) - .willReturn("somePngFileName.png"); - given(mockMultipartFile.getSize()) - .willReturn(new Random().nextLong()); - given(mockMultipartFile.getInputStream()) - .willReturn(mock(InputStream.class)); - - // when - String directoryName = "testDirectoryName"; - URI actual = s3Uploader.upload(mockMultipartFile, directoryName); - - String resourceUriWithoutHost = LUTHER_IMAGE_URI_S3.split("amazonaws.com")[1]; - String expectedUri = cloudFrontUrl + resourceUriWithoutHost; - - // then - assertThat(actual).isEqualTo(URI.create(expectedUri)); - } - - @Test - @DisplayName("경로를 입력받아 파일을 삭제할 수 있다.") - void delete() { - // given - AmazonS3 amazonS3 = mock(AmazonS3.class); - String bucketName = "testBucketName"; - String cloudFrontUrl = "https://testCloudFrontUrl.net"; - - S3Uploader s3Uploader = new S3Uploader(amazonS3, bucketName, cloudFrontUrl); - - String fileName = "filename.png"; - String directory = "directoryName"; - - // when, then - assertDoesNotThrow(() -> s3Uploader.delete(directory + "/" + fileName)); - } -} diff --git a/s3proxy/src/test/java/com/woowacourse/s3proxy/service/S3ServiceTest.java b/s3proxy/src/test/java/com/woowacourse/s3proxy/service/S3ServiceTest.java deleted file mode 100644 index 7e5c8de29..000000000 --- a/s3proxy/src/test/java/com/woowacourse/s3proxy/service/S3ServiceTest.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.woowacourse.s3proxy.service; - -import com.woowacourse.s3proxy.infrastructure.S3Uploader; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.web.multipart.MultipartFile; - -import java.net.URI; - -import static com.woowacourse.s3proxy.Constants.LUTHER_IMAGE_URI_CLOUDFRONT; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; - -class S3ServiceTest extends ServiceTest { - @MockBean - private S3Uploader s3Uploader; - - @Autowired - private S3Service s3Service; - - @BeforeEach - void mockingS3Uploader() { - given(s3Uploader.upload(any(MultipartFile.class), anyString())) - .willReturn(URI.create(LUTHER_IMAGE_URI_CLOUDFRONT)); - } - - @Test - @DisplayName("멀티파트로 전송된 파일을 요청한 디렉토리에 업로드한다.") - void upload() { - // given - MultipartFile multipartFile = mock(MultipartFile.class); - - // when - URI actual = s3Service.upload(multipartFile, "thumbnails"); - - // then - assertThat(actual).isEqualTo(URI.create(LUTHER_IMAGE_URI_CLOUDFRONT)); - } - - @Test - @DisplayName("스토리지의 파일을 삭제할 수 있다.") - void delete() { - // given - String fileName = "filename.png"; - String directory = "directoryName"; - - // when, then - assertDoesNotThrow(() -> s3Service.delete(directory, fileName)); - } -} diff --git a/s3proxy/src/test/java/com/woowacourse/s3proxy/service/ServiceTest.java b/s3proxy/src/test/java/com/woowacourse/s3proxy/service/ServiceTest.java deleted file mode 100644 index e6bbd09a1..000000000 --- a/s3proxy/src/test/java/com/woowacourse/s3proxy/service/ServiceTest.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.woowacourse.s3proxy.service; - -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.ActiveProfiles; - -@SpringBootTest -@ActiveProfiles("test") -public class ServiceTest { -} diff --git a/s3proxy/src/test/resources/luther.png b/s3proxy/src/test/resources/luther.png deleted file mode 100644 index c2eac157e..000000000 Binary files a/s3proxy/src/test/resources/luther.png and /dev/null differ