Skip to content

Commit

Permalink
경북대 BE_안용진 3주차 과제 (0단계) (#225)
Browse files Browse the repository at this point in the history
* Initial commit

* feat: set up the project

* 경북대BE_안용진_2주차 과제(step0) (#74)

* Initial commit

* 경북대BE_안용진_1주차 과제 (#197)

* docs(README.md): fill README.md with features that should be implemented

* docs(README.md): add some response details

* feat: add ProductRecord class for storing product information

and some code for testing if record class works well

* feat: dd feature to check existence of record by ID

* feat: add feature to create new id

* feat: change id type from String to long

* feat: add feature to insert records with specified ID

* feat: add feature to insert records with automatically specified ID

* feat: implement post handler method

* feat: implement get request handler

* feat: add feature to handling DELETE request

* refactor: change addNewRecord to throw Exception instead of to return null

* feat: implement feature to handle PUT

* refactor: extract feature that make created response entity to function

* feat: implement feature to handle PATCH

* feat: add global exception handler

* refactor: extract withId method to Record class for creating new objects with specific id

* refactor : reorder methods for better readability and understanding

* docs: add extra features

* refactor: restructure project packages

* Step2 시작

* feat: create necessary files for server side rendering

* feat: add simple table that shows information of products

* feat: make simple edit page

* fix: fix getNewId() to correctly find new id

* fix: ensure null is passed when input is empty

* feat: add simple product add page

* feat: add buttons to manipulate data

* docs: fill README with list of features for step2

* style: apply styling using CSS

* style: apply styling on product add page using CSS

* style: apply styling on product edit page

* Step3 시작

* feat: connect Record get functions to jdbcTemplates

* feat: integrate addNewRecord method with JDBC

* feat: integrate replaceRecord method that update all field of record with jdbc

* feat: Integrate edit record feature with JDBC

* feat: integrate feature deleting record with JDBC

* refactor: edit getNewId not to make overflow

* fix: correct table name typo

* fix: adjust some bug

* feat: set auto schema initialization using schema.sql

* refactor: change field-based injection to constructor-based injection

* refactor: Ensure consistency by updating all Rest methods to use ResponseEntity

* feat: Implement global handler method for all subclasses of Exception

* docs: add TODO comments

* refactor: Remove unused fields

* refactor: Remove unused fields

* refactor: Change all field injections to constructor injections

* refactor: Replace all array structures with List

* feat: Change to generate keys in the database

---------

Co-authored-by: 박재성(Jason) <[email protected]>

* 경북대 BE_안용진 2주차 과제(1단계) (#209)

* feat: Display error messages to users when receiving an error response

* docs: fill README with list of features for step1

* feat: Apply validation using BeanValidation

* build: Add boot-starter-validation dependency

* feat: Add exception handler for MethodArgumentNotValidException

* feat: Implement response handling for 400 errors on admin page

* fix: Correct minor text typos

* 경북대 BE_안용진 2주차 과제 (2, 3단계) (#376)

* 2단계 시작

* feat: Add users table definition to schema.sql

* feat: Add users table definition to schema.sql

* refactor: Update data types to comply with SQL standards

* refactor: Rename model package to entity package

* feat: Implement API to retrieve all users

* feat: add Request Http for test getUsers API

* refactor: add UserService class

* refactor: Modify API request URLs to start with /api

* feat: Add signup

* feat: Respond with 409 Conflict for duplicate email signups

* feat: Add account deletion

* feat: Implement password change functionality (JWT token not yet applied)

* feat: Implement login

* feat: Add exception handler to response 403 for forbidden requests or password errors

* refactor: Redistribute responsibilities

* refactor: Rename JWT package to lowercase

* feat: Apply authentication filter

* feat: Implement TokenEmail Resolver to extract email from token and pass as method argument

* feat: Implement user-specific wishlist retrieval

* feat: Implement wishlist addition

* feat: Implement wishlist deletion

* fix: fix sql typos

* refactor: Update API paths

* refactor: Reorganize packages

* refactor: remove unneccessary folder

---------

Co-authored-by: 박재성(Jason) <[email protected]>
  • Loading branch information
sapsalian and wotjd243 authored Jul 11, 2024
1 parent 234f088 commit 2a66fdc
Show file tree
Hide file tree
Showing 45 changed files with 1,704 additions and 1 deletion.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1 @@
# spring-gift-jpa
# spring-gift-jpa
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
runtimeOnly 'com.h2database:h2'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation group: 'org.mindrot', name: 'jbcrypt', version: '0.4'
implementation group: 'io.jsonwebtoken', name: 'jjwt', version: '0.12.6'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/gift/annotation/TokenEmail.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package gift.annotation;

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 TokenEmail {
}
37 changes: 37 additions & 0 deletions src/main/java/gift/config/WebConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package gift.config;

import gift.resolver.TokenEmailResolver;
import gift.security.authfilter.AuthenticationFilter;
import gift.security.jwt.TokenExtractor;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;

@Configuration
public class WebConfig implements WebMvcConfigurer {
private final TokenExtractor tokenExtractor;

public WebConfig(TokenExtractor tokenExtractor) {
this.tokenExtractor = tokenExtractor;
}

@Bean
public FilterRegistrationBean<AuthenticationFilter> authenticationFilterRegistrationBean(TokenExtractor tokenExtractor) {
FilterRegistrationBean<AuthenticationFilter> registrationBean = new FilterRegistrationBean<>();

registrationBean.setFilter(new AuthenticationFilter(tokenExtractor));
registrationBean.addUrlPatterns("/api/*");
registrationBean.setOrder(1);

return registrationBean;
}

@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new TokenEmailResolver(tokenExtractor));
}
}
40 changes: 40 additions & 0 deletions src/main/java/gift/controller/page/AdminController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package gift.controller.page;

import gift.entity.ProductRecord;
import gift.repository.ProductDAO;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

import java.util.List;

@Controller
public class AdminController {
private final ProductDAO productDAO;

AdminController(ProductDAO productDAO) {
this.productDAO = productDAO;
}

@GetMapping("/")
public String admin(Model model) {
List<ProductRecord> products = productDAO.getAllRecords();

model.addAttribute("products", products);
return "admin";
}

@GetMapping("/products/{id}/edit")
public String editProduct(@PathVariable int id, Model model) {
ProductRecord product = productDAO.getRecord(id);
model.addAttribute("product", product);

return "product_edit";
}

@GetMapping("/products/add")
public String addProduct(Model model) {
return "product_add";
}
}
68 changes: 68 additions & 0 deletions src/main/java/gift/controller/product/ProductController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package gift.controller.product;

import gift.entity.ProductRecord;
import gift.repository.ProductDAO;
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

import java.net.URI;
import java.util.List;
import java.util.NoSuchElementException;

@RestController
public class ProductController {
private final ProductDAO productDAO;

ProductController(ProductDAO productDAO) {
this.productDAO = productDAO;
}

@GetMapping("/api/products")
public ResponseEntity<List<ProductRecord>> getAllProducts() {
return ResponseEntity.ok(productDAO.getAllRecords());
}

@PostMapping("/api/products")
public ResponseEntity<ProductRecord> addProduct(@Valid @RequestBody ProductRecord product) {
ProductRecord result = productDAO.addNewRecord(product);

return makeCreatedResponse(result);
}

@DeleteMapping("/api/products/{id}")
public ResponseEntity<Void> deleteProduct(@PathVariable int id) {
productDAO.deleteRecord(id);

return ResponseEntity.noContent().build();
}

@PutMapping("/api/products/{id}")
public ResponseEntity<ProductRecord> updateProduct(@PathVariable int id, @Valid @RequestBody ProductRecord product) {
ProductRecord result;
try {
result = productDAO.replaceRecord(id, product);

return ResponseEntity.ok(result);
} catch (NoSuchElementException e) {
result = productDAO.addNewRecord(product, id);

return makeCreatedResponse(result);
}
}

@PatchMapping("/api/products/{id}")
public ResponseEntity<ProductRecord> updateProductPartially(@PathVariable int id, @Valid @RequestBody ProductRecord patch) {
return ResponseEntity.ok(productDAO.updateRecord(id, patch));
}

private ResponseEntity<ProductRecord> makeCreatedResponse(ProductRecord product) {
URI location = ServletUriComponentsBuilder.fromCurrentRequest()
.path("/products/"+ product.id())
.build()
.toUri();

return ResponseEntity.created(location).body(product);
}
}
35 changes: 35 additions & 0 deletions src/main/java/gift/controller/user/AuthController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package gift.controller.user;

import gift.dto.user.TokenResponseDTO;
import gift.dto.user.UserRequestDTO;
import gift.exception.InvalidPasswordException;
import gift.service.UserService;
import jakarta.validation.Valid;
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.RestController;

@RestController
public class AuthController {
private final UserService userService;

public AuthController(UserService userService) {
this.userService = userService;
}

@PostMapping("/signup")
public ResponseEntity<TokenResponseDTO> signUp(@RequestBody @Valid UserRequestDTO userRequestDTO) {
TokenResponseDTO tokenResponseDTO = userService.signUp(userRequestDTO);

return ResponseEntity.ok(tokenResponseDTO);
}


@PostMapping("/login")
public ResponseEntity<TokenResponseDTO> login(@RequestBody @Valid UserRequestDTO userRequestDTO) throws InvalidPasswordException {
TokenResponseDTO tokenResponseDTO = userService.login(userRequestDTO);

return ResponseEntity.ok(tokenResponseDTO);
}
}
47 changes: 47 additions & 0 deletions src/main/java/gift/controller/user/UserController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package gift.controller.user;

import gift.annotation.TokenEmail;
import gift.dto.user.PwUpdateDTO;
import gift.dto.user.UserResponseDTO;
import gift.exception.ForbiddenRequestException;
import gift.service.UserService;
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
public class UserController {
private final UserService userService;

public UserController(UserService userService) {
this.userService = userService;
}

@GetMapping("/api/users")
public ResponseEntity<List<UserResponseDTO>> getUsers() {
return ResponseEntity.ok(userService.getAllUsers());
}



@DeleteMapping("/api/users")
public ResponseEntity<Void> deleteUser(@TokenEmail String email) {
userService.deleteUser(email);
return ResponseEntity.noContent().build();
}

@PatchMapping("/api/password-change")
public ResponseEntity<String> updatePw(@TokenEmail String email, @RequestBody @Valid PwUpdateDTO pwUpdateDTO) {
final boolean FORBIDDEN = true;

if (FORBIDDEN) {
throw new ForbiddenRequestException("password changing is not allowed");
}

userService.updatePw(email, pwUpdateDTO);

return ResponseEntity.ok("Password updated successfully");
}
}
47 changes: 47 additions & 0 deletions src/main/java/gift/controller/wish/WishController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package gift.controller.wish;

import gift.annotation.TokenEmail;
import gift.dto.wish.WishRequestDTO;
import gift.dto.wish.WishResponseDTO;
import gift.service.WishService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

import java.net.URI;
import java.util.List;

@RestController
public class WishController {
private final WishService wishService;

public WishController(WishService wishService) {
this.wishService = wishService;
}

@GetMapping("/api/wishes")
public ResponseEntity<List<WishResponseDTO>> getWishes(@TokenEmail String email) {
return ResponseEntity.ok(wishService.getWishes(email));
}

@PostMapping("/api/wishes")
public ResponseEntity<WishResponseDTO> addWish(@TokenEmail String email, @RequestBody WishRequestDTO wishRequestDTO) {
return makeCreatedResponse(wishService.addWish(email, wishRequestDTO));
}

@DeleteMapping("/api/wishes/{wishId}")
public ResponseEntity<Void> deleteWish(@TokenEmail String email, @PathVariable long wishId) {
wishService.deleteWish(email, wishId);

return ResponseEntity.noContent().build();
}

private ResponseEntity<WishResponseDTO> makeCreatedResponse(WishResponseDTO wish) {
URI location = ServletUriComponentsBuilder.fromCurrentRequest()
.path("/api/wishes/"+ wish.id())
.build()
.toUri();

return ResponseEntity.created(location).body(wish);
}
}
7 changes: 7 additions & 0 deletions src/main/java/gift/dto/user/EncryptedUpdateDTO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package gift.dto.user;

public record EncryptedUpdateDTO(
long id,
String encryptedPw
) {
}
9 changes: 9 additions & 0 deletions src/main/java/gift/dto/user/PwUpdateDTO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package gift.dto.user;

import jakarta.validation.constraints.Pattern;

public record PwUpdateDTO(
@Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9]).{8,15}$", message = "비밀번호는 8 ~ 15자로, 대문자, 소문자, 숫자가 반드시 포함되어야 합니다.")
String password
) {
}
5 changes: 5 additions & 0 deletions src/main/java/gift/dto/user/TokenResponseDTO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package gift.dto.user;

public record TokenResponseDTO(String token) {

}
6 changes: 6 additions & 0 deletions src/main/java/gift/dto/user/UserEncryptedDTO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package gift.dto.user;

public record UserEncryptedDTO(
String email,
String encryptedPW
) { }
9 changes: 9 additions & 0 deletions src/main/java/gift/dto/user/UserInfoDTO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package gift.dto.user;

public record UserInfoDTO(
long id,
String email,
String encryptedPw
) {

}
13 changes: 13 additions & 0 deletions src/main/java/gift/dto/user/UserRequestDTO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package gift.dto.user;

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Pattern;

public record UserRequestDTO(
@Email(message = "email 형식에 맞지 않습니다.")
String email,

@Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9]).{8,15}$", message = "비밀번호는 8 ~ 15자로, 대문자, 소문자, 숫자가 반드시 포함되어야 합니다.")
String password
) {
}
16 changes: 16 additions & 0 deletions src/main/java/gift/dto/user/UserResponseDTO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package gift.dto.user;

public record UserResponseDTO(
long id,
String email
) {
public UserResponseDTO {
if (id < 0) {
throw new IllegalArgumentException("Id cannot be negative");
}

if (email == null) {
throw new IllegalArgumentException("Email cannot be null");
}
}
}
8 changes: 8 additions & 0 deletions src/main/java/gift/dto/wish/WishCreateDTO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package gift.dto.wish;

public record WishCreateDTO(
long userId,
long productId,
int quantity
) {
}
9 changes: 9 additions & 0 deletions src/main/java/gift/dto/wish/WishInfoDTO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package gift.dto.wish;

public record WishInfoDTO(
long id,
long userId,
long productId,
int quantity
) {
}
Loading

0 comments on commit 2a66fdc

Please sign in to comment.