Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

짱바구니2 #13

Open
wants to merge 63 commits into
base: main
Choose a base branch
from

Conversation

woo-chang
Copy link

No description provided.

woo-chang and others added 30 commits April 28, 2023 16:19
* docs: README.md 기능 목록 작성

* chore: DB 설정 추가

* feat: 상품, 카테고리, 상품카테고리 DB 테이블 설계

* test: DB 연결 테스트

* feat: 상품명을 검증하는 Name 객체 구현

* feat: 이미지 경로를 검증하는 ImageUrl 객체 구현

* refactor: ParameterizedTest name 필드 추가

* feat: 상품 가격을 검증하는 Price 객체 구현

* feat: 상품 설명을 검증하는 Description 객체 구현

* feat: ProductEntity 구현

* feat: CategoryEntity 구현

* refactor: 카테고리 디폴트 값으로 초기화

* refactor: ID LONG 타입으로 변경

* feat: ProductCategoryEntity 구현

* docs: DB 테이블과 엔티티 구현 완료

* feat: CategoryDao 구현

* feat: ProductDao 구현

* feat: ProductDao 전체 조회 기능 구현

* feat: ProductDao 삭제 기능 구현

* refactor: equals&hashCode 재정의

* refactor: Id null 검증 로직 추가

* refactor: equals&hashCode 메서드 위치 변경

* feat: 상품 Id로 조회 기능 추가

* feat: 상품 수정 기능 추가

* feat: ProductCategoryDao 구현

* feat: 상품카테고리 조회 기능 구현

* refactor: Id null 검증 로직 추가

* feat: 상품 카테고리 삭제 기능 구현

* docs: 사용하지 않는 기능 삭제

* feat: 상품 생성 비즈니스 로직 구현

* feat: 상품 목록 조회 비즈니스 로직 추가

* feat: 상품 정보 수정 비즈니스 로직 추가

* feat: 상품 정보 수정 비즈니스 로직 추가

* feat: 상품 정보 삭제 비즈니스 로직 구현

* feat: 상품 생성 컨트롤러 API 메서드 구현

* feat: 상품 업데이트 컨트롤러 API 메서드 구현

* feat: 상품 삭제 컨트롤러 API 메서드 구현

* refactor: 클래스에 final 키워드 사용

* refactor: 프록시를 생성하는 객체에 한하여 final 키워드 제거

* feat: 페이지 생성 컨트롤러 구현

* feat: 상품 조회 컨트롤러 API 메서드 구현

* feat: 상품 정보 출력, 입력 뷰 수정

* refactor: 동적 뷰 생성 로직 수정

* refactor: 서비스 메서드 트랜잭션 적용

* refactor: 상품 테이블 이미지URL 컬럼 TEXT 타입으로 변경

* feat: ExceptionAdvice 객체 구현

* feat: Spring Validation 기능 추가

* refactor: 초기 카테고리에 없음 추가

* refactor: 검증 역할 서비스로 이동

* refactor: 이미지 URL 길이 제한 제거

* chore: 불필요한 출력문 제거

* feat: 서버 로그 추가

* refactor: 불필요한 imageUrl 테스트 제거

* refactor: 코드 컨벤션 수정

* feat: 서버 예외 처리 기능 구현

---------

Co-authored-by: splitube <[email protected]>
Copy link
Member

@drunkenhw drunkenhw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

허브신 위에 다즐신이네요 ㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷ

@@ -14,6 +14,9 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-validation'

implementation 'org.springdoc:springdoc-openapi-ui:1.7.0'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh what is this?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Swagger를 사용하기 위한 의존성 추가입니다~

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

스웩 ~

this.cartService = cartService;
}

@Operation(summary = "장바구니 목록 조회 API", description = "사용자의 모든 장바구니 목록을 조회합니다.")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아 스웨거 더러워요

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ㅋㅋ ㅈㅅ

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

동의합니다! 간단한 api + 리뷰어가 Swagger 사용 추천 + 안써봤으니 경험으로 사용하게 되었습니다 ㅎㅎ

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

스웨거 좋네요


@Operation(summary = "상품 삭제 API", description = "해당 상품을 삭제한다.")
@DeleteMapping(path = "/{id}")
public ResponseEntity<Void> removeProduct(@PathVariable(name = "id") Long productId) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@PathVariable(name = "id") 왜 이거씀? 그냥 path에 /{productId} 박으면 안되나용

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/products/{productId}로 봤을 때는 중복되어 보이는 것 같아서 ..

가져와서 사용할 때는 어떤 ID인지 헷갈리는 것 같아서 ..

Comment on lines +27 to +33
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
final String authorization = request.getHeader(AUTHORIZATION_HEADER_NAME);
final MemberAuthDto memberAuthDto = basicAuthenticationExtractor.extract(authorization);
memberService.findMember(memberAuthDto);
return true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

권한만 체크해주는거 맞죵?

Comment on lines +23 to +26
(rs, rowNum) -> new MemberEntity(
rs.getLong("id"),
rs.getString("email"),
rs.getString("password")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rowMapper 상수 춫ㄹ해주세요


public final class MemberAuthDto {

@Schema(description = "멤버 이메일")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

스웨거 싫어 스웩이 없네

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

스웨ㅔㅇ~

Comment on lines +49 to +58
for (FieldError fieldError : bindingResult.getFieldErrors()) {
stringBuilder.append("[");
stringBuilder.append(fieldError.getField());
stringBuilder.append("](은)는 ");
stringBuilder.append(fieldError.getDefaultMessage());
stringBuilder.append(" 입력된 값 : [");
stringBuilder.append(fieldError.getRejectedValue());
stringBuilder.append("]");
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ㅋㅋ 정성 멋지네요

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

칭찬 받으려고 해봤습니다

}

@Transactional
public Long putInCart(final Long productId, final Long memberId) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

메소드명 굿

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

풑읻캍투

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

풑인카트 ㄷㄷㄷ;

Comment on lines +69 to +76
private ValidatableResponse cartFindApi(final String auth) {
return RestAssured.given()
.contentType(MediaType.APPLICATION_JSON_VALUE)
.header("Authorization", "Basic " + auth)
.when()
.get("/carts")
.then();
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

중복코드 추출 멋지네여

Comment on lines +71 to +74
assertThat(productDao.findAll()).hasSize(2);
assertThat(productDao.findAll()).map(ProductEntity::getId)
.containsExactly(savedFirstProductId, savedSecondProductId);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assertAll

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

단속반한테 검거 당했네요

Copy link
Member

@greeng00se greeng00se left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

다즐신 ㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷㄷ

this.cartService = cartService;
}

@Operation(summary = "장바구니 목록 조회 API", description = "사용자의 모든 장바구니 목록을 조회합니다.")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

스웨거 좋네요

Comment on lines +26 to +42
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<ErrorResponseDto> handleException(final IllegalArgumentException e) {
logger.error(e.getMessage());
return ResponseEntity.badRequest().body(new ErrorResponseDto(e.getMessage()));
}

@ExceptionHandler(MissingServletRequestParameterException.class)
public ResponseEntity<ErrorResponseDto> handleException(final MissingServletRequestParameterException e) {
logger.error(e.getMessage());
return ResponseEntity.badRequest().body(new ErrorResponseDto(e.getMessage()));
}

@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public ResponseEntity<ErrorResponseDto> handleException(final MethodArgumentTypeMismatchException e) {
logger.error(e.getMessage());
return ResponseEntity.badRequest().body(new ErrorResponseDto(e.getMessage()));
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

예상된 에러의 경우 로그 레벨을 warn 정도로 조정해보는건 어떨까?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

좋습니다 😍

}

@Transactional
public Long putInCart(final Long productId, final Long memberId) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

풑인카트 ㄷㄷㄷ;

Comment on lines +7 to +15
CREATE TABLE PRODUCT
(
id BIGINT NOT NULL AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
image_url TEXT NOT NULL,
price INT NOT NULL,
description VARCHAR(255),
PRIMARY KEY (id)
);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

schema.sql로 분리해보는건 어떨까?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

좋습니다 🩵

Comment on lines +20 to +22
@Nested
@DisplayName("인증 토큰에서 정보 추출 시 ")
class Extract {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

네스티드 고..수

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

토니 그립습니다 ..

.statusCode(HttpStatus.BAD_REQUEST.value());
}

private ValidatableResponse cartPutApi(final String auth, final String productId) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

u gosu?

Copy link
Author

@woo-chang woo-chang May 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고수 싫어합니다

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

다즐도 점점 ..

spring:
h2:
console:
enabled: true
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

테스트에서는 필요없는 옵션일수도?!

DELETE
FROM CART;
DELETE
FROM PRODUCT;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

멤버만 일부러 안지운거구나! ㅏ다즐 천재

import org.springframework.http.MediaType;
import org.springframework.test.context.jdbc.Sql;

@Sql("/init.sql")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이거 쓰면 이 테스트 실행하기 전에 한 번 이 파일을 실행하고 가는거야?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

클래스 레벨, 메소드 레벨 둘 다 적용가능한 어노테이션인데 클래스 레벨에 두면 해당 클래스의 모든 메서드 실행 전 실행됩니다 ㅎㅎ

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아하 @Sql는 강제로 sql실행실켜주는 애??

RestAssured.port = port;
}

@Nested

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

네스티드 굳~

void findCartItemsForMember() {
final Long savedProductId = productService.registerProduct(ProductRequestFixture.request);
final Long savedCartId = cartService.putInCart(savedProductId, 1L);
final String encodedAuth = new String(Base64.getEncoder().encode("[email protected]:password1".getBytes()));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

더미 데이터에 의존하지 않고
테스트 코드에서 실제로 추가하고 나서 바로 테스트하는식으로 바꿔볼 수 있어??
(내가 보려고 ㅋ)

Copy link

@hanueleee hanueleee left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

다즐 테스트 너무 꼼꼼해서 멋져

@@ -14,6 +14,9 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-validation'

implementation 'org.springdoc:springdoc-openapi-ui:1.7.0'

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

스웩 ~

try {
return Optional.ofNullable(jdbcTemplate.queryForObject(
sql,
(rs, rowNum) -> new CartEntity(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BeanPropertyRowMapper를 사용해보는건 어떨까요?

this.productId = productId;
}

public boolean isOwner(final Long memberId) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오 이거 멋져요. 근데 entity에도 getter말고 다른 로직(?)이 들어가도 되나요?

this.description = new Description(description);
}

public void update(final String name, final String imageUrl, final Integer price, final String description) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

entity를 새로 만들어주지 않고 update(사실상 setter)하는 이유는 먼가요??


@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponseDto> handleException(final MethodArgumentNotValidException e) {
final BindingResult bindingResult = e.getBindingResult();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BindingResult가 먼가요?

this.message = message;
}

public HttpStatus getHttpStatus() {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

멋지다 다즐

url: jdbc:h2:mem:testdb;MODE=MySQL
driver-class-name: org.h2.Driver

springdoc:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이거 먼가요?

import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest;

@JdbcTest
class ConnectionTest {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

꼼꼼한 테스트 굿굿

import org.springframework.http.MediaType;
import org.springframework.test.context.jdbc.Sql;

@Sql("/init.sql")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아하 @Sql는 강제로 sql실행실켜주는 애??

.statusCode(HttpStatus.BAD_REQUEST.value());
}

private ValidatableResponse cartPutApi(final String auth, final String productId) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

다즐도 점점 ..

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants