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

[스프린트 3] 강수민 - 팀 생성 기능 추가 #31

Merged
merged 2 commits into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/main/java/PNUMEAT/Backend/domain/auth/entity/Member.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import static PNUMEAT.Backend.global.images.ImageConstant.DEFAULT_MEMBER_PROFILE_IMAGE_URL;

import PNUMEAT.Backend.domain.article.entity.Article;
import PNUMEAT.Backend.domain.team_member.entity.TeamMember;
import jakarta.persistence.*;

import java.util.ArrayList;
Expand Down Expand Up @@ -40,6 +41,9 @@ public class Member {
@OneToMany(mappedBy = "member")
private List<Article> articles = new ArrayList<>();

@OneToMany(mappedBy = "member")
private List<TeamMember> teamMembers = new ArrayList<>();

protected Member() {
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package PNUMEAT.Backend.domain.team.controller;

import PNUMEAT.Backend.domain.auth.dto.request.MemberProfileRequest;
import PNUMEAT.Backend.domain.auth.entity.Member;
import PNUMEAT.Backend.domain.auth.service.MemberService;
import PNUMEAT.Backend.domain.team.dto.request.TeamRequest;
import PNUMEAT.Backend.domain.team.service.TeamService;
import PNUMEAT.Backend.global.error.dto.response.ApiResponse;
import PNUMEAT.Backend.global.security.annotation.LoginMember;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import static PNUMEAT.Backend.global.response.ResponseMessageEnum.TEAM_CREATED_SUCCESS;

@RestController
@RequestMapping("/api/v1/teams")
@RequiredArgsConstructor
public class TeamController {

private final TeamService teamService;

@PostMapping
public ResponseEntity<ApiResponse<?>> createTeam(@ModelAttribute @Valid TeamRequest teamRequest,
@RequestPart(value = "teamIcon", required = false) MultipartFile teamIcon,
@LoginMember Member member){
teamService.createTeam(teamRequest, teamIcon, member);

return ResponseEntity.status(TEAM_CREATED_SUCCESS.getStatusCode())
.contentType(MediaType.APPLICATION_JSON)
.body(ApiResponse.createResponseWithMessage(TEAM_CREATED_SUCCESS.getMessage()));
}
}


Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package PNUMEAT.Backend.domain.team.dto.request;

import PNUMEAT.Backend.global.validation.annotation.NotNullOrBlank;
import jakarta.validation.constraints.*;

public record TeamRequest(
@NotNullOrBlank
@Pattern(
regexp = "^[가-힣a-zA-Z0-9\\s]*$",
message = "한글, 영어, 숫자, 공백만 입력 가능합니다."
)
@Size(
max = 10,
message = "글자 수를 초과했습니다."
)
String teamName,

@Pattern(
regexp = "^[가-힣a-zA-Z0-9!@#$%^&*()_+\\-=\\[\\]{};':\"\\\\|,.<>/?`~ ]*$",
message = "팀 설명은 한글, 영어, 숫자, 기본 특수문자만 사용가능합니다."
)
@Size(
max = 50,
message = "글자 수를 초과했습니다."
)
String teamExplain,

@NotNullOrBlank
String topic,

@NotNullOrBlank
@Digits(integer = Integer.MAX_VALUE, fraction = 0, message = "2 ~ 20 사이의 숫자를 입력해주세요.")
@Min(value = 2, message = "2 ~ 20 사이의 숫자를 입력해주세요.")
@Max(value = 20, message = "2 ~ 20 사이의 숫자를 입력해주세요.")
int memberLimit,

@Pattern(
regexp = "^[0-9]{4}$",
message = "숫자 4자리를 입력해주세요."
)
String password
) {
}
66 changes: 66 additions & 0 deletions src/main/java/PNUMEAT/Backend/domain/team/entity/Team.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package PNUMEAT.Backend.domain.team.entity;

import PNUMEAT.Backend.domain.auth.entity.Member;
import PNUMEAT.Backend.domain.team.enums.Topic;
import PNUMEAT.Backend.domain.team_member.entity.TeamMember;
import jakarta.persistence.*;
import lombok.Builder;
import lombok.Getter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

import static PNUMEAT.Backend.global.images.ImageConstant.DEFAULT_TEAM_IMAGE_URL;

@Entity
@Getter
@EntityListeners(AuditingEntityListener.class)
public class Team {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long teamId;

private String teamName;

private String teamIconUrl = DEFAULT_TEAM_IMAGE_URL;

private Topic teamTopic;

private String teamExplain;

private int maxParticipant;

private String teamPassword;

@CreatedDate
private LocalDateTime createdAt;

Choose a reason for hiding this comment

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

createDate 는 설정 따로 빼기 (상속받기)


private int streakDays;

@ManyToOne
@JoinColumn(name = "member_id")
private Member teamManager;

@OneToMany(mappedBy = "team")
private List<TeamMember> teamMembers = new ArrayList<>();

Choose a reason for hiding this comment

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

Fetch Lazy 설정 필요


protected Team(){
}

@Builder
public Team(String teamName, Topic teamTopic, String teamExplain, int maxParticipant, String teamPassword, Member teamManager) {
this.teamName = teamName;
this.teamTopic = teamTopic;
this.teamExplain = teamExplain;
this.maxParticipant = maxParticipant;
this.teamPassword = teamPassword;
this.teamManager = teamManager;
}

public void updateTeamIconUrl(String teamIconUrl){
this.teamIconUrl = teamIconUrl;
}
}
36 changes: 36 additions & 0 deletions src/main/java/PNUMEAT/Backend/domain/team/enums/Topic.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package PNUMEAT.Backend.domain.team.enums;

import PNUMEAT.Backend.global.error.ErrorCode;
import PNUMEAT.Backend.global.error.Team24Exception;

import static PNUMEAT.Backend.global.error.ErrorCode.TOPIC_INVALID_ERROR;

public enum Topic {
CODINGTEST(1, "코딩테스트"),
STUDY(2, "스터디");

private final int code;
private final String name;

Topic(int code, String name) {
this.code = code;
this.name = name;
}

public int getCode() {
return code;
}

public String getName() {
return name;
}

public static Topic fromName(String name){

Choose a reason for hiding this comment

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

stream 쓰기

for(Topic topic : Topic.values()){
if(topic.getName().equals(name)){
return topic;
}
}
throw new Team24Exception(TOPIC_INVALID_ERROR);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package PNUMEAT.Backend.domain.team.repository;

import PNUMEAT.Backend.domain.team.entity.Team;
import org.springframework.data.jpa.repository.JpaRepository;

public interface TeamRepository extends JpaRepository<Team, Long> {
}
49 changes: 49 additions & 0 deletions src/main/java/PNUMEAT/Backend/domain/team/service/TeamService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package PNUMEAT.Backend.domain.team.service;

import PNUMEAT.Backend.domain.auth.entity.Member;
import PNUMEAT.Backend.domain.team.dto.request.TeamRequest;
import PNUMEAT.Backend.domain.team.entity.Team;
import PNUMEAT.Backend.domain.team.enums.Topic;
import PNUMEAT.Backend.domain.team.repository.TeamRepository;
import PNUMEAT.Backend.domain.team_member.entity.TeamMember;
import PNUMEAT.Backend.domain.team_member.repository.TeamMemberRepository;
import PNUMEAT.Backend.global.images.ImageService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class TeamService {

private final TeamRepository teamRepository;
private final TeamMemberRepository teamMemberRepository;
private final ImageService imageService;

@Transactional
public Team createTeam(TeamRequest teamRequest,
MultipartFile teamIcon, Member member){

Team team = Team.builder()
.teamName(teamRequest.teamName())
.teamExplain(teamRequest.teamExplain())
.teamTopic(Topic.fromName(teamRequest.topic()))
.maxParticipant(teamRequest.memberLimit())
.teamPassword(teamRequest.password())
.teamManager(member)
.build();

if (teamIcon != null){
String teamIconUrl = imageService.teamImageUpload(teamIcon);
team.updateTeamIconUrl(teamIconUrl);
}

TeamMember teamMember = new TeamMember(team, member);

teamMemberRepository.save(teamMember);

return teamRepository.save(team);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package PNUMEAT.Backend.domain.team_member.entity;

import PNUMEAT.Backend.domain.auth.entity.Member;
import PNUMEAT.Backend.domain.team.entity.Team;
import jakarta.persistence.*;

import java.util.Objects;

@Entity
public class TeamMember {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long teamInfoId;

@ManyToOne
@JoinColumn(name="member_id")
private Member member;

@ManyToOne
@JoinColumn(name="team_id")
private Team team;

protected TeamMember(){}

public TeamMember(Team team, Member member) {
this.member = member;
this.team = team;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TeamMember that = (TeamMember) o;
return Objects.equals(teamInfoId, that.teamInfoId) && Objects.equals(member, that.member) && Objects.equals(team, that.team);
}

@Override
public int hashCode() {
return Objects.hash(teamInfoId, member, team);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package PNUMEAT.Backend.domain.team_member.repository;

import PNUMEAT.Backend.domain.team_member.entity.TeamMember;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface TeamMemberRepository extends JpaRepository<TeamMember, Long> {
}
3 changes: 3 additions & 0 deletions src/main/java/PNUMEAT/Backend/global/error/ErrorCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ public enum ErrorCode {

ARTICLE_FORBIDDEN_ERROR(HttpStatus.FORBIDDEN, "게시글 권한이 없습니다."),

//TEAM
TOPIC_INVALID_ERROR(HttpStatus.BAD_REQUEST, "주제가 올바른 형식이 아닙니다."),

// ARTICLE
ARTICLE_NOT_FOUND_ERROR(HttpStatus.NOT_FOUND, "존재 하지 않는 게시글 입니다."),

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package PNUMEAT.Backend.global.response;

import lombok.Getter;

@Getter
public enum ResponseMessageEnum {
// TEAM
TEAM_CREATED_SUCCESS("팀이 성공적으로 생성되었습니다.", 201);


private final String message;
private final int statusCode;

ResponseMessageEnum(String message, int statusCode) {
this.message = message;
this.statusCode = statusCode;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package PNUMEAT.Backend.global.validation.annotation;

import PNUMEAT.Backend.global.validation.validator.NotNullOrBlankValidator;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = NotNullOrBlankValidator.class)
public @interface NotNullOrBlank {
String message() default "값은 null이거나 공백만 입력될 수 없습니다.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package PNUMEAT.Backend.global.validation.validator;

import PNUMEAT.Backend.global.validation.annotation.NotNullOrBlank;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;

public class NotNullOrBlankValidator implements ConstraintValidator<NotNullOrBlank, String> {
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return value != null && !value.trim().isEmpty();
}
}
Loading