diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index cbbe98a..0000000 Binary files a/.DS_Store and /dev/null differ diff --git a/.gitattributes.txt b/.gitattributes.txt deleted file mode 100644 index 2125666..0000000 --- a/.gitattributes.txt +++ /dev/null @@ -1 +0,0 @@ -* text=auto \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..be23922 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +### IntelliJ IDEA ### +/.idea/ +### Mac OS ### +.DS_Store +### OAuth ### +application-oauth.properties \ No newline at end of file diff --git a/ServerStudyPractice/.idea/.gitignore b/ServerStudyPractice/.idea/.gitignore deleted file mode 100644 index 13566b8..0000000 --- a/ServerStudyPractice/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/ServerStudyPractice/.idea/gradle.xml b/ServerStudyPractice/.idea/gradle.xml deleted file mode 100644 index 0ead08a..0000000 --- a/ServerStudyPractice/.idea/gradle.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/ServerStudyPractice/.idea/misc.xml b/ServerStudyPractice/.idea/misc.xml deleted file mode 100644 index 8917924..0000000 --- a/ServerStudyPractice/.idea/misc.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/ServerStudyPractice/build.gradle b/ServerStudyPractice/build.gradle deleted file mode 100644 index 6e4b62d..0000000 --- a/ServerStudyPractice/build.gradle +++ /dev/null @@ -1,39 +0,0 @@ -buildscript { - ext { - springBootVersion = '3.0.5' - } - repositories { - mavenCentral() - jcenter() - } - dependencies { - classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") - } -} - -plugins { - id 'java' - id 'eclipse' - id 'org.springframework.boot' version '3.0.5' - id 'io.spring.dependency-management' version '1.1.0' -} - -group = 'com.sujin.book' -version = '1.0-SNAPSHOT' -sourceCompatibility = '17' - -repositories { - mavenCentral() -} - -dependencies { - implementation('org.springframework.boot:spring-boot-starter-web') - testImplementation('org.springframework.boot:spring-boot-starter-test') - testImplementation('junit:junit:4.12') // Junit 4.12 라이브러리 추가 - implementation('org.projectlombok:lombok') - annotationProcessor('org.projectlombok:lombok') -} - -test { - useJUnitPlatform() -} \ No newline at end of file diff --git a/ServerStudyPractice/settings.gradle b/ServerStudyPractice/settings.gradle deleted file mode 100644 index 4ce9650..0000000 --- a/ServerStudyPractice/settings.gradle +++ /dev/null @@ -1,2 +0,0 @@ -rootProject.name = 'ServerStudyPractice' - diff --git a/ServerStudyPractice/.gitignore b/suucong/SpringBoot/SpringBootPractice/.gitignore similarity index 90% rename from ServerStudyPractice/.gitignore rename to suucong/SpringBoot/SpringBootPractice/.gitignore index b63da45..f05f4e8 100644 --- a/ServerStudyPractice/.gitignore +++ b/suucong/SpringBoot/SpringBootPractice/.gitignore @@ -39,4 +39,7 @@ bin/ .vscode/ ### Mac OS ### -.DS_Store \ No newline at end of file +.DS_Store + +### OAuth ### +application-oauth.properties \ No newline at end of file diff --git a/suucong/SpringBoot/SpringBootPractice/build.gradle b/suucong/SpringBoot/SpringBootPractice/build.gradle new file mode 100644 index 0000000..3fa8671 --- /dev/null +++ b/suucong/SpringBoot/SpringBootPractice/build.gradle @@ -0,0 +1,37 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '2.7.15' + id 'io.spring.dependency-management' version '1.0.15.RELEASE' +} + +group = 'com.example' +version = '0.0.1-SNAPSHOT' + +java { + sourceCompatibility = '11' +} + +repositories { + mavenCentral() +} + +dependencies { + implementation 'org.springframework.boot:spring-boot-starter-mustache' + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.projectlombok:lombok:1.18.22' + testImplementation 'org.testng:testng:7.1.0' + testImplementation 'junit:junit:4.13.1' + developmentOnly 'org.springframework.boot:spring-boot-devtools' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'com.h2database:h2' + implementation('org.springframework.boot:spring-boot-starter-mustache') + implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' + implementation 'org.springframework.session:spring-session-jdbc' + annotationProcessor'org.projectlombok:lombok' + testImplementation('org.springframework.security:spring-security-test') +} + +tasks.named('test') { + useJUnitPlatform() +} \ No newline at end of file diff --git a/ServerStudyPractice/gradle/wrapper/gradle-wrapper.jar b/suucong/SpringBoot/SpringBootPractice/gradle/wrapper/gradle-wrapper.jar similarity index 100% rename from ServerStudyPractice/gradle/wrapper/gradle-wrapper.jar rename to suucong/SpringBoot/SpringBootPractice/gradle/wrapper/gradle-wrapper.jar diff --git a/ServerStudyPractice/gradle/wrapper/gradle-wrapper.properties b/suucong/SpringBoot/SpringBootPractice/gradle/wrapper/gradle-wrapper.properties similarity index 80% rename from ServerStudyPractice/gradle/wrapper/gradle-wrapper.properties rename to suucong/SpringBoot/SpringBootPractice/gradle/wrapper/gradle-wrapper.properties index 768277e..ee38219 100644 --- a/ServerStudyPractice/gradle/wrapper/gradle-wrapper.properties +++ b/suucong/SpringBoot/SpringBootPractice/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sun Sep 17 21:41:43 KST 2023 +#Mon Oct 02 08:27:41 KST 2023 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/ServerStudyPractice/gradlew b/suucong/SpringBoot/SpringBootPractice/gradlew similarity index 100% rename from ServerStudyPractice/gradlew rename to suucong/SpringBoot/SpringBootPractice/gradlew diff --git a/ServerStudyPractice/gradlew.bat b/suucong/SpringBoot/SpringBootPractice/gradlew.bat similarity index 100% rename from ServerStudyPractice/gradlew.bat rename to suucong/SpringBoot/SpringBootPractice/gradlew.bat diff --git a/suucong/SpringBoot/SpringBootPractice/settings.gradle b/suucong/SpringBoot/SpringBootPractice/settings.gradle new file mode 100644 index 0000000..40d2fc5 --- /dev/null +++ b/suucong/SpringBoot/SpringBootPractice/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'SpringBootPractice' + diff --git a/ServerStudyPractice/src/main/java/com/sujin/book/springboot/Application.java b/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/Application.java similarity index 83% rename from ServerStudyPractice/src/main/java/com/sujin/book/springboot/Application.java rename to suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/Application.java index 91d2bda..86fddbb 100644 --- a/ServerStudyPractice/src/main/java/com/sujin/book/springboot/Application.java +++ b/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/Application.java @@ -1,7 +1,9 @@ package com.sujin.book.springboot; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +@EnableJpaAuditing // JPA Auditing 어노테이션을 모두 활성화 할 수 있도록 함. @SpringBootApplication // 스프링부트의 자동 설정, 스프링 Bean 읽기와 생성을 모두 자동으로 설정. 이 어노테이션이 있는 위치부터 설정을 읽어가기 떄문에 이 클래스는 항상 프로젝트 최상단에 위치해야함. public class Application { public static void main(String[] args) { diff --git a/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/config/auth/CustomOAuth2UserService.java b/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/config/auth/CustomOAuth2UserService.java new file mode 100644 index 0000000..b74bdb6 --- /dev/null +++ b/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/config/auth/CustomOAuth2UserService.java @@ -0,0 +1,53 @@ +package com.sujin.book.springboot.config.auth; + +import com.sujin.book.springboot.config.auth.dto.OAuthAttributes; +import com.sujin.book.springboot.config.auth.dto.SessionUser; +import com.sujin.book.springboot.domain.user.User; +import com.sujin.book.springboot.domain.user.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.user.DefaultOAuth2User; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.stereotype.Service; + +import javax.servlet.http.HttpSession; +import java.util.Collections; + +@RequiredArgsConstructor +@Service +public class CustomOAuth2UserService implements OAuth2UserService { + private final UserRepository userRepository; + private final HttpSession httpSession; + + @Override + public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { + OAuth2UserService delegate = new DefaultOAuth2UserService(); + OAuth2User oAuth2User = delegate.loadUser(userRequest); + + String registrationId = userRequest.getClientRegistration().getRegistrationId(); + String userNameAttributeName = userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName(); + + OAuthAttributes attributes = OAuthAttributes.of(registrationId, userNameAttributeName, oAuth2User.getAttributes()); + + User user = saveOrUpdate(attributes); + httpSession.setAttribute("user", new SessionUser(user)); + + return new DefaultOAuth2User( + Collections.singleton(new + SimpleGrantedAuthority(user.getRoleKey())), + attributes.getAttributes(), + attributes.getNameAttributeKey()); + } + + private User saveOrUpdate(OAuthAttributes attributes) { + User user = userRepository.findByEmail(attributes.getEmail()) + .map(entity -> entity.update(attributes.getName(), attributes.getPicture())) + .orElse(attributes.toEntity()); + + return userRepository.save(user); + } +} diff --git a/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/config/auth/LoginUser.java b/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/config/auth/LoginUser.java new file mode 100644 index 0000000..6d8aeec --- /dev/null +++ b/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/config/auth/LoginUser.java @@ -0,0 +1,11 @@ +package com.sujin.book.springboot.config.auth; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +public @interface LoginUser { +} diff --git a/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/config/auth/SecurityConfig.java b/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/config/auth/SecurityConfig.java new file mode 100644 index 0000000..78755c6 --- /dev/null +++ b/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/config/auth/SecurityConfig.java @@ -0,0 +1,32 @@ +package com.sujin.book.springboot.config.auth; + +import lombok.RequiredArgsConstructor; +import com.sujin.book.springboot.domain.user.Role; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + +@RequiredArgsConstructor +@EnableWebSecurity +public class SecurityConfig extends WebSecurityConfigurerAdapter { + private final CustomOAuth2UserService customOAuth2UserService; + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .csrf().disable() + .headers().frameOptions().disable() + .and() + .authorizeRequests() + .antMatchers("/", "/css/**", "/images/**", "/js/**", "/h2-console/**").permitAll() + .antMatchers("/api/v1/**").hasRole(Role.USER.name()) + .anyRequest().authenticated() + .and() + .logout() + .logoutSuccessUrl("/") + .and() + .oauth2Login() + .userInfoEndpoint() + .userService(customOAuth2UserService); + } +} \ No newline at end of file diff --git a/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/config/auth/dto/OAuthAttributes.java b/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/config/auth/dto/OAuthAttributes.java new file mode 100644 index 0000000..c146193 --- /dev/null +++ b/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/config/auth/dto/OAuthAttributes.java @@ -0,0 +1,49 @@ +package com.sujin.book.springboot.config.auth.dto; + +import com.sujin.book.springboot.domain.user.Role; +import com.sujin.book.springboot.domain.user.User; +import lombok.Builder; +import lombok.Getter; + +import java.util.Map; + +@Getter +public class OAuthAttributes { + private Map attributes; + private String nameAttributeKey; + private String name; + private String email; + private String picture; + + @Builder + public OAuthAttributes(Map attributes, String nameAttributeKey, String name, String email, String picture) { + this.attributes = attributes; + this.nameAttributeKey = nameAttributeKey; + this.name = name; + this.email = email; + this.picture = picture; + } + + public static OAuthAttributes of(String registrationId, String userNameAttributeName, Map attributes) { + return ofGoogle(userNameAttributeName, attributes); + } + + private static OAuthAttributes ofGoogle(String userNameAttributeName, Map attributes) { + return OAuthAttributes.builder() + .name((String) attributes.get("name")) + .email((String) attributes.get("email")) + .picture((String) attributes.get("picture")) + .attributes(attributes) + .nameAttributeKey(userNameAttributeName) + .build(); + } + + public User toEntity() { + return User.builder() + .name(name) + .email(email) + .picture(picture) + .role(Role.GUEST) + .build(); + } +} \ No newline at end of file diff --git a/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/config/auth/dto/SessionUser.java b/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/config/auth/dto/SessionUser.java new file mode 100644 index 0000000..015be94 --- /dev/null +++ b/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/config/auth/dto/SessionUser.java @@ -0,0 +1,19 @@ +package com.sujin.book.springboot.config.auth.dto; + +import com.sujin.book.springboot.domain.user.User; +import lombok.Getter; + +import java.io.Serializable; + +@Getter +public class SessionUser implements Serializable { + private String name; + private String email; + private String picture; + + public SessionUser(User user) { + this.name = user.getName(); + this.email = user.getEmail(); + this.picture = user.getPicture(); + } +} diff --git a/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/domain/BaseTimeEntity.java b/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/domain/BaseTimeEntity.java new file mode 100644 index 0000000..f45e278 --- /dev/null +++ b/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/domain/BaseTimeEntity.java @@ -0,0 +1,22 @@ +package com.sujin.book.springboot.domain; + +import lombok.Getter; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import javax.persistence.EntityListeners; +import javax.persistence.MappedSuperclass; +import java.time.LocalDateTime; + +@Getter +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) + +public abstract class BaseTimeEntity { + @CreatedDate + private LocalDateTime createdDate; + + @LastModifiedDate + private LocalDateTime modifiedDate; +} \ No newline at end of file diff --git a/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/domain/posts/Posts.java b/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/domain/posts/Posts.java new file mode 100644 index 0000000..a402ead --- /dev/null +++ b/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/domain/posts/Posts.java @@ -0,0 +1,41 @@ +package com.sujin.book.springboot.domain.posts; + +import com.sujin.book.springboot.domain.BaseTimeEntity; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +@Getter +@NoArgsConstructor +@Entity +public class Posts extends BaseTimeEntity { // BaseTimeEntity 클래스를 상속받음. + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(length = 500, nullable = false) + private String title; + + @Column(columnDefinition = "TEXT", nullable = false) + private String content; + + private String author; + + @Builder + public Posts(String title, String content, String author) { + this.title = title; + this.content = content; + this.author = author; + } + + public void update(String title, String content) { + this.title = title; + this.content = content; + } +} diff --git a/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/domain/posts/PostsRepository.java b/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/domain/posts/PostsRepository.java new file mode 100644 index 0000000..03f55af --- /dev/null +++ b/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/domain/posts/PostsRepository.java @@ -0,0 +1,14 @@ +package com.sujin.book.springboot.domain.posts; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +import java.util.List; + +public interface PostsRepository extends JpaRepository{ + + // SpringDataJpa에서 제공하는 기본 메서드만으로 해결 가능하지만. Query가 + // 가독성이 좋기 때문에 선택해서 사용하면 된다. + @Query("SELECT p FROM Posts p ORDER BY p.id DESC") + List findAllDesc(); +} diff --git a/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/domain/products/Products.java b/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/domain/products/Products.java new file mode 100644 index 0000000..1ea8d50 --- /dev/null +++ b/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/domain/products/Products.java @@ -0,0 +1,42 @@ +package com.sujin.book.springboot.domain.products; + +import javax.persistence.*; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@Entity +public class Products { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long productNum; + + @Column(length = 200, nullable = false) + private String name; + + @Column(nullable = false) + private Long price; + + @Column(nullable = false) + private Long stock; + + @Column(length = 200, nullable = false) + private String category; + + @Builder + public Products(String name, Long price, Long stock, String category) { + this.name = name; + this.price = price; + this.stock = stock; + this.category = category; + } + + public void update(String name, Long price, Long stock, String category) { + this.name = name; + this.price = price; + this.stock = stock; + this.category = category; + } +} diff --git a/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/domain/products/ProductsRepository.java b/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/domain/products/ProductsRepository.java new file mode 100644 index 0000000..3fd3e99 --- /dev/null +++ b/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/domain/products/ProductsRepository.java @@ -0,0 +1,6 @@ +package com.sujin.book.springboot.domain.products; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ProductsRepository extends JpaRepository { +} diff --git a/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/domain/user/Role.java b/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/domain/user/Role.java new file mode 100644 index 0000000..9f1c7c5 --- /dev/null +++ b/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/domain/user/Role.java @@ -0,0 +1,14 @@ +package com.sujin.book.springboot.domain.user; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum Role { + GUEST("ROLE_GUEST", "손님"), + USER("ROLE_USER", "일반 사용자"); + + private final String key; + private final String title; +} diff --git a/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/domain/user/User.java b/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/domain/user/User.java new file mode 100644 index 0000000..f76ce3e --- /dev/null +++ b/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/domain/user/User.java @@ -0,0 +1,50 @@ +package com.sujin.book.springboot.domain.user; + +import com.sujin.book.springboot.domain.BaseTimeEntity; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import javax.persistence.*; + +@Getter +@NoArgsConstructor +@Entity +@Table(name = "users") +public class User extends BaseTimeEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private String name; + + @Column(nullable = false) + private String email; + + @Column + private String picture; + + @Enumerated(EnumType.STRING) // EnumType.String: enum 이름을 DB에 저장 + @Column + private Role role; + + @Builder + public User(String name, String email, String picture, Role role) { + this.name = name; + this.email = email; + this.picture = picture; + this.role = role; + } + + public User update(String name, String picture) { + this.name = name; + this.picture = picture; + + return this; + } + + public String getRoleKey() { + return this.role.getKey(); + } +} diff --git a/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/domain/user/UserRepository.java b/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/domain/user/UserRepository.java new file mode 100644 index 0000000..76e5f0c --- /dev/null +++ b/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/domain/user/UserRepository.java @@ -0,0 +1,9 @@ +package com.sujin.book.springboot.domain.user; + +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface UserRepository extends JpaRepository { + Optional findByEmail(String email); // 이메일을 통해 이미 생성된 사용자인지 처음 가입하는 사용자인지 판단하기 위한 메서드 +} diff --git a/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/service/posts/PostsService.java b/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/service/posts/PostsService.java new file mode 100644 index 0000000..d0b98ad --- /dev/null +++ b/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/service/posts/PostsService.java @@ -0,0 +1,60 @@ +package com.sujin.book.springboot.service.posts; + +import com.sujin.book.springboot.domain.posts.Posts; +import com.sujin.book.springboot.domain.posts.PostsRepository; +import com.sujin.book.springboot.web.dto.PostsListResponseDto; +import com.sujin.book.springboot.web.dto.PostsResponseDto; +import com.sujin.book.springboot.web.dto.PostsSaveRequestDto; +import com.sujin.book.springboot.web.dto.PostsUpdateRequestDto; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.stream.Collectors; + +@RequiredArgsConstructor +@Service +public class PostsService { + private final PostsRepository postsRepository; + + @Transactional + public Long save(PostsSaveRequestDto requestDto) { + return postsRepository.save(requestDto.toEntity()).getId(); + } + + @Transactional + public Long update(Long id, PostsUpdateRequestDto requestDto) { + Posts posts = postsRepository.findById(id) + .orElseThrow(() -> new IllegalArgumentException("해당 게시글이 없습니다. id=" + id)); + posts.update(requestDto.getTitle(), requestDto.getContent()); + + return id; + } + + @Transactional + public PostsResponseDto findById(Long id) { + Posts entity = postsRepository.findById(id) + .orElseThrow(() -> new IllegalArgumentException("해당 게시글이 없습니다. id=" + id)); + + return new PostsResponseDto(entity); + } + + @Transactional(readOnly = true) // readOnly = true를 주면 트랜잭션 범위는 유지하되, 조회 기능만 남겨두어 조회 속도가 개선. + public List findAllDesc() { + return postsRepository.findAllDesc().stream() + // .map(posts -> new PostsListResponseDto(posts)) 와 같다. + // postsRepository의 결과로 넘어온 Posts의 Stream을 map을 통해 + // PostsListResponseDto로 변환 -> List로 반환하는 클래스 + .map(PostsListResponseDto::new) + .collect(Collectors.toList()); + } + + @Transactional + public void delete (Long id) { + Posts posts = postsRepository.findById(id) + .orElseThrow(() -> new IllegalArgumentException("해당 게시글이 없습니다. id = " + id)); + + postsRepository.delete(posts); + } +} diff --git a/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/service/products/ProductsService.java b/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/service/products/ProductsService.java new file mode 100644 index 0000000..b7afd8a --- /dev/null +++ b/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/service/products/ProductsService.java @@ -0,0 +1,53 @@ +package com.sujin.book.springboot.service.products; + +import com.sujin.book.springboot.domain.products.Products; +import com.sujin.book.springboot.domain.products.ProductsRepository; +import com.sujin.book.springboot.web.dto.ProductsResponseDto; +import com.sujin.book.springboot.web.dto.ProductsSaveRequestDto; +import com.sujin.book.springboot.web.dto.ProductsUpdateRequestDto; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@RequiredArgsConstructor +@Service +public class ProductsService { + + private final ProductsRepository productsRepository; + + // 상품 저장 + @Transactional + public Long save(ProductsSaveRequestDto requestDto) { + return productsRepository.save(requestDto.toEntity()).getProductNum(); + } + + // 상품 정보 수정 + @Transactional + public Long update(Long productNum, ProductsUpdateRequestDto requestDto) { + Products products = productsRepository.findById(productNum) + .orElseThrow(() -> new IllegalArgumentException("해당 상품이 없습니다. productNum=" + productNum)); + products.update(requestDto.getName(), requestDto.getPrice(), requestDto.getStock(), requestDto.getCategory()); + + return productNum; + } + + // 상품 조회 + @Transactional + public ProductsResponseDto findByProductNum(Long productNum) { + Products products = productsRepository.findById(productNum) + .orElseThrow(() -> new IllegalArgumentException("해당 상품이 없습니다. productNum=" + productNum)); + + return new ProductsResponseDto(products); + } + + // 상품 삭제 + @Transactional + public Long delete(Long productNum) { + Products products = productsRepository.findById(productNum) + .orElseThrow(() -> new IllegalArgumentException("해당 상품이 없습니다. productNum=" + productNum)); + productsRepository.delete(products); + + return productNum; + } + +} diff --git a/ServerStudyPractice/src/main/java/com/sujin/book/springboot/web/HelloController.java b/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/web/HelloController.java similarity index 99% rename from ServerStudyPractice/src/main/java/com/sujin/book/springboot/web/HelloController.java rename to suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/web/HelloController.java index 39b0319..a4fbafa 100644 --- a/ServerStudyPractice/src/main/java/com/sujin/book/springboot/web/HelloController.java +++ b/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/web/HelloController.java @@ -1,5 +1,6 @@ package com.sujin.book.springboot.web; import com.sujin.book.springboot.web.dto.HelloResponseDto; + import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; diff --git a/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/web/IndexController.java b/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/web/IndexController.java new file mode 100644 index 0000000..37945af --- /dev/null +++ b/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/web/IndexController.java @@ -0,0 +1,45 @@ +package com.sujin.book.springboot.web; + +import com.sujin.book.springboot.config.auth.dto.SessionUser; +import com.sujin.book.springboot.service.posts.PostsService; +import com.sujin.book.springboot.web.dto.PostsResponseDto; +import javax.servlet.http.HttpSession; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; + +@RequiredArgsConstructor +@Controller +public class IndexController { + + private final PostsService postsService; + private final HttpSession httpSession; + + @GetMapping("/") + public String index(Model model) { + model.addAttribute("posts", postsService.findAllDesc()); + + SessionUser user = (SessionUser) httpSession.getAttribute("user"); + + if(user != null) { + model.addAttribute("userName", user.getName()); + } + + return "index"; + } + + @GetMapping("/posts/save") + public String postsSave() { + return "posts-save"; + } + + @GetMapping("/posts/update/{id}") + public String postsUpdate(@PathVariable Long id, Model model) { + PostsResponseDto dto = postsService.findById(id); + model.addAttribute("post", dto); + + return "posts-update"; + } +} diff --git a/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/web/PostsApiController.java b/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/web/PostsApiController.java new file mode 100644 index 0000000..b038c6e --- /dev/null +++ b/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/web/PostsApiController.java @@ -0,0 +1,37 @@ +package com.sujin.book.springboot.web; + +import com.sujin.book.springboot.service.posts.PostsService; +import com.sujin.book.springboot.web.dto.PostsResponseDto; +import com.sujin.book.springboot.web.dto.PostsSaveRequestDto; +import com.sujin.book.springboot.web.dto.PostsUpdateRequestDto; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +@RequiredArgsConstructor +@RestController +public class PostsApiController { + + private final PostsService postsService; + + @PostMapping("/api/v1/posts") + public Long save(@RequestBody PostsSaveRequestDto requestDto) { + return postsService.save(requestDto); + } + + @PutMapping("/api/v1/posts/{id}") + public Long update(@PathVariable Long id, @RequestBody PostsUpdateRequestDto requestDto) { + return postsService.update(id, requestDto); + } + + @GetMapping("/api/v1/posts/{id}") + public PostsResponseDto findById(@PathVariable Long id) { + return postsService.findById(id); + } + + @DeleteMapping("/api/v1/posts/{id}") + public Long delete(@PathVariable Long id) { + postsService.delete(id); + + return id; + } +} diff --git a/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/web/ProductsApiController.java b/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/web/ProductsApiController.java new file mode 100644 index 0000000..32de494 --- /dev/null +++ b/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/web/ProductsApiController.java @@ -0,0 +1,35 @@ +package com.sujin.book.springboot.web; + +import com.sujin.book.springboot.service.products.ProductsService; +import com.sujin.book.springboot.web.dto.ProductsResponseDto; +import com.sujin.book.springboot.web.dto.ProductsSaveRequestDto; +import com.sujin.book.springboot.web.dto.ProductsUpdateRequestDto; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +@RequiredArgsConstructor +@RestController +public class ProductsApiController { + + private final ProductsService productsService; + + @PostMapping("/api/v1/save/products") + public Long save(@RequestBody ProductsSaveRequestDto requestDto) { + return productsService.save(requestDto); + } + + @PutMapping("/api/v1/update/products/{productNum}") + public Long update(@PathVariable Long productNum, @RequestBody ProductsUpdateRequestDto requestDto) { + return productsService.update(productNum, requestDto); + } + + @GetMapping("/api/v1/get/products/{productNum}") + public ProductsResponseDto getProducts(@PathVariable Long productNum) { + return productsService.findByProductNum(productNum); + } + + @DeleteMapping("/api/v1/delete/products/{productNum}") + public Long deleteProducts(@PathVariable Long productNum) { + return productsService.delete(productNum); + } +} diff --git a/ServerStudyPractice/src/main/java/com/sujin/book/springboot/web/dto/HelloResponseDto.java b/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/web/dto/HelloResponseDto.java similarity index 100% rename from ServerStudyPractice/src/main/java/com/sujin/book/springboot/web/dto/HelloResponseDto.java rename to suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/web/dto/HelloResponseDto.java diff --git a/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/web/dto/PostsListResponseDto.java b/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/web/dto/PostsListResponseDto.java new file mode 100644 index 0000000..509bc9a --- /dev/null +++ b/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/web/dto/PostsListResponseDto.java @@ -0,0 +1,21 @@ +package com.sujin.book.springboot.web.dto; + +import com.sujin.book.springboot.domain.posts.Posts; +import lombok.Getter; + +import java.time.LocalDateTime; + +@Getter +public class PostsListResponseDto { + private Long id; + private String title; + private String author; + private LocalDateTime modifiedDate; + + public PostsListResponseDto(Posts entity) { + this.id = entity.getId(); + this.title = entity.getTitle(); + this.author = entity.getAuthor(); + this.modifiedDate = entity.getModifiedDate(); + } +} diff --git a/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/web/dto/PostsResponseDto.java b/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/web/dto/PostsResponseDto.java new file mode 100644 index 0000000..40406dc --- /dev/null +++ b/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/web/dto/PostsResponseDto.java @@ -0,0 +1,20 @@ +package com.sujin.book.springboot.web.dto; + +import com.sujin.book.springboot.domain.posts.Posts; +import lombok.Getter; + +@Getter +public class PostsResponseDto { + + private Long id; + private String title; + private String content; + private String author; + + public PostsResponseDto(Posts entity) { + this.id = entity.getId(); + this.title = entity.getTitle(); + this.content = entity.getContent(); + this.author = entity.getAuthor(); + } +} diff --git a/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/web/dto/PostsSaveRequestDto.java b/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/web/dto/PostsSaveRequestDto.java new file mode 100644 index 0000000..106288c --- /dev/null +++ b/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/web/dto/PostsSaveRequestDto.java @@ -0,0 +1,29 @@ +package com.sujin.book.springboot.web.dto; + +import com.sujin.book.springboot.domain.posts.Posts; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class PostsSaveRequestDto { + private String title; + private String content; + private String author; + + @Builder + public PostsSaveRequestDto(String title, String content, String author) { + this.title = title; + this.content = content; + this.author = author; + } + + public Posts toEntity() { + return Posts.builder() + .title(title) + .content(content) + .author(author) + .build(); + } +} diff --git a/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/web/dto/PostsUpdateRequestDto.java b/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/web/dto/PostsUpdateRequestDto.java new file mode 100644 index 0000000..45d7d4c --- /dev/null +++ b/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/web/dto/PostsUpdateRequestDto.java @@ -0,0 +1,19 @@ +package com.sujin.book.springboot.web.dto; + +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class PostsUpdateRequestDto { + + private String title; + private String content; + + @Builder + public PostsUpdateRequestDto(String title, String content) { + this.title = title; + this.content = content; + } +} diff --git a/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/web/dto/ProductsResponseDto.java b/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/web/dto/ProductsResponseDto.java new file mode 100644 index 0000000..aff3ce7 --- /dev/null +++ b/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/web/dto/ProductsResponseDto.java @@ -0,0 +1,22 @@ +package com.sujin.book.springboot.web.dto; + +import com.sujin.book.springboot.domain.products.Products; +import lombok.Getter; + +@Getter +public class ProductsResponseDto { + private Long productNum; + private String name; + private Long price; + private Long stock; + private String category; + + public ProductsResponseDto(Products products) { + this.productNum = products.getProductNum(); + this.name = products.getName(); + this.price = products.getProductNum(); + this.stock = products.getStock(); + this.category = products.getCategory(); + } + +} diff --git a/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/web/dto/ProductsSaveRequestDto.java b/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/web/dto/ProductsSaveRequestDto.java new file mode 100644 index 0000000..73d1ff4 --- /dev/null +++ b/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/web/dto/ProductsSaveRequestDto.java @@ -0,0 +1,34 @@ +package com.sujin.book.springboot.web.dto; + +import com.sujin.book.springboot.domain.products.Products; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +@Getter +public class ProductsSaveRequestDto { + + private String name; + private Long price; + private Long stock; + private String category; + + @Builder + public ProductsSaveRequestDto(String name, Long price, Long stock, String category) { + this.name = name; + this.price = price; + this.stock = stock; + this.category = category; + } + + public Products toEntity() { + return Products.builder() + .name(name) + .price(price) + .stock(stock) + .category(category) + .build(); + } + +} diff --git a/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/web/dto/ProductsUpdateRequestDto.java b/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/web/dto/ProductsUpdateRequestDto.java new file mode 100644 index 0000000..666c028 --- /dev/null +++ b/suucong/SpringBoot/SpringBootPractice/src/main/java/com/sujin/book/springboot/web/dto/ProductsUpdateRequestDto.java @@ -0,0 +1,24 @@ +package com.sujin.book.springboot.web.dto; + +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class ProductsUpdateRequestDto { + + private String name; + private Long price; + private Long stock; + private String category; + + @Builder + public ProductsUpdateRequestDto(String name, Long price, Long stock, String category) { + this.name = name; + this.price = price; + this.stock = stock; + this.category = category; + } + +} diff --git a/suucong/SpringBoot/SpringBootPractice/src/main/resources/application.properties b/suucong/SpringBoot/SpringBootPractice/src/main/resources/application.properties new file mode 100644 index 0000000..22de2f6 --- /dev/null +++ b/suucong/SpringBoot/SpringBootPractice/src/main/resources/application.properties @@ -0,0 +1,11 @@ +spring.jpa.show-sql=true +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL57Dialect +spring.jpa.properties.hibernate.dialect.storage_engine=innodb +spring.h2.console.path=/h2-console +spring.datasource.username=sa +spring.datasource.password=suu +spring.datasource.hikari.jdbc-url=jdbc:h2:mem://localhost/~/testdb;MODE=MYSQL;DB_CLOSE_ON_EXIT=FALSE +spring.h2.console.enabled=true +server.servlet.encoding.force-response=true +spring.session.store-type=jdbc +spring.profiles.include=oauth \ No newline at end of file diff --git a/suucong/SpringBoot/SpringBootPractice/src/main/resources/static/js/app/index.js b/suucong/SpringBoot/SpringBootPractice/src/main/resources/static/js/app/index.js new file mode 100644 index 0000000..3f54520 --- /dev/null +++ b/suucong/SpringBoot/SpringBootPractice/src/main/resources/static/js/app/index.js @@ -0,0 +1,76 @@ +var main = { + init : function () { + var _this = this; + $('#btn-save').on('click', function () { + _this.save(); + }); + + $('#btn-update').on('click', function () { + _this.update(); + }); + + $('#btn-delete').on('click', function () { + _this.delete(); + }); + }, + save : function () { + var data = { + title: $('#title').val(), + author: $('#author').val(), + content: $('#content').val() + }; + + $.ajax({ + type: 'POST', + url: '/api/v1/posts', + dataType: 'json', + contentType:'application/json; charset=utf-8', + data: JSON.stringify(data) + }).done(function() { + alert('글이 등록되었습니다.'); + // 글 등록이 성공하면 메인페이지로 이동 + window.location.href = '/'; + }).fail(function (error) { + alert(JSON.stringify(error)); + }); + }, + update : function () { + var data = { + title: $('#title').val(), + content: $('#content').val() + }; + + var id = $('#id').val(); + + $.ajax({ + type: 'PUT', + url: '/api/v1/posts/'+id, + dataType: 'json', + contentType:'application/json; charset=utf-8', + data: JSON.stringify(data) + }).done(function() { + alert('글이 수정되었습니다.'); + window.location.href = '/'; + }).fail(function (error) { + alert(JSON.stringify(error)); + }); + }, + delete : function () { + var id = $('#id').val(); + + $.ajax({ + type: 'DELETE', + url: '/api/v1/posts/'+id, + dataType: 'json', + contentType:'application/json; charset=utf-8' + }).done(function() { + alert('글이 삭제되었습니다.'); + window.location.href = '/'; + }).fail(function (error) { + alert(JSON.stringify(error)); + }); + } + +}; + +main.init(); \ No newline at end of file diff --git a/suucong/SpringBoot/SpringBootPractice/src/main/resources/templates/index.mustache b/suucong/SpringBoot/SpringBootPractice/src/main/resources/templates/index.mustache new file mode 100644 index 0000000..6e3a0e8 --- /dev/null +++ b/suucong/SpringBoot/SpringBootPractice/src/main/resources/templates/index.mustache @@ -0,0 +1,54 @@ + + + + + + + + + + + + +{{>layout/header}} + +

스프링부트로 시작하는 웹 서비스 Ver.2

+
+
+
+ 글 등록 + {{#userName}} + Logged in as: {{userName}} + Logout + {{/userName}} + {{^userName}} + Google Login + Naver Login + {{/userName}} +
+
+
+ + + + + + + + + + + + {{#posts}} + + + + + + + {{/posts}} + +
게시글번호제목작성자최종수정일
{{id}}{{title}}{{author}}{{modifiedDate}}
+
+ +{{>layout/footer}} \ No newline at end of file diff --git a/suucong/SpringBoot/SpringBootPractice/src/main/resources/templates/layout/footer.mustache b/suucong/SpringBoot/SpringBootPractice/src/main/resources/templates/layout/footer.mustache new file mode 100644 index 0000000..2e8c833 --- /dev/null +++ b/suucong/SpringBoot/SpringBootPractice/src/main/resources/templates/layout/footer.mustache @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/suucong/SpringBoot/SpringBootPractice/src/main/resources/templates/layout/header.mustache b/suucong/SpringBoot/SpringBootPractice/src/main/resources/templates/layout/header.mustache new file mode 100644 index 0000000..c38178a --- /dev/null +++ b/suucong/SpringBoot/SpringBootPractice/src/main/resources/templates/layout/header.mustache @@ -0,0 +1,10 @@ + + + + 스프링 부트 웹 서비스 + + + + + + \ No newline at end of file diff --git a/suucong/SpringBoot/SpringBootPractice/src/main/resources/templates/posts-save.mustache b/suucong/SpringBoot/SpringBootPractice/src/main/resources/templates/posts-save.mustache new file mode 100644 index 0000000..8eb0bb5 --- /dev/null +++ b/suucong/SpringBoot/SpringBootPractice/src/main/resources/templates/posts-save.mustache @@ -0,0 +1,26 @@ +{{>layout/header}} + +

게시글 등록

+ +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ 취소 + +
+
+ +{{>layout/footer}} \ No newline at end of file diff --git a/suucong/SpringBoot/SpringBootPractice/src/main/resources/templates/posts-update.mustache b/suucong/SpringBoot/SpringBootPractice/src/main/resources/templates/posts-update.mustache new file mode 100644 index 0000000..8270d91 --- /dev/null +++ b/suucong/SpringBoot/SpringBootPractice/src/main/resources/templates/posts-update.mustache @@ -0,0 +1,33 @@ +{{>layout/header}} + +

게시글 수정

+ +
+
+
+
+ + + +
+
+ + +
+
+ + + +
+
+ + +
+
+ 취소 + + +
+
+ +{{>layout/footer}} \ No newline at end of file diff --git a/ServerStudyPractice/src/test/java/com/sujin/book/springboot/Application.java b/suucong/SpringBoot/SpringBootPractice/src/test/java/com/sujin/book/springboot/Application.java similarity index 89% rename from ServerStudyPractice/src/test/java/com/sujin/book/springboot/Application.java rename to suucong/SpringBoot/SpringBootPractice/src/test/java/com/sujin/book/springboot/Application.java index ef54076..87ba533 100644 --- a/ServerStudyPractice/src/test/java/com/sujin/book/springboot/Application.java +++ b/suucong/SpringBoot/SpringBootPractice/src/test/java/com/sujin/book/springboot/Application.java @@ -1,7 +1,9 @@ package com.sujin.book.springboot; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +@EnableJpaAuditing @SpringBootApplication // 스프링부트의 자동 설정, 스프링 Bean 읽기와 생성을 모두 자동으로 설정. 이 어노테이션이 있는 위치부터 설정을 읽어가기 떄문에 이 클래스는 항상 프로젝트 최상단에 위치해야함. public class Application { public static void main(String[] args) { diff --git a/suucong/SpringBoot/SpringBootPractice/src/test/java/com/sujin/book/springboot/domain/posts/PostsRepositoryTest.java b/suucong/SpringBoot/SpringBootPractice/src/test/java/com/sujin/book/springboot/domain/posts/PostsRepositoryTest.java new file mode 100644 index 0000000..92e3791 --- /dev/null +++ b/suucong/SpringBoot/SpringBootPractice/src/test/java/com/sujin/book/springboot/domain/posts/PostsRepositoryTest.java @@ -0,0 +1,70 @@ +package com.sujin.book.springboot.domain.posts; + +import org.junit.After; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +import java.time.LocalDateTime; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class PostsRepositoryTest { + + @Autowired + PostsRepository postsRepository; + + @After // Junit에서 단위테스트가 끝날 때마다 수행되는 메소드 지정 + public void cleanup() { + postsRepository.deleteAll(); + } + + @Test + public void 게시글저장_불러오기() { + // given + String title = "테스트 게시글"; + String content = "테스트 본문"; + + postsRepository.save(Posts.builder() + .title(title) + .content(content) + .author("jojoldu@gmail.com") + .build()); + + // when + List postsList = postsRepository.findAll(); + + // then + Posts posts = postsList.get(0); + assertThat(posts.getTitle()).isEqualTo(title); + assertThat(posts.getContent()).isEqualTo(content); + } + + @Test + public void BaseTimeEntity_등록() { + // given + LocalDateTime now = LocalDateTime.of(2023, 10, 1, 0, 0, 0); + postsRepository.save(Posts.builder() + .title("title") + .content("content") + .author("author") + .build()); + + // when + List postsList = postsRepository.findAll(); + + // then + Posts posts = postsList.get(0); + + System.out.println(">>>>>>>>>> createDate = " + posts.getCreatedDate() + + ", modifiedDate = " + posts.getModifiedDate()); + + assertThat(posts.getCreatedDate()).isAfter(now); + assertThat(posts.getModifiedDate()).isAfter(now); + } +} diff --git a/ServerStudyPractice/src/test/java/com/sujin/book/springboot/web/HelloControllerTest.java b/suucong/SpringBoot/SpringBootPractice/src/test/java/com/sujin/book/springboot/web/HelloControllerTest.java similarity index 100% rename from ServerStudyPractice/src/test/java/com/sujin/book/springboot/web/HelloControllerTest.java rename to suucong/SpringBoot/SpringBootPractice/src/test/java/com/sujin/book/springboot/web/HelloControllerTest.java diff --git a/suucong/SpringBoot/SpringBootPractice/src/test/java/com/sujin/book/springboot/web/IndexControllerTest.java b/suucong/SpringBoot/SpringBootPractice/src/test/java/com/sujin/book/springboot/web/IndexControllerTest.java new file mode 100644 index 0000000..2159ed4 --- /dev/null +++ b/suucong/SpringBoot/SpringBootPractice/src/test/java/com/sujin/book/springboot/web/IndexControllerTest.java @@ -0,0 +1,27 @@ +package com.sujin.book.springboot.web; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class IndexControllerTest { + + @Autowired + private TestRestTemplate restTemplate; + + @Test + public void 메인페이지_로딩() { // URL 호출 시 페이지의 내용이 제대로 호출되는지에 대한 테스트 + // when + String body = this.restTemplate.getForObject("/", String.class); + + // then + assertThat(body).contains("스프링 부트로 시작하는 웹 서비스"); + } +} diff --git a/suucong/SpringBoot/SpringBootPractice/src/test/java/com/sujin/book/springboot/web/PostsApiControllerTest.java b/suucong/SpringBoot/SpringBootPractice/src/test/java/com/sujin/book/springboot/web/PostsApiControllerTest.java new file mode 100644 index 0000000..cebddd4 --- /dev/null +++ b/suucong/SpringBoot/SpringBootPractice/src/test/java/com/sujin/book/springboot/web/PostsApiControllerTest.java @@ -0,0 +1,100 @@ +package com.sujin.book.springboot.web; + +import com.sujin.book.springboot.domain.posts.Posts; +import com.sujin.book.springboot.domain.posts.PostsRepository; +import com.sujin.book.springboot.web.dto.PostsSaveRequestDto; +import com.sujin.book.springboot.web.dto.PostsUpdateRequestDto; +import org.junit.After; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.junit4.SpringRunner; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class PostsApiControllerTest { + @LocalServerPort + private int port; + + @Autowired + private TestRestTemplate restTemplate; + + @Autowired + private PostsRepository postsRepository; + + @After + public void tearDown() throws Exception { + postsRepository.deleteAll(); + } + + @Test + public void Posts_등록된다() throws Exception { + // given + String title = "title"; + String content = "content"; + PostsSaveRequestDto requestDto = PostsSaveRequestDto.builder() + .title(title) + .content(content) + .author("author") + .build(); + + String url = "http://localhost:" + port + "/api/v1/posts"; + + // when + ResponseEntity responseEntity = restTemplate.postForEntity(url, requestDto, Long.class); + + // then + assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(responseEntity.getBody()).isGreaterThan(0L); + List all = postsRepository.findAll(); + assertThat(all.get(0).getTitle()).isEqualTo(title); + assertThat(all.get(0).getContent()).isEqualTo(content); + } + + @Test + public void Posts_수정된다() throws Exception { + // given + Posts savedPosts = postsRepository.save(Posts.builder() + .title("title") + .content("content") + .author("author") + .build()); + + Long updateId = savedPosts.getId(); + String expectedTitle = "title2"; + String expectedContent = "content2"; + + PostsUpdateRequestDto requestDto = PostsUpdateRequestDto.builder() + .title(expectedTitle) + .content(expectedContent) + .build(); + + String url = "http://localhost:" + port + "/api/v1/posts/" + updateId; + + HttpEntity requestEntity = new HttpEntity<>(requestDto); + + // when + ResponseEntity responseEntity = restTemplate. + exchange(url, HttpMethod.PUT, + requestEntity, Long.class); + + // then + assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(responseEntity.getBody()).isGreaterThan(0L); + + List all = postsRepository.findAll(); + assertThat(all.get(0).getTitle()).isEqualTo(expectedTitle); + assertThat(all.get(0).getContent()).isEqualTo(expectedContent); + } +} diff --git a/suucong/SpringBoot/SpringBootPractice/src/test/java/com/sujin/book/springboot/web/ProductsApiControllerTest.java b/suucong/SpringBoot/SpringBootPractice/src/test/java/com/sujin/book/springboot/web/ProductsApiControllerTest.java new file mode 100644 index 0000000..3739d9a --- /dev/null +++ b/suucong/SpringBoot/SpringBootPractice/src/test/java/com/sujin/book/springboot/web/ProductsApiControllerTest.java @@ -0,0 +1,106 @@ +package com.sujin.book.springboot.web; + +import com.sujin.book.springboot.domain.products.Products; +import com.sujin.book.springboot.domain.products.ProductsRepository; +import com.sujin.book.springboot.web.dto.ProductsSaveRequestDto; +import com.sujin.book.springboot.web.dto.ProductsUpdateRequestDto; +import org.junit.After; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.junit4.SpringRunner; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class ProductsApiControllerTest { + @LocalServerPort + private int port; + + @Autowired + private TestRestTemplate restTemplate; + + @Autowired + private ProductsRepository productsRepository; + + @After + public void tearDown() throws Exception { + productsRepository.deleteAll(); + } + + @Test + public void Products_등록된다() throws Exception { + + // given + String name = "name"; + String category = "category"; + + ProductsSaveRequestDto requestDto = ProductsSaveRequestDto.builder() + .name(name) + .price(2000L) + .stock(20L) + .category(category) + .build(); + + String url = "http://localhost:" + port + "/api/v1/save/products"; + + // when + ResponseEntity responseEntity = restTemplate.postForEntity(url, requestDto, Long.class); + + assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(responseEntity.getBody()).isGreaterThan(0L); + List all = productsRepository.findAll(); + assertThat(all.get(0).getName()).isEqualTo(name); + assertThat(all.get(0).getCategory()).isEqualTo(category); + } + + @Test + public void Products_수정된다() throws Exception { + + // given + Products savedProducts = productsRepository.save(Products.builder() + .name("name") + .price(2000L) + .stock(20L) + .category("category") + .build()); + + Long updateNum = savedProducts.getProductNum(); + String expectedName = "name2"; + String expectedCategory = "category2"; + + ProductsUpdateRequestDto requestDto = ProductsUpdateRequestDto.builder() + .name(expectedName) + .price(2000L) + .stock(20L) + .category(expectedCategory) + .build(); + + String url = "http://localhost:" + port + "/api/v1/update/products/" + updateNum; + + HttpEntity requestEntity = new HttpEntity<>(requestDto); + + // when + ResponseEntity responseEntity = restTemplate. + exchange(url, HttpMethod.PUT, + requestEntity, Long.class); + + // when + assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(responseEntity.getBody()).isGreaterThan(0L); + + List all = productsRepository.findAll(); + assertThat(all.get(0).getName()).isEqualTo(expectedName); + assertThat(all.get(0).getCategory()).isEqualTo(expectedCategory); + } +} diff --git a/ServerStudyPractice/src/test/java/com/sujin/book/springboot/web/dto/HelloResponseDtoTest.java b/suucong/SpringBoot/SpringBootPractice/src/test/java/com/sujin/book/springboot/web/dto/HelloResponseDtoTest.java similarity index 100% rename from ServerStudyPractice/src/test/java/com/sujin/book/springboot/web/dto/HelloResponseDtoTest.java rename to suucong/SpringBoot/SpringBootPractice/src/test/java/com/sujin/book/springboot/web/dto/HelloResponseDtoTest.java diff --git a/week 1/server-name.html b/week 1/server-name.html deleted file mode 100644 index 21ad7e9..0000000 --- a/week 1/server-name.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - Document - - -

[1주차 스터디] 깃 연습 - 서버 멤버 이름 작성

- -

0. 최유선

-

1. 문소영

-

2. 노수진

-

3. 이예림

-

4. 이현지

-

5. 김나영

- -