Skip to content

Commit

Permalink
Feat: 사용자는 링크 허브를 생성할 수 있다.
Browse files Browse the repository at this point in the history
Feat: 사용자는 링크 허브를 생성할 수 있다.
  • Loading branch information
hseong3243 authored Feb 10, 2024
2 parents c05b409 + d1e008e commit beef594
Show file tree
Hide file tree
Showing 21 changed files with 631 additions and 5 deletions.
12 changes: 12 additions & 0 deletions src/docs/asciidoc/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,15 @@ operation::link-controller-test/find-links[snippets='http-request,request-header
==== response

operation::link-controller-test/find-links[snippets='http-response,response-fields']

== 허브

=== 허브 생성

==== request

operation::hub-controller-test/create-hub[snippets='http-request,request-headers,request-fields']

==== response

operation::hub-controller-test/create-hub[snippets='http-response,response-fields']
50 changes: 50 additions & 0 deletions src/main/java/com/seong/shoutlink/domain/hub/Hub.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.seong.shoutlink.domain.hub;

import com.seong.shoutlink.domain.exception.ErrorCode;
import com.seong.shoutlink.domain.exception.ShoutLinkException;
import java.text.MessageFormat;
import java.util.Objects;
import lombok.Getter;

@Getter
public class Hub {

private static final int NAME_MAX_SIZE = 30;
private static final String DESCRIPTION_DEFAULT = "";
private static final int DESCRIPTION_MAX_SIZE = 200;

private Long hubId;
private String name;
private String description;
private boolean isPrivate;

public Hub(String name, String description, boolean isPrivate) {
this.name = validateName(name);
this.description = validateDescription(description);
this.isPrivate = isPrivate;
}

private String validateName(String name) {
if (Objects.isNull(name)) {
throw new ShoutLinkException("허브 이름은 필수입니다.", ErrorCode.ILLEGAL_ARGUMENT);
}
if(name.isEmpty() || name.length() > NAME_MAX_SIZE) {
throw new ShoutLinkException(
MessageFormat.format("허브 이름은 1자 이상, {0}자 이하여야 합니다.", NAME_MAX_SIZE),
ErrorCode.ILLEGAL_ARGUMENT);
}
return name;
}

private String validateDescription(String description) {
if(Objects.isNull(description)) {
return DESCRIPTION_DEFAULT;
}
if(description.length() > DESCRIPTION_MAX_SIZE) {
throw new ShoutLinkException(
MessageFormat.format("허브 설명은 {0}자 이하여야 합니다.", DESCRIPTION_MAX_SIZE),
ErrorCode.ILLEGAL_ARGUMENT);
}
return description;
}
}
16 changes: 16 additions & 0 deletions src/main/java/com/seong/shoutlink/domain/hub/HubWithMembers.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.seong.shoutlink.domain.hub;

import com.seong.shoutlink.domain.member.Member;
import lombok.Getter;

@Getter
public class HubWithMembers {

private final Hub hub;
private final Member member;

public HubWithMembers(Hub hub, Member member) {
this.hub = hub;
this.member = member;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.seong.shoutlink.domain.hub.controller;

import com.seong.shoutlink.domain.auth.LoginUser;
import com.seong.shoutlink.domain.hub.controller.request.CreateHubRequest;
import com.seong.shoutlink.domain.hub.service.HubService;
import com.seong.shoutlink.domain.hub.service.request.CreateHubCommand;
import com.seong.shoutlink.domain.hub.service.response.CreateHubResponse;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequiredArgsConstructor
@RequestMapping("/api")
public class HubController {

private final HubService hubService;

@PostMapping("/hubs")
public ResponseEntity<CreateHubResponse> createHub(
@RequestBody @Valid CreateHubRequest request,
@LoginUser Long memberId) {
CreateHubResponse response = hubService.createHub(new CreateHubCommand(
memberId,
request.name(),
request.description(),
request.isPrivate()));
return ResponseEntity.status(HttpStatus.CREATED).body(response);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.seong.shoutlink.domain.hub.controller.request;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;

public record CreateHubRequest(
@NotBlank(message = "허브 이름은 공백일 수 없습니다.")
String name,
String description,
@NotNull(message = "허브 공개 여부는 필수입니다.")
Boolean isPrivate) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.seong.shoutlink.domain.hub.repository;

import com.seong.shoutlink.domain.hub.Hub;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class HubEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long hubId;

@Column(nullable = false)
private String name;

@Column(nullable = false)
private String description;

@Column(nullable = false)
private boolean isPrivate;

private HubEntity(String name, String description, boolean isPrivate) {
this.name = name;
this.description = description;
this.isPrivate = isPrivate;
}

public static HubEntity create(Hub hub) {
return new HubEntity(hub.getName(), hub.getDescription(), hub.isPrivate());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.seong.shoutlink.domain.hub.repository;

import org.springframework.data.jpa.repository.JpaRepository;

public interface HubJpaRepository extends JpaRepository<HubEntity, Long> {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.seong.shoutlink.domain.hub.repository;

import com.seong.shoutlink.domain.hub.HubWithMembers;
import com.seong.shoutlink.domain.hub.service.HubRepository;
import com.seong.shoutlink.domain.hubMember.repository.HubMemberEntity;
import com.seong.shoutlink.domain.hubMember.repository.HubMemberJpaRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;

@Repository
@RequiredArgsConstructor
public class HubRepositoryImpl implements HubRepository {

private final HubJpaRepository hubJpaRepository;
private final HubMemberJpaRepository hubMemberJpaRepository;

@Override
public Long save(HubWithMembers hubWithMembers) {
HubEntity hubEntity = HubEntity.create(hubWithMembers.getHub());
hubJpaRepository.save(hubEntity);
HubMemberEntity hubMemberEntity
= HubMemberEntity.create(hubWithMembers.getHub(), hubWithMembers.getMember());
hubMemberJpaRepository.save(hubMemberEntity);
return hubEntity.getHubId();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.seong.shoutlink.domain.hub.service;

import com.seong.shoutlink.domain.hub.HubWithMembers;

public interface HubRepository {

Long save(HubWithMembers hubWithMembers);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.seong.shoutlink.domain.hub.service;

import com.seong.shoutlink.domain.common.EventPublisher;
import com.seong.shoutlink.domain.exception.ErrorCode;
import com.seong.shoutlink.domain.exception.ShoutLinkException;
import com.seong.shoutlink.domain.hub.Hub;
import com.seong.shoutlink.domain.hub.HubWithMembers;
import com.seong.shoutlink.domain.hub.service.event.CreateHubEvent;
import com.seong.shoutlink.domain.hub.service.request.CreateHubCommand;
import com.seong.shoutlink.domain.hub.service.response.CreateHubResponse;
import com.seong.shoutlink.domain.member.Member;
import com.seong.shoutlink.domain.member.service.MemberRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class HubService {

private final MemberRepository memberRepository;
private final HubRepository hubRepository;
private final EventPublisher eventPublisher;

public CreateHubResponse createHub(CreateHubCommand command) {
Member member = getMember(command.memberId());
Hub hub = new Hub(command.name(), command.description(), command.isPrivate());
Long hubId = hubRepository.save(new HubWithMembers(hub, member));
eventPublisher.publishEvent(new CreateHubEvent(hubId));
return new CreateHubResponse(hubId);
}

private Member getMember(Long memberId) {
return memberRepository.findById(memberId)
.orElseThrow(() -> new ShoutLinkException("존재하지 않는 사용자입니다.", ErrorCode.NOT_FOUND));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.seong.shoutlink.domain.hub.service.event;

import com.seong.shoutlink.domain.common.Event;

public record CreateHubEvent(Long hubId) implements Event {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.seong.shoutlink.domain.hub.service.request;

public record CreateHubCommand(Long memberId, String name, String description, boolean isPrivate) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.seong.shoutlink.domain.hub.service.response;

public record CreateHubResponse(Long hubId) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.seong.shoutlink.domain.hubMember;

public enum HubMemberRole {
MASTER, PARTICIPANTS
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.seong.shoutlink.domain.hubMember.repository;

import com.seong.shoutlink.domain.hub.Hub;
import com.seong.shoutlink.domain.hubMember.HubMemberRole;
import com.seong.shoutlink.domain.member.Member;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class HubMemberEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long hubMemberId;

@Column(nullable = false)
private Long memberId;

@Column(nullable = false)
private Long hubId;

@Column(nullable = false)
@Enumerated(EnumType.STRING)
private HubMemberRole hubMemberRole;

public HubMemberEntity(Long memberId, Long hubId, HubMemberRole hubMemberRole) {
this.memberId = memberId;
this.hubId = hubId;
this.hubMemberRole = hubMemberRole;
}

public static HubMemberEntity create(Hub hub, Member member) {
return new HubMemberEntity(member.getMemberId(), hub.getHubId(), HubMemberRole.MASTER);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.seong.shoutlink.domain.hubMember.repository;

import org.springframework.data.jpa.repository.JpaRepository;

public interface HubMemberJpaRepository extends JpaRepository<HubMemberEntity, Long> {

}
Loading

0 comments on commit beef594

Please sign in to comment.