diff --git a/.gitignore b/.gitignore index 0caf866b0..1da26a69c 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,6 @@ out/ ### Mac OS ### .DS_Store + +### secret-key 설정 +src/main/resources/application-secret.properties diff --git a/README.md b/README.md index b2202f113..162e2ffe2 100644 --- a/README.md +++ b/README.md @@ -1 +1,44 @@ -# spring-gift-point \ No newline at end of file +# spring-gift-point + +# 0단계 + +## API 명세서 +https://impossible-repair-22e.notion.site/57ec013f9424421eb2317b11a2b9a29c?v=f3fe7340ebae425bbfa70db78123a663 + +# 1단계 + +## 구현할 기능 목록 +- [ ] 작성한 API 문서를 기반으로 팀 내에서 지금까지 만든 API를 검토하고 통일하여 변경 사항을 반영 + - [x] 일반 회원가입 + - [x] 일반 로그인 + - [x] 모든 상품 조회 + - [x] 특정 제품의 옵션 조회 + - [x] 특정 카테고리별 상품 목록조회 + - [x] 모든 카테고리 조회 + - [x] 위시리스트에 추가 + - [x] 위시리스트에서 삭제 + - [x] 로그인한 회원의 위시리스트 조회 + - [x] 주문 생성 + +# 2단계 + +## 구현할 기능 목록 +- [x] API 오류 확인 + - [x] wish + - [x] product 카테고리별 상품 불러오는 api endpoint 경로 오류 수정 +- [x] 배포 자동화 + - [x] 배포 스크립트 작성 + - [x] 웹을 서버에 배포하기 위한 셀 스크립트 작성 + - [x] 스크립트는 현재 실행 중인 JAR 파일을 종료하고, 새로운 JAR 파일을 복사하여 실행하는 기능을 포함 + - https://www.notion.so/18cdfba8d1e44daa978eb1c7e7ee03d9 + +- [x] 보안 문제 + - [x] JWT를 사용한 인증에 문제가 없는지 프론트와 연결하면서 테스트 + +- [x] cors 에러 해결 + - [x] 전역 CORS 설정 : WebConfig에서 cors에러 해결 + - [ ] ~~특정 controller에서 CORS 설정~~ + - [ ] ~~Spring Security에서 CORS 설정~~ + - [ ] ~~application에서 CORS 설정 사용~~ + +- [x] CORS 테스트 구현 \ No newline at end of file diff --git a/build.gradle b/build.gradle index df7db9334..ba7e80e37 100644 --- a/build.gradle +++ b/build.gradle @@ -22,8 +22,27 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.springframework.boot:spring-boot-starter-web' runtimeOnly 'com.h2database:h2' + runtimeOnly 'com.mysql:mysql-connector-j' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + implementation 'org.springframework.boot:spring-boot-starter-validation:2.7.3' + + // JWT 관련 의존성 + compileOnly 'io.jsonwebtoken:jjwt-api:0.11.2' + runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.2' + runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.2' + + // JPA + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + + // 보안 관련 의존성 + //implementation 'org.springframework.boot:spring-boot-starter-security' + //implementation 'org.springframework.security:spring-security-test' + + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0' + + // Json + implementation group: 'org.json', name: 'json', version: '20090211' } tasks.named('test') { diff --git a/data/test.mv.db b/data/test.mv.db new file mode 100644 index 000000000..83ff339a0 Binary files /dev/null and b/data/test.mv.db differ diff --git a/data/test.trace.db b/data/test.trace.db new file mode 100644 index 000000000..f17ccc23d --- /dev/null +++ b/data/test.trace.db @@ -0,0 +1,300 @@ +2024-08-01 04:48:27.040702+09:00 jdbc[13]: exception +org.h2.jdbc.JdbcSQLSyntaxErrorException: Column "IMAGEURL" not found; SQL statement: +-- Product 테이블에 더미 데이터 삽입 +INSERT INTO product (name, price, imageUrl, category_id) VALUES +('Smartphone', 699, 'http://example.com/smartphone.jpg', 1), +('Laptop', 999, 'http://example.com/laptop.jpg', 1), +('T-Shirt', 19, 'http://example.com/tshirt.jpg', 2), +('Coffee Maker', 49, 'http://example.com/coffeemaker.jpg', 3) [42122-224] + at org.h2.message.DbException.getJdbcSQLException(DbException.java:514) + at org.h2.message.DbException.getJdbcSQLException(DbException.java:489) + at org.h2.message.DbException.get(DbException.java:223) + at org.h2.message.DbException.get(DbException.java:199) + at org.h2.table.Table.getColumn(Table.java:759) + at org.h2.command.Parser.parseColumn(Parser.java:1190) + at org.h2.command.Parser.parseColumnList(Parser.java:1175) + at org.h2.command.Parser.parseInsert(Parser.java:1549) + at org.h2.command.Parser.parsePrepared(Parser.java:719) + at org.h2.command.Parser.parse(Parser.java:592) + at org.h2.command.Parser.parse(Parser.java:564) + at org.h2.command.Parser.prepareCommand(Parser.java:483) + at org.h2.engine.SessionLocal.prepareLocal(SessionLocal.java:639) + at org.h2.engine.SessionLocal.prepareCommand(SessionLocal.java:559) + at org.h2.jdbc.JdbcConnection.prepareCommand(JdbcConnection.java:1166) + at org.h2.jdbc.JdbcStatement.executeInternal(JdbcStatement.java:245) + at org.h2.jdbc.JdbcStatement.execute(JdbcStatement.java:231) + at org.h2.server.web.WebApp.getResult(WebApp.java:1345) + at org.h2.server.web.WebApp.query(WebApp.java:1143) + at org.h2.server.web.WebApp.query(WebApp.java:1119) + at org.h2.server.web.WebApp.process(WebApp.java:245) + at org.h2.server.web.WebApp.processRequest(WebApp.java:177) + at org.h2.server.web.JakartaWebServlet.doGet(JakartaWebServlet.java:129) + at org.h2.server.web.JakartaWebServlet.doPost(JakartaWebServlet.java:166) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:590) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) + at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) + at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:482) + at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115) + at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) + at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) + at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344) + at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:389) + at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) + at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:904) + at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1741) + at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) + at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1190) + at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) + at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) + at java.base/java.lang.Thread.run(Thread.java:1583) +2024-08-01 04:49:22.693481+09:00 jdbc[13]: exception +org.h2.jdbc.JdbcSQLSyntaxErrorException: Column "IMAGEURL" not found; SQL statement: +-- Product 테이블에 더미 데이터 삽입 +INSERT INTO product (name, price, imageUrl, category_id) VALUES +('Smartphone', 699, 'http://example.com/smartphone.jpg', 1), +('Laptop', 999, 'http://example.com/laptop.jpg', 1), +('T-Shirt', 19, 'http://example.com/tshirt.jpg', 2), +('Coffee Maker', 49, 'http://example.com/coffeemaker.jpg', 3) [42122-224] + at org.h2.message.DbException.getJdbcSQLException(DbException.java:514) + at org.h2.message.DbException.getJdbcSQLException(DbException.java:489) + at org.h2.message.DbException.get(DbException.java:223) + at org.h2.message.DbException.get(DbException.java:199) + at org.h2.table.Table.getColumn(Table.java:759) + at org.h2.command.Parser.parseColumn(Parser.java:1190) + at org.h2.command.Parser.parseColumnList(Parser.java:1175) + at org.h2.command.Parser.parseInsert(Parser.java:1549) + at org.h2.command.Parser.parsePrepared(Parser.java:719) + at org.h2.command.Parser.parse(Parser.java:592) + at org.h2.command.Parser.parse(Parser.java:564) + at org.h2.command.Parser.prepareCommand(Parser.java:483) + at org.h2.engine.SessionLocal.prepareLocal(SessionLocal.java:639) + at org.h2.engine.SessionLocal.prepareCommand(SessionLocal.java:559) + at org.h2.jdbc.JdbcConnection.prepareCommand(JdbcConnection.java:1166) + at org.h2.jdbc.JdbcStatement.executeInternal(JdbcStatement.java:245) + at org.h2.jdbc.JdbcStatement.execute(JdbcStatement.java:231) + at org.h2.server.web.WebApp.getResult(WebApp.java:1345) + at org.h2.server.web.WebApp.query(WebApp.java:1143) + at org.h2.server.web.WebApp.query(WebApp.java:1119) + at org.h2.server.web.WebApp.process(WebApp.java:245) + at org.h2.server.web.WebApp.processRequest(WebApp.java:177) + at org.h2.server.web.JakartaWebServlet.doGet(JakartaWebServlet.java:129) + at org.h2.server.web.JakartaWebServlet.doPost(JakartaWebServlet.java:166) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:590) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) + at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) + at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:482) + at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115) + at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) + at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) + at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344) + at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:389) + at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) + at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:904) + at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1741) + at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) + at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1190) + at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) + at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) + at java.base/java.lang.Thread.run(Thread.java:1583) +2024-08-01 04:49:58.801112+09:00 jdbc[13]: exception +org.h2.jdbc.JdbcSQLSyntaxErrorException: Table "OPTIONS" not found; SQL statement: +-- Option 테이블에 더미 데이터 삽입 +INSERT INTO options (name, quantity, id) VALUES +('64GB Storage', 100, 1), -- Smartphone +('128GB Storage', 50, 1), -- Smartphone +('16GB RAM', 20, 2), -- Laptop +('32GB RAM', 15, 2), -- Laptop +('Size M', 200, 3), -- T-Shirt +('Size L', 150, 3), -- T-Shirt +('Black', 75, 4), -- Coffee Maker +('White', 50, 4) [42102-224] +2024-08-01 04:49:58.805468+09:00 jdbc[13]: exception +org.h2.jdbc.JdbcSQLSyntaxErrorException: Table "OPTIONS" not found; SQL statement: +select * from options [42102-224] +2024-08-01 04:50:16.036865+09:00 jdbc[13]: exception +org.h2.jdbc.JdbcSQLSyntaxErrorException: Table "OPTIONS" not found; SQL statement: +select * from options [42102-224] +2024-08-03 09:51:45.353433+09:00 jdbc[3]: exception +org.h2.jdbc.JdbcSQLSyntaxErrorException: Column "ID" not found; SQL statement: + + alter table if exists product_option + add constraint UKgw94gqd8aw1f2wqb7i9n5xfse unique (id, [42122-224] + at org.h2.message.DbException.getJdbcSQLException(DbException.java:514) + at org.h2.message.DbException.getJdbcSQLException(DbException.java:489) + at org.h2.message.DbException.get(DbException.java:223) + at org.h2.message.DbException.get(DbException.java:199) + at org.h2.table.Table.getColumn(Table.java:759) + at org.h2.table.IndexColumn.mapColumns(IndexColumn.java:184) + at org.h2.command.ddl.AlterTableAddConstraint.tryUpdate(AlterTableAddConstraint.java:182) + at org.h2.command.ddl.AlterTableAddConstraint.update(AlterTableAddConstraint.java:74) + at org.h2.command.ddl.AlterTable.update(AlterTable.java:46) + at org.h2.command.CommandContainer.update(CommandContainer.java:169) + at org.h2.command.Command.executeUpdate(Command.java:256) + at org.h2.jdbc.JdbcStatement.executeInternal(JdbcStatement.java:262) + at org.h2.jdbc.JdbcStatement.execute(JdbcStatement.java:231) + at com.zaxxer.hikari.pool.ProxyStatement.execute(ProxyStatement.java:94) + at com.zaxxer.hikari.pool.HikariProxyStatement.execute(HikariProxyStatement.java) + at org.hibernate.tool.schema.internal.exec.GenerationTargetToDatabase.accept(GenerationTargetToDatabase.java:80) + at org.hibernate.tool.schema.internal.AbstractSchemaMigrator.applySqlString(AbstractSchemaMigrator.java:575) + at org.hibernate.tool.schema.internal.AbstractSchemaMigrator.applySqlStrings(AbstractSchemaMigrator.java:515) + at org.hibernate.tool.schema.internal.AbstractSchemaMigrator.applyUniqueKeys(AbstractSchemaMigrator.java:410) + at org.hibernate.tool.schema.internal.GroupedSchemaMigratorImpl.performTablesMigration(GroupedSchemaMigratorImpl.java:98) + at org.hibernate.tool.schema.internal.AbstractSchemaMigrator.performMigration(AbstractSchemaMigrator.java:232) + at org.hibernate.tool.schema.internal.AbstractSchemaMigrator.doMigration(AbstractSchemaMigrator.java:117) + at org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator.performDatabaseAction(SchemaManagementToolCoordinator.java:280) + at org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator.lambda$process$5(SchemaManagementToolCoordinator.java:144) + at java.base/java.util.HashMap.forEach(HashMap.java:1429) + at org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator.process(SchemaManagementToolCoordinator.java:141) + at org.hibernate.boot.internal.SessionFactoryObserverForSchemaExport.sessionFactoryCreated(SessionFactoryObserverForSchemaExport.java:37) + at org.hibernate.internal.SessionFactoryObserverChain.sessionFactoryCreated(SessionFactoryObserverChain.java:35) + at org.hibernate.internal.SessionFactoryImpl.(SessionFactoryImpl.java:322) + at org.hibernate.boot.internal.SessionFactoryBuilderImpl.build(SessionFactoryBuilderImpl.java:457) + at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:1506) + at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider.java:75) + at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:390) + at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory(AbstractEntityManagerFactoryBean.java:409) + at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:396) + at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.afterPropertiesSet(LocalContainerEntityManagerFactoryBean.java:366) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1835) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1784) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:600) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:522) + at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:337) + at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) + at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:335) + at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:205) + at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:952) + at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:624) + at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754) + at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:456) + at org.springframework.boot.SpringApplication.run(SpringApplication.java:335) + at org.springframework.boot.test.context.SpringBootContextLoader.lambda$loadContext$3(SpringBootContextLoader.java:137) + at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:58) + at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:46) + at org.springframework.boot.SpringApplication.withHook(SpringApplication.java:1463) + at org.springframework.boot.test.context.SpringBootContextLoader$ContextLoaderHook.run(SpringBootContextLoader.java:553) + at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:137) + at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:108) + at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:225) + at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:152) + at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:130) + at org.springframework.test.context.web.ServletTestExecutionListener.setUpRequestContextIfNecessary(ServletTestExecutionListener.java:191) + at org.springframework.test.context.web.ServletTestExecutionListener.prepareTestInstance(ServletTestExecutionListener.java:130) + at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:260) + at org.springframework.test.context.junit.jupiter.SpringExtension.postProcessTestInstance(SpringExtension.java:163) + at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeTestInstancePostProcessors$10(ClassBasedTestDescriptor.java:378) + at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.executeAndMaskThrowable(ClassBasedTestDescriptor.java:383) + at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeTestInstancePostProcessors$11(ClassBasedTestDescriptor.java:378) + at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197) + at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:179) + at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1708) + at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509) + at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) + at java.base/java.util.stream.StreamSpliterators$WrappingSpliterator.forEachRemaining(StreamSpliterators.java:310) + at java.base/java.util.stream.Streams$ConcatSpliterator.forEachRemaining(Streams.java:735) + at java.base/java.util.stream.Streams$ConcatSpliterator.forEachRemaining(Streams.java:734) + at java.base/java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:762) + at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.invokeTestInstancePostProcessors(ClassBasedTestDescriptor.java:377) + at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$instantiateAndPostProcessTestInstance$6(ClassBasedTestDescriptor.java:290) + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.instantiateAndPostProcessTestInstance(ClassBasedTestDescriptor.java:289) + at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$testInstancesProvider$4(ClassBasedTestDescriptor.java:279) + at java.base/java.util.Optional.orElseGet(Optional.java:364) + at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$testInstancesProvider$5(ClassBasedTestDescriptor.java:278) + at org.junit.jupiter.engine.execution.TestInstancesProvider.getTestInstances(TestInstancesProvider.java:31) + at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$prepare$0(TestMethodTestDescriptor.java:106) + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.prepare(TestMethodTestDescriptor.java:105) + at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.prepare(TestMethodTestDescriptor.java:69) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$prepare$2(NodeTestTask.java:123) + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.prepare(NodeTestTask.java:123) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:90) + at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) + at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155) + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141) + at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139) + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95) + at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) + at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155) + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141) + at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139) + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95) + at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35) + at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57) + at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54) + at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:198) + at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:169) + at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:93) + at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:58) + at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:141) + at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:57) + at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:103) + at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:85) + at org.junit.platform.launcher.core.DelegatingLauncher.execute(DelegatingLauncher.java:47) + at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.processAllTestClasses(JUnitPlatformTestClassProcessor.java:119) + at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.access$000(JUnitPlatformTestClassProcessor.java:94) + at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor.stop(JUnitPlatformTestClassProcessor.java:89) + at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:62) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36) + at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24) + at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33) + at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94) + at jdk.proxy1/jdk.proxy1.$Proxy2.stop(Unknown Source) + at org.gradle.api.internal.tasks.testing.worker.TestWorker$3.run(TestWorker.java:193) + at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:129) + at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:100) + at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:60) + at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56) + at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:113) + at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:65) + at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) + at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) diff --git a/src/main/java/gift/Application.java b/src/main/java/gift/Application.java index 61603cca0..11fe2d374 100644 --- a/src/main/java/gift/Application.java +++ b/src/main/java/gift/Application.java @@ -2,10 +2,13 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.builder.SpringApplicationBuilder; @SpringBootApplication public class Application { public static void main(String[] args) { - SpringApplication.run(Application.class, args); + new SpringApplicationBuilder(Application.class) + .properties("spring.config.location=classpath:/application.properties,classpath:/application-secret.properties") + .run(args); } } diff --git a/src/main/java/gift/config/JwtConfig.java b/src/main/java/gift/config/JwtConfig.java new file mode 100644 index 000000000..4bfc94602 --- /dev/null +++ b/src/main/java/gift/config/JwtConfig.java @@ -0,0 +1,54 @@ +package gift.config; + +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.security.Keys; +import io.jsonwebtoken.SignatureAlgorithm; +import org.springframework.stereotype.Component; +import gift.constant.Constants; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +@Component +public class JwtConfig { + + public String generateToken(String email) { + // Create a claims map + Map claims = new HashMap<>(); + claims.put("email", email); + + // Generate the JWT token + return Jwts.builder() + .setClaims(claims) + .setSubject(email) + .setIssuedAt(new Date()) + .setExpiration(new Date(System.currentTimeMillis() + Constants.ONE_DAY_MILLIS)) + .signWith(Keys.hmacShaKeyFor(Constants.SECRET_KEY.getBytes()), SignatureAlgorithm.HS256) + .compact(); + } + + public String extractEmail(String token) { + if (token.startsWith("Bearer ")) { + token = token.substring(7); + } + return extractAllClaims(token).getSubject(); + } + + public Claims extractAllClaims(String token) { + return Jwts.parser() + .setSigningKey(Keys.hmacShaKeyFor(Constants.SECRET_KEY.getBytes())) + .parseClaimsJws(token) + .getBody(); + } + + public boolean validateToken(String token, String email) { + final String extractedEmail = extractEmail(token); + return extractedEmail.equals(email) && !isTokenExpired(token); + } + + private boolean isTokenExpired(String token) { + return extractAllClaims(token).getExpiration().before(new Date()); + } +} diff --git a/src/main/java/gift/config/WebConfig.java b/src/main/java/gift/config/WebConfig.java new file mode 100644 index 000000000..2c5ed7641 --- /dev/null +++ b/src/main/java/gift/config/WebConfig.java @@ -0,0 +1,64 @@ +package gift.config; + +import gift.resolver.LoginMemberArgumentResolver; +import gift.service.MemberService; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.MediaType; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.web.filter.CharacterEncodingFilter; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import java.util.List; + +@Configuration +public class WebConfig implements WebMvcConfigurer { + private final MemberService memberService; + + public WebConfig(MemberService memberService) { + this.memberService = memberService; + } + + @Override + public void addArgumentResolvers(List resolvers) { + resolvers.add(new LoginMemberArgumentResolver(memberService)); + } + + @Override + public void configureContentNegotiation(ContentNegotiationConfigurer configurer) { + configurer.favorPathExtension(false) + .favorParameter(true) + .parameterName("mediaType") + .ignoreAcceptHeader(false) + .useRegisteredExtensionsOnly(false) + .defaultContentType(MediaType.APPLICATION_JSON) + .mediaType("json", MediaType.APPLICATION_JSON) + .mediaType("xml", MediaType.APPLICATION_XML); + } + + @Bean + public CharacterEncodingFilter characterEncodingFilter() { + CharacterEncodingFilter filter = new CharacterEncodingFilter(); + filter.setEncoding("UTF-8"); + filter.setForceEncoding(true); + return filter; + } + + @Override + public void configureMessageConverters(List> converters) { + converters.add(new MappingJackson2HttpMessageConverter()); + } + + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") + .allowedOrigins("http://localhost:3000") + .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") + .allowedHeaders("*") + .allowCredentials(true); + } +} diff --git a/src/main/java/gift/constant/Constants.java b/src/main/java/gift/constant/Constants.java new file mode 100644 index 000000000..90683e192 --- /dev/null +++ b/src/main/java/gift/constant/Constants.java @@ -0,0 +1,8 @@ +package gift.constant; + +public class Constants { + public static final String SECRET_KEY = "Yn2kjibddFAWtnPJ2AFlL8WXmohJMCvigQggaEypa5E="; + public static final long ONE_DAY_MILLIS = 86400000; + public static final String AUTHENTICATE_HEADER = "Authenticate"; + public static final String BEARER = "Bearer"; +} diff --git a/src/main/java/gift/controller/CategoryController.java b/src/main/java/gift/controller/CategoryController.java new file mode 100644 index 000000000..0a50d14ef --- /dev/null +++ b/src/main/java/gift/controller/CategoryController.java @@ -0,0 +1,59 @@ +package gift.controller; + +import gift.domain.Category; +import gift.dto.CategoryListDto; +import gift.dto.UpdateCategoryDto; +import gift.service.CategoryService; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import java.util.List; + +@RestController +@RequestMapping("/api/categories") +public class CategoryController { + private final CategoryService categoryService; + + @Autowired + public CategoryController(CategoryService categoryService) { + this.categoryService = categoryService; + } + + @Operation(summary = "모든 카테고리 조회") + @GetMapping + public ResponseEntity> getAllCategories(@RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "10") int size) { + Pageable pageable = PageRequest.of(page, size); + Page categories = categoryService.findAll(pageable); + return ResponseEntity.ok(categories.getContent()); + } + +/* @GetMapping("/{id}") + public ResponseEntity getCategoryById(@PathVariable Long id) { + Category category = categoryService.findById(id); + return ResponseEntity.ok(category); + } + + @PostMapping + public ResponseEntity createCategory(@RequestParam String name) { + Category category = categoryService.addCategory(name); + return ResponseEntity.ok(category); + } + + @PutMapping("/{id}") + public ResponseEntity updateCategory(@PathVariable Long id, @RequestBody UpdateCategoryDto updateCategoryDto) { + Category updatedCategory = categoryService.updateCategory(id, updateCategoryDto); + return ResponseEntity.ok(updatedCategory); + } + + @DeleteMapping("/{id}") + public ResponseEntity deleteCategory(@PathVariable Long id) { + categoryService.deleteCategory(id); + return ResponseEntity.noContent().build(); + }*/ + +} diff --git a/src/main/java/gift/controller/KakaoAuthController.java b/src/main/java/gift/controller/KakaoAuthController.java new file mode 100644 index 000000000..36c0246f0 --- /dev/null +++ b/src/main/java/gift/controller/KakaoAuthController.java @@ -0,0 +1,37 @@ +package gift.controller; + +import gift.service.KakaoTokenService; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.ModelAndView; + +import java.io.IOException; + +@RestController("api/kakao") +public class KakaoAuthController { + /*@Value("${kakao.app.key}") + private String appKey; + + private final KakaoTokenService kakaoTokenService; + + public KakaoAuthController(KakaoTokenService kakaoTokenService) { + this.kakaoTokenService = kakaoTokenService; + } + + @GetMapping("/login") + public void redirectToKakao(HttpServletResponse response) throws IOException { + String redirectUri = "http://localhost:8080/callback"; // 리디렉션 URI + String kakaoAuthUrl = String.format("https://kauth.kakao.com/oauth/authorize?scope=talk_message&response_type=code&redirect_uri=%s&client_id=%s", redirectUri, appKey); + response.sendRedirect(kakaoAuthUrl); // 카카오 인가 페이지로 리다이렉트 + } + + @GetMapping("/callback") + public String getKakaoAuthorizationCode(@RequestParam("code") String authorizationCode) { + String accessToken = kakaoTokenService.getAccessToken(authorizationCode); + System.out.println("Access Token: " + accessToken); + return accessToken; + }*/ +} diff --git a/src/main/java/gift/controller/MemberController.java b/src/main/java/gift/controller/MemberController.java new file mode 100644 index 000000000..10416bca8 --- /dev/null +++ b/src/main/java/gift/controller/MemberController.java @@ -0,0 +1,42 @@ +package gift.controller; + +import gift.constant.Constants; +import gift.dto.LoginRequest; +import gift.dto.LoginResponse; +import gift.dto.MemberRequest; +import gift.dto.JoinResponse; +import gift.service.MemberService; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("api/members") +public class MemberController { + + private final MemberService memberService; + + public MemberController(MemberService memberService) { + this.memberService = memberService; + } + + @Operation(summary = "일반 회원가입") + @PostMapping("/register") + public ResponseEntity registerMember(@RequestBody MemberRequest requestDto) { + JoinResponse responseDto = memberService.registerMember(requestDto); + return ResponseEntity.status(HttpStatus.CREATED).body(responseDto); + } + + @Operation(summary = "일반 로그인") + @PostMapping("/login") + public ResponseEntity login(@RequestBody LoginRequest loginRequest) { + LoginResponse loginResponse = memberService.login(loginRequest); + return ResponseEntity.status(loginResponse.getAccess_token() != null ? HttpStatus.OK : HttpStatus.UNAUTHORIZED) + .header(Constants.AUTHENTICATE_HEADER, Constants.BEARER) + .body(loginResponse); + } +} \ No newline at end of file diff --git a/src/main/java/gift/controller/OrderController.java b/src/main/java/gift/controller/OrderController.java new file mode 100644 index 000000000..061a332d6 --- /dev/null +++ b/src/main/java/gift/controller/OrderController.java @@ -0,0 +1,36 @@ +package gift.controller; + +import gift.dto.OrderRequest; +import gift.dto.OrderResponse; +import gift.service.KakaoTokenService; +import gift.service.OrderService; +import io.swagger.v3.oas.annotations.Operation; +import jakarta.servlet.http.HttpSession; +import org.json.JSONException; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("api/orders") +public class OrderController { + private final OrderService orderService; + private final KakaoTokenService kakaoTokenService; + + public OrderController(OrderService orderService, KakaoTokenService kakaoTokenService) { + this.orderService = orderService; + this.kakaoTokenService = kakaoTokenService; + } + + @Operation(summary = "주문 생성") + @PostMapping + public ResponseEntity createOrder(@RequestBody OrderRequest orderRequest, @RequestHeader(HttpHeaders.AUTHORIZATION) String accessToken) throws JSONException { + // 주문 처리 로직 + OrderResponse orderResponse = orderService.createOrder(orderRequest, accessToken); + + //kakaoTokenService.processOrder(orderResponse); + + return new ResponseEntity<>(orderResponse, HttpStatus.CREATED); + } +} diff --git a/src/main/java/gift/controller/ProductController.java b/src/main/java/gift/controller/ProductController.java new file mode 100644 index 000000000..2ad552f9a --- /dev/null +++ b/src/main/java/gift/controller/ProductController.java @@ -0,0 +1,85 @@ +package gift.controller; + +import gift.domain.Product; +import gift.dto.CreateProductDto; +import gift.dto.ProductListDto; +import gift.dto.ProductOptionDto; +import gift.dto.UpdateProductDto; +import gift.service.OptionService; +import gift.service.ProductService; +import io.swagger.v3.oas.annotations.Operation; +import jakarta.validation.Valid; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.web.PageableDefault; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("api/products") +public class ProductController { + + private final ProductService productService; + private final OptionService optionService; + + public ProductController(ProductService productService, OptionService optionService) { + this.productService = productService; + this.optionService = optionService; + } + +/* // 상품 추가 + @PostMapping + public ResponseEntity createProduct(@Valid @RequestBody CreateProductDto productDto) { + Product product = productService.createProduct(productDto); + return ResponseEntity.ok(product); + }*/ + + // 전체 상품 조회 + @Operation(summary = "모든 제품 조회하기") + @GetMapping + public ResponseEntity> getAllProducts(@RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "10") int size) { + Pageable pageable = PageRequest.of(page, size); + Page allProducts = productService.getAllProducts(pageable); + if (allProducts.isEmpty()) { + return ResponseEntity.status(HttpStatus.NO_CONTENT).body(null); + } + return ResponseEntity.ok(allProducts.getContent()); // List 반환 + } + + // 특정 상품 조회 + @Operation(summary = "특정 상품의 옵션 조회") + @GetMapping("/{product_id}/options") + public ResponseEntity> getProduct(@PathVariable Long product_id) { + List productOptions = productService.getProductOptions(product_id); + return ResponseEntity.ok(productOptions); + } + +/* // 상품 정보 update + @PutMapping("/{product_id}") + public ResponseEntity updateProduct(@PathVariable Long product_id, @Valid @RequestBody UpdateProductDto productDto) { + Product updatedProduct = productService.updateProduct(product_id, productDto); + return ResponseEntity.ok(updatedProduct); + } + + // 상품 정보 삭제 + @DeleteMapping("/{product_id}") + public ResponseEntity deleteProduct(@PathVariable Long product_id) { + productService.deleteProduct(product_id); + return ResponseEntity.ok().build(); + }*/ + + // 특정 카테고리별 상품 목록 조회 + @Operation(summary = "특정 카테고리의 상품 목록 조회") + @GetMapping("/categories/{category_id}") + public ResponseEntity> getProductOptions(@PathVariable Long category_id, @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "10") int size) { + Pageable pageable = PageRequest.of(page, size); + Page products = productService.getProductsByCategory(category_id, pageable); + return ResponseEntity.ok(products.getContent()); + } +} diff --git a/src/main/java/gift/controller/WishController.java b/src/main/java/gift/controller/WishController.java new file mode 100644 index 000000000..efd05b1ee --- /dev/null +++ b/src/main/java/gift/controller/WishController.java @@ -0,0 +1,61 @@ +package gift.controller; + +import gift.domain.Member; +import gift.domain.Product; +import gift.domain.Wish; +import gift.dto.WishListDto; +import gift.security.LoginMember; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import gift.service.WishService; + +import java.util.List; + +@RestController +@RequestMapping("/api/wishes") +public class WishController { + private final WishService wishService; + + public WishController(WishService wishService) { + this.wishService = wishService; + } + + @Operation(summary = "특정 회원의 위시리스트 조회") + @GetMapping() + public ResponseEntity> getWishList( + @RequestHeader(HttpHeaders.AUTHORIZATION) String accessToken, + @RequestParam(defaultValue = "10") int size) { + System.out.println(accessToken); + if (accessToken == null) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(null); + } + int page = 0; + Pageable pageable = PageRequest.of(page, size); + Page wishPage = wishService.getWishPage(accessToken, pageable); + return ResponseEntity.ok(wishPage.getContent()); + } + + @Operation(summary = "위시리스트에 상품 추가") + @PostMapping("/{product_id}") + public ResponseEntity addToWishList(@RequestHeader(HttpHeaders.AUTHORIZATION) String accessToken, @PathVariable Long product_id) { + System.out.println(accessToken); + wishService.addWish(accessToken, product_id); + return ResponseEntity.status(HttpStatus.CREATED).build(); + } + + @Operation(summary = "위시리스트에서 삭제") + @DeleteMapping("/{product_id}") + public ResponseEntity removeFromWishList(@RequestHeader(HttpHeaders.AUTHORIZATION) String accessToken, @PathVariable Long product_id) { + wishService.deleteWish(accessToken, product_id); + return ResponseEntity.noContent().build(); + } +} + diff --git a/src/main/java/gift/domain/Category.java b/src/main/java/gift/domain/Category.java new file mode 100644 index 000000000..bafa08017 --- /dev/null +++ b/src/main/java/gift/domain/Category.java @@ -0,0 +1,51 @@ +package gift.domain; + +import jakarta.persistence.*; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Table(name = "category") +public class Category { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long category_id; + + @Column(nullable = false, unique = true) + private String name; + + private String color; + private String image_url; + private String description; + + @OneToMany(mappedBy = "category") + private List products = new ArrayList<>(); + + public Category(String name) { + this.name = name; + } + + public Category() { + + } + + public Long getId() { + return category_id; + } + public String getName(){ + return name; + } + public String getColor() { + return color; + } + public String getImage_url() { + return image_url; + } + public String getDescription() { + return description; + } + public void setName(String name) { + this.name = name; + } +} diff --git a/src/main/java/gift/domain/Member.java b/src/main/java/gift/domain/Member.java new file mode 100644 index 000000000..9e0518b15 --- /dev/null +++ b/src/main/java/gift/domain/Member.java @@ -0,0 +1,56 @@ +package gift.domain; + +import jakarta.persistence.*; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Table(name = "member") +public class Member { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long user_id; + @Column(unique = true, nullable = false) + private String email; + @Column(nullable = false) + private String password; + private String accessToken; + + @OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true) + private List wishes = new ArrayList<>(); + + public Member(String mail, String password, String accessToken) { + this.email = mail; + this.password = password; + this.accessToken = accessToken; + } + + public Member() { + + } + + public String getEmail() { + return email; + } + + public String getPassword() { + return password; + } + + public Long getId() { + return user_id; + } + + public List getWishes() { + return this.wishes; + } + + public String getAccessToken() { + return accessToken; + } + + public void setAccessToken(String token) { + this.accessToken = token; + } +} diff --git a/src/main/java/gift/domain/Option.java b/src/main/java/gift/domain/Option.java new file mode 100644 index 000000000..fef58314e --- /dev/null +++ b/src/main/java/gift/domain/Option.java @@ -0,0 +1,73 @@ +package gift.domain; + +import jakarta.persistence.*; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; + +@Entity +@Table(name = "product_option", uniqueConstraints = @UniqueConstraint(columnNames = {"id", "name"})) +public class Option { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long option_id; + + @Column(nullable = false, length = 50) + @Size(min = 1, max = 50, message = "Option 이름은 공백 포함 50문자 이내여야 합니다.") + @Pattern(regexp = "^[a-zA-Z0-9\\s()\\[\\]+\\-&/\\_]*$", message = "허용된 특수 문자는 (, ), [, ], +, -, &, /, _ 입니다.") + private String name; + + @Column(nullable = false) + @Size(min = 1, max = 100000000) + private Integer quantity; + + @ManyToOne + @JoinColumn(name = "product_id", nullable = false) + private Product product; + + public Option(String name, Product product) { + this.name = name; + this.product = product; + } + + public Option() { + + } + + public void subtract(Integer amount) { + if (amount <= 0) { + throw new IllegalArgumentException("감소할 수량은 0보다 커야 합니다."); + } + if (this.quantity < amount) { + throw new IllegalArgumentException("재고 수량이 부족합니다."); + } + this.quantity -= amount; + } + + public String getName() { + return name; + } + + public Long getId() { + return option_id; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getQuantity() { + return quantity; + } + + public void setQuantity(Integer quantity) { + this.quantity = quantity; + } + + public void setId(long l) { + this.option_id = l; + } + + public Product getProduct() { + return product; + } +} diff --git a/src/main/java/gift/domain/Order.java b/src/main/java/gift/domain/Order.java new file mode 100644 index 000000000..60adf1579 --- /dev/null +++ b/src/main/java/gift/domain/Order.java @@ -0,0 +1,74 @@ +package gift.domain; + +import gift.dto.OrderResponse; +import jakarta.persistence.*; + +import java.time.LocalDate; + + +@Entity +@Table(name = "orders") +public class Order { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long option_id; + + @ManyToOne + @JoinColumn(name = "member_id", nullable = false) + private Member member; + + @OneToOne + @JoinColumn(name = "product_id", nullable = false) + private Product product; + + @OneToOne + @JoinColumn(name = "option_id", nullable = false) + private Option option; + + private int quantity; + + private String message; + + private LocalDate ordered_at; + + public Order(Member member, Option option, Integer quantity, String message, Product product) { + this.member = member; + this.option = option; + this.quantity = quantity; + this.message = message; + this.ordered_at = LocalDate.now(); + this.product = product; + + } + + public Order() { + + } + + @PrePersist + public void prePersist() { + this.ordered_at = LocalDate.now(); // 현재 날짜를 설정 + } + + public Long getId() { + return option_id; + } + public Member getMember() { + return member; + } + public Option getOption() { + return option; + } + public int getQuantity() { + return quantity; + } + public String getMessage() { + return message; + } + public LocalDate getOrdered_at() { + return ordered_at; + } + public Product getProduct() { + return product; + } +} diff --git a/src/main/java/gift/domain/Product.java b/src/main/java/gift/domain/Product.java new file mode 100644 index 000000000..a238e561c --- /dev/null +++ b/src/main/java/gift/domain/Product.java @@ -0,0 +1,81 @@ +package gift.domain; + +import jakarta.persistence.*; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Table(name = "product") +public class Product { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long product_id; + + @Column(nullable = false, length = 15) + private String name; + + @Column(nullable = false) + private Integer price; + + @Column(nullable = false) + private String imageUrl; + + @ManyToOne + @JoinColumn(name = "category_id", nullable = false) + private Category category; + + @OneToMany(mappedBy = "product", cascade = CascadeType.ALL, orphanRemoval = true) + private List wishes = new ArrayList<>(); + + @OneToMany(mappedBy = "product", cascade = CascadeType.ALL, orphanRemoval = true ) + private List