Skip to content

πŸš€ 2단계 - μž₯λ°”κ΅¬λ‹ˆ κΈ°λŠ₯#38

Open
hongmoSung wants to merge 5 commits into
next-step:hongmosungfrom
hongmoSung:step2
Open

πŸš€ 2단계 - μž₯λ°”κ΅¬λ‹ˆ κΈ°λŠ₯#38
hongmoSung wants to merge 5 commits into
next-step:hongmosungfrom
hongmoSung:step2

Conversation

@hongmoSung
Copy link
Copy Markdown

  • μ‚¬μš©μž κΈ°λŠ₯ κ΅¬ν˜„
  • μ‚¬μš©μž μ„€μ • νŽ˜μ΄μ§€ 연동
  • μž₯λ°”κ΅¬λ‹ˆ κΈ°λŠ₯ κ΅¬ν˜„
  • μž₯λ°”κ΅¬λ‹ˆ νŽ˜μ΄μ§€ 연동

- μ‚¬μš©μž κΈ°λŠ₯ κ΅¬ν˜„
- μ‚¬μš©μž μ„€μ • νŽ˜μ΄μ§€ 연동
- μž₯λ°”κ΅¬λ‹ˆ κΈ°λŠ₯ κ΅¬ν˜„
- μž₯λ°”κ΅¬λ‹ˆ νŽ˜μ΄μ§€ 연동
Copy link
Copy Markdown

@Gomding Gomding left a comment

Choose a reason for hiding this comment

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

μ•ˆλ…•ν•˜μ„Έμš” 홍λͺ¨λ‹˜!!
μž₯λ°”κ΅¬λ‹ˆ λ―Έμ…˜ 2단계도 λΉ λ₯΄κ²Œ μ§„ν–‰ν•΄μ£Όμ…¨λ„€μš” πŸ‘ πŸ‘ πŸ‘
1단계 merge ν•˜λ©° λ‚¨κ²Όλ˜ λ¦¬λ·°λŠ” 확인이 μ•ˆλœκ²ƒ κ°™μ•„ λ¦¬λ§ˆμΈλ“œ λ“œλ¦½λ‹ˆλ‹€..!
λͺ‡ κ°€μ§€ μ½”λ©˜νŠΈ λ‚¨κ²ΌμœΌλ‹ˆ ν™•μΈλΆ€νƒλ“œλ €μš” πŸ˜„

κΆκΈˆν•œ 점 있으면 μ–Έμ œλ“  νŽΈν•˜κ²Œ DM λ‚¨κ²¨μ£Όμ„Έμš”~

Comment on lines +37 to +43
CartMember cartMember;
try {
cartMember = jdbcTemplate.queryForObject(sql, carMemberRowMapper(), email, password);
} catch (Exception e) {
return Optional.empty();
};
return Optional.ofNullable(cartMember);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

CartMember λ³€μˆ˜λ₯Ό ν• λ‹Ήν•˜λŠ” 과정이 ν•„μš”ν•˜μ§€ μ•Šμ•„ λ³΄μ—¬μš”!
λ³€μˆ˜λ₯Ό ν• λ‹Ήν•˜μ§€ μ•Šκ³  각각의 상황에 λ°”λ‘œ λ°˜ν™˜ν•˜λŠ” 것은 μ–΄λ–»κ²Œ μƒκ°ν•˜μ‹œλ‚˜μš”?

Suggested change
CartMember cartMember;
try {
cartMember = jdbcTemplate.queryForObject(sql, carMemberRowMapper(), email, password);
} catch (Exception e) {
return Optional.empty();
};
return Optional.ofNullable(cartMember);
try {
return Optional.ofNullable(jdbcTemplate.queryForObject(sql, carMemberRowMapper(), email, password));
} catch (Exception e) {
return Optional.empty();
};

Comment on lines +46 to +47
private RowMapper<CartMember> carMemberRowMapper() {
return (rs, rowNum) -> {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

ν•΄λ‹Ή λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•  λ•Œλ§ˆλ‹€ RowMapper κ°€ μƒμ„±λ κ²ƒμœΌλ‘œ λ³΄μ—¬μš” :)
ν•„λ“œλ‘œ λ§Œλ“€μ§€ μ•ŠμœΌμ‹  μ΄μœ κ°€ μžˆμ„κΉŒμš”?

Suggested change
private RowMapper<CartMember> carMemberRowMapper() {
return (rs, rowNum) -> {
private final RowMapper<CartMember> carMemberRowMapper = (rs, rowNum) -> {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

μ•— μ²˜μŒμ— μ•Œλ €μ£Όμ‹  λ‚΄μš©μ„ λ°”λ‘œ μ΄ν•΄ν•˜μ§€ λͺ»ν•˜μ—¬μ„œ λ”°λ‘œ λ©”μ„œλ“œλ‘œ λΆ„λ¦¬ν•˜μ—¬ λ§Œλ“€μ—ˆμ—ˆμŠ΅λ‹ˆλ‹€.
μ•Œλ €μ£Όμ‹  λ‚΄μš©μ²˜λŸΌ ν•„λ“œλ‘œ λ§Œλ“€μ–΄ μˆ˜μ •ν•˜μ˜€μŠ΅λ‹ˆλ‹€.

Comment thread src/main/resources/data.sql Outdated
'https://i.namu.wiki/i/6dLtHMNcLvz8BWVrtEB9CNEPqjDEhLIbtEpnZdIcbxZ4TZPc-P-Yk69qoEWSIaCudos3zI0mT-jw89Lm881FHg.webp',
now());

create table CART_MEMBER
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

CART_MEMBER λΌλŠ” 이름은 CART λΌλŠ” μ„œλΉ„μŠ€μ— λ„ˆλ¬΄ ν•œμ •λ˜μ–΄ μžˆλŠ” λŠλ‚Œμ΄κ΅°μš” πŸ€”
λ‚˜μ€‘μ— λ‹€λ₯Έ κΈ°λŠ₯μ—μ„œ μ‚¬μš©ν•˜λ©΄ μ–΄μƒ‰ν•œ ν…Œμ΄λΈ”μ΄ λ˜μ§€ μ•Šμ„κΉŒμš”? πŸ€”

}

@Override
public List<ViewCartItem> getCartItems(Long memberId) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Repository μ—μ„œ presentation μ˜μ—­μ˜ DTOλ₯Ό λ°˜ν™˜ν•΄μ£Όκ³  μžˆκ΅°μš” πŸ€”
persistance μ˜μ—­μ—μ„œ View 의 λ‚΄μš©κΉŒμ§€ μ•Œμ•„λ²„λ¦¬κ²Œλ˜λ‹ˆ
μ‘λ‹΅κ°’μ˜ 변화에 persistance μ˜μ—­μ΄ 영ν–₯을 λ°›κ²Œ 될 수 μžˆκ² λ„€μš”..!

entity λΌλŠ” 객체λ₯Ό λ§Œλ“€μ–΄ μ£Όμ…¨μŒμ—λ„ View μ˜μ—­μ˜ DTO λ₯Ό λ”°λ‘œ μ‚¬μš©ν•˜μ‹  μ΄μœ κ°€ κΆκΈˆν•΄μš”!

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

화면에 λ…ΈμΆœν•΄μ•Όν•  데이터λ₯Ό λ§žμΆ”κΈ° μœ„ν•΄
join 으둜 ν•„μš”ν•œ 데이터λ₯Ό λ§žμΆ”λ‹€ λ³΄λ‹ˆ View μ˜μ—­μ˜ dto κ°€ μ ν•©ν•˜λ‹€ 생각이 λ“€μ–΄
μ‚¬μš©ν•˜μ˜€μŠ΅λ‹ˆλ‹€. πŸ˜…

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Repository μ—μ„œλŠ” μ €μž₯μ†Œμ— μžˆλŠ” 데이터λ₯Ό μ‘°νšŒν•˜κ³  μ‘λ‹΅ν•΄μ£ΌλŠ” 것 κΉŒμ§€κ°€ μ±…μž„μ΄λΌκ³  μƒκ°ν•΄μš”!

View μ—κ²Œ μ–΄λ–»κ²Œ 응닡될 μ§€λŠ” 관심사 밖이 μ•„λ‹κΉŒμš”? πŸ€”
Entity κ°μ²΄λ‚˜ 도메인 객체에 λ§€ν•‘ν•˜λŠ” 방법도 κ³ λ €ν•΄λ³Ό 수 μžˆκ² λ„€μš” πŸ˜„

Comment on lines +61 to +64
int updateCount = jdbcTemplate.update(sql, id);
if (updateCount != 1) {
throw new RuntimeException(id + "에 ν•΄λ‹Ήν•˜λŠ” μž₯λ°”κ΅¬λ‹ˆ 아이탬을 μ‚­μ œν•˜λŠ”λ° 였λ₯˜κ°€ λ°œμƒν•˜μ˜€μŠ΅λ‹ˆλ‹€.");
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

1단계 merge ν•˜λ©΄μ„œ λ‚¨κ²Όλ˜ 뢀뢄에 λŒ€ν•œ μ˜κ²¬λ„ ν™•μΈλΆ€νƒλ“œλ €μš” ! πŸ˜„

delete μ‹œ DAO λŠ” updateCount λ§Œμ„ λ°˜ν™˜ν•˜κ³ 
μ˜ˆμ™Έμ²˜λ¦¬λŠ” ν˜ΈμΆœν•˜λŠ” μͺ½μ—μ„œ 해보면 μ–΄λ–¨κΉŒμš”?

DAO λŠ” κ°€λŠ₯ν•˜λ©΄ λ°”κΉ₯μ—μ„œ μ–΄λ–»κ²Œ μ‚¬μš©λ  지에 관심을 κ°€μ§€μ§€ μ•Šλ„λ‘ κ΅¬ν˜„ν•˜λŠ”κ²ƒμ΄ μ’‹μŠ΅λ‹ˆλ‹€ :)
μ–΄λ–€ λ‘œμ§μ—μ„œλŠ” μ‚­μ œν•  λ•Œ λŒ€μƒμ΄ 없어도 μ˜ˆμ™Έκ°€ λ°œμƒν•˜μ§€ μ•Šμ•„μ•Όν•  μˆ˜λ„ 있겠죠?

#34 (comment)

return Optional.ofNullable(new AuthInfo(email, password));
}

return Optional.empty();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

인증 값을 μΆ”μΆœν•  수 μ—†μœΌλ©΄ Optional.emtpy() λ₯Ό λ°˜ν™˜ν•˜λ„λ‘ ν•΄μ£Όμ…¨κ΅°μš” πŸ’―

Comment on lines +28 to +33
Optional<AuthInfo> optionalAuthInfo = auth.extract(request);

if (optionalAuthInfo.isEmpty()) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();

}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

μ‚¬μš©μž 인증을 μœ„ν•΄ 같은 둜직이 μ€‘λ³΅μ‚¬μš©λ˜κ³  μžˆκ΅°μš”!

Interceptor 와 ArgumanetResolver 에 λŒ€ν•œ ν•™μŠ΅ ν…ŒμŠ€νŠΈλ₯Ό μ§„ν–‰ν•΄λ³ΌκΉŒμš”?

import java.util.Optional;

@Repository
public class MemberJdbcRepository implements MemberRepository {
Copy link
Copy Markdown

@Gomding Gomding May 15, 2023

Choose a reason for hiding this comment

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

ν…ŒμŠ€νŠΈλ“€μ„ μ μ ˆν•˜κ²Œ μž‘μ„±ν•΄λ³΄μ…¨μœΌλ©΄ μ’‹κ² μ–΄μš”~!!
μŠ€ν”„λ§μ—μ„œ μ œκ³΅ν•˜λŠ” ν…ŒμŠ€νŠΈ 도ꡬ듀을 ν™œμš©ν•΄λ³΄μ£  :)

Spring ν”„λ ˆμž„μ›Œν¬λ₯Ό μ‚¬μš©ν•œ ν…ŒμŠ€νŠΈλ„ μž‘μ„±ν•΄λ³΄λ©΄ μ–΄λ–¨κΉŒμš”?

κ°„λ‹¨ν•˜κ²Œ @jdbcTest λΌλŠ” μ• λ„ˆν…Œμ΄μ…˜μ„ μ‚¬μš©ν•΄μ„œ Repository ν…ŒμŠ€νŠΈλΆ€ν„° μž‘μ„±ν•΄λ³΄μ‹œλ©΄ μ’‹κ² μ–΄μš”!
https://www.baeldung.com/spring-jdbctemplate-testing

+) 더 λ‚˜μ•„κ°€μ„œ Service , Controller ν…ŒμŠ€νŠΈλ„ μž‘μ„±ν•΄λ³΄μ‹œλ©΄ μ’‹κ² κ΅°μš” πŸ˜„
Controller ν…ŒμŠ€νŠΈλŠ” ProductIntegrationTest 에 μƒ˜ν”Œ ν…ŒμŠ€νŠΈκ°€ μžˆμœΌλ‹ˆ λ‹€λ₯Έ path 듀도 ν…ŒμŠ€νŠΈλ₯Ό μž‘μ„±ν•΄λ³Ό 수 μžˆκ² λ„€μš”!

}

public List<ViewMember> getMembers() {
return memberRepository.getMembers().stream().map(ViewMember::new)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

λ©”μ„œλ“œ 체이닝을 ν•  λ•ŒλŠ” 각 λ©”μ„œλ“œλ§ˆλ‹€ κ°œν–‰ν•΄μ£ΌλŠ” 것이 가독성이 μ’‹λ‹€κ³  μƒκ°ν•΄μš”~

Suggested change
return memberRepository.getMembers().stream().map(ViewMember::new)
return memberRepository.getMembers()
.stream()
.map(ViewMember::new)

Copy link
Copy Markdown

@Gomding Gomding left a comment

Choose a reason for hiding this comment

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

μ•ˆλ…•ν•˜μ„Έμš” 홍λͺ¨λ‹˜!
Interceptor 잘 κ΅¬ν˜„ν•΄μ£Όμ…¨κ΅°μš” πŸ‘
μΆ”κ°€λ‘œ λͺ‡κ°€μ§€ μ½”λ©˜νŠΈ λ‚¨κ²ΌμœΌλ‹ˆ 확인 λΆ€νƒλ“œλ €μš”!!

κΆκΈˆν•œ 점 μžˆμœΌμ‹œλ©΄ μ–Έμ œλ“  νŽΈν•˜κ²Œ DM λ‚¨κ²¨μ£Όμ„Έμš” πŸ™‡

import java.util.List;

@Service
public class ItemService {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

μ–΄λ–€ Item 에 λŒ€ν•œ Service 인지 ν΄λž˜μŠ€μ΄λ¦„λ§Œ 보고 ν™•μ‹ ν•  수 μ—†μ–΄λ³΄μ—¬μš”!
CartItemService λŠ” μ–΄λ–»κ²Œ μƒκ°ν•˜μ‹œλ‚˜μš”?


private final MemberRepository memberRepository;
private final ProductRepository productRepository;
private final ItemRepository itemRepository;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

ItemRepository 도 같은 μ˜κ²¬μž…λ‹ˆλ‹€ πŸ˜„


public void addToCart(AuthInfo authInfo, RequestAddItem addItem) {
CartMember member = memberRepository.getMember(authInfo.getEmail(), authInfo.getPassword())
.orElseThrow(() -> new RuntimeException(authInfo + "에 ν•΄λ‹Ήν•˜λŠ” 맴버가 μ—†μŠ΅λ‹ˆλ‹€."));
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

ν˜„μž¬ Application μ—μ„œ μ—λŸ¬κ°€ λ°œμƒν•˜λ©΄ ν΄λΌμ΄μ–ΈνŠΈμ—κ²Œ μ–΄λ–»κ²Œ μ‘λ‹΅λ˜κ³  μžˆμ„κΉŒμš”? πŸ˜„

ControllerAdvice와 ExceptionHandler 에 λŒ€ν•΄ ν•™μŠ΅ν•΄λ³ΌκΉŒμš”!
https://tecoble.techcourse.co.kr/post/2021-05-10-controller_advice_exception_handler/

import java.util.Optional;

@Component
public class MyInterceptor implements HandlerInterceptor {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

HandlerInterceptor λ₯Ό μ‚¬μš©ν•΄μ£Όμ…¨κ΅°μš” πŸ‘

Comment on lines +28 to +33
Optional<AuthInfo> optionalAuthInfo = auth.extract(request);

if (optionalAuthInfo.isEmpty()) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();

}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

  1. Interceptor μ—μ„œ μ‚¬μš©μž 인증을 μ§„ν–‰ν–ˆλŠ”λ° ν•œλ²ˆ 더 κ²€μ‚¬ν•˜μ‹œλŠ” μ΄μœ κ°€ κΆκΈˆν•΄μš”!
  2. λ§Œμ•½ AuthInfo λΌλŠ” 정보가 ν•„μš”ν•˜λ‹€λ©΄ ArgumentResolver λ₯Ό μ‚¬μš©ν•΄λ³΄λŠ”κ±΄ μ–΄λ–¨κΉŒμš” :)

Comment on lines +17 to +21
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
Optional<AuthInfo> optionalAuthInfo = auth.extract(request);
return optionalAuthInfo.isPresent();
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Header 에 μ‚¬μš©μž 정보가 λ“€μ–΄μžˆλŠ”μ§€λ§Œ ν™•μΈν•˜λ©΄ μ‚¬μš©μž 인증이 됐닀고 ν•  수 μžˆμ„κΉŒμš”? πŸ€”

μ‚¬μš©μžμ˜ 정보가 μ‹€μ œ μ‚¬μš©μž 정보와 μΌμΉ˜ν•˜λŠ”μ§€ ν™•μΈν•΄μ£Όμ–΄μ•Όν•˜μ§€ μ•Šμ„κΉŒμš”?
ν˜„μž¬λŠ” email κ³Ό password 정보가 λ“€μ–΄μžˆμœΌλ‹ˆ μ €μž₯μ†Œμ— μžˆλŠ” μ‚¬μš©μž 정보와 비ꡐ해볼 수 μžˆκ² λ„€μš” πŸ˜„

import java.util.Optional;

@Component
public class MyInterceptor implements HandlerInterceptor {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

인증을 μœ„ν•œ Interceptor μ΄λ‹ˆ 그에 λ§žλŠ” 이름을 μ§€μ–΄μ£Όλ©΄ μ–΄λ–¨κΉŒμš”? πŸ™Œ


import static org.assertj.core.api.AssertionsForClassTypes.assertThat;

class MemberJdbcRepositoryTest {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@JdbcTest λ₯Ό ν™œμš©ν•˜λ©΄ Repository λΉˆμ„ 을 μ£Όμž…λ°›μ•„ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€ :)
μŠ€ν”„λ§μ—μ„œ ν…ŒμŠ€νŠΈλ₯Ό μ‰½κ²Œν•  수 μžˆλ„λ‘ μ œκ³΅ν•˜λŠ” κΈ°λŠ₯이죠 πŸ˜„

+) @Sql μ• λ„ˆν…Œμ΄μ…˜μ„ μ‚¬μš©ν•˜λ©΄ sql 슀크립트 싀행이 κ°€λŠ₯ν•©λ‹ˆλ‹€ :)

단 @JdbcTest μ• λ„ˆν…Œμ΄μ…˜μ„ μ‚¬μš©ν•˜λ©΄ ν…ŒμŠ€νŠΈ μ‹€ν–‰ μ „~ν›„ λ₯Ό νŠΈλžœμž­μ…˜ λ²”μœ„λ‘œ μž‘μ•„μ„œ
각 ν…ŒμŠ€νŠΈ λ©”μ„œλ“œ μ‹€ν–‰ ν›„ 둀백을 μ‹€ν–‰ν•©λ‹ˆλ‹€.

즉, 각 ν…ŒμŠ€νŠΈ λ©”μ„œλ“œμ—μ„œ μ‹€ν–‰ν•œ 쿼리듀은 둀백이 λ˜μ–΄ ν…ŒμŠ€νŠΈ λ…λ¦½μ μœΌλ‘œ 싀행이 κ°€λŠ₯ν•΄μ§‘λ‹ˆλ‹€.

sql 슀크립트 λŒ€μ‹  @BeforeEachμ—μ„œ 더미데이터λ₯Ό λ„£μ–΄μ£ΌλŠ”κ±΄ μ–΄λ–¨κΉŒμš”?

Suggested change
class MemberJdbcRepositoryTest {
@Sql("classpath:test-data.sql")
@JdbcTest(
includeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, value = Repository.class)
}
)
class MemberJdbcRepositoryTest {
@Autowired
private MemberRepository memberRepository;

import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

WebConfig μ„€μ • πŸ‘

Comment on lines +16 to +17
@Service
public class ItemService {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Repository ν…ŒμŠ€νŠΈλ₯Ό μž‘μ„±ν•΄μ£Όμ…¨μœΌλ‹ˆ λ‹€μŒμ€ Service ν…ŒμŠ€νŠΈλ„ μž‘μ„±ν•΄λ³ΌκΉŒμš”? πŸ˜„
@SpringBootTest μ• λ„ˆν…Œμ΄μ…˜μ„ μ‚¬μš©ν•˜λ©΄ ν…ŒμŠ€νŠΈμ—μ„œ μŠ€ν”„λ§ ν™˜κ²½κ³Ό λΉ„μŠ·ν•˜κ²Œ ν…ŒμŠ€νŠΈκ°€ κ°€λŠ₯ν•©λ‹ˆλ‹€ :)
λΉˆμ„ μ£Όμž…λ°›λŠ”κ²ƒλ„ κ°€λŠ₯ν•˜μ£  :)

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.

2 participants