Skip to content

Commit 812651d

Browse files
authored
✨ feat: 태그 생성 기능 개발 (#89)
* 🚚 rename: Tag 도메인 분리 기존 Organization에서 다루던 Tag 엔티티 개발 편의를 위해 도메인 분리, 그에 따른 파일 이동 * ✨ feat: Tag 도메인 기본 클래스 생성 * 🚚 rename: Tag 도메인 생성에 따른 TagResponse 클래스 도메인 패키지 내로 이동 * ✨ feat: OrganizationTag 삭제에 따른 연관관계 수정 * ✨ feat: Tag 생성 기능 개발 * ✅ test: Tag 생성 기능 테스트 request 구현
1 parent 0804228 commit 812651d

15 files changed

+203
-75
lines changed

http/test.http

+21-11
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
@matsterToken = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwiZW1haWwiOiJzcG9udXNAZ21haWwuY29tIiwiYXV0aCI6IlNUVURFTlQiLCJpYXQiOjE3MDU4MzQ2MTksImV4cCI6MTcwNjgzNDYxOX0.XO8ydia0RB1eVGXrA7nBeP5AND2Ui4ivK26DEldZLnE
1+
@masterToken = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwiZW1haWwiOiJzcG9udXNAZ21haWwuY29tIiwiYXV0aCI6IlNUVURFTlQiLCJpYXQiOjE3MDU4MzQ2MTksImV4cCI6MTcwNjgzNDYxOX0.XO8ydia0RB1eVGXrA7nBeP5AND2Ui4ivK26DEldZLnE
22

33
### 회원가입
44
POST http://localhost:8080/api/v1/organizations/join
@@ -31,7 +31,7 @@ Content-Type: application/json
3131

3232
### 공고 생성
3333
POST http://localhost:8080/api/v1/announcements
34-
Authorization: Bearer {{matsterToken}}
34+
Authorization: Bearer {{masterToken}}
3535
Content-Type: application/json
3636

3737
{
@@ -43,15 +43,15 @@ Content-Type: application/json
4343

4444
### 공고 상세 조회
4545
GET http://localhost:8080/api/v1/announcements/1
46-
Authorization: Bearer {{matsterToken}}
46+
Authorization: Bearer {{masterToken}}
4747

4848
### 공고 상태별 목록 조회
4949
GET http://localhost:8080/api/v1/announcements/status?status=POSTED
50-
Authorization: Bearer {{matsterToken}}
50+
Authorization: Bearer {{masterToken}}
5151

5252
### 공고 수정
5353
PATCH http://localhost:8080/api/v1/announcements/1
54-
Authorization: Bearer {{matsterToken}}
54+
Authorization: Bearer {{masterToken}}
5555
Content-Type: application/json
5656

5757
{
@@ -63,7 +63,7 @@ Content-Type: application/json
6363

6464
### 공고 삭제 (생성한 단체만 삭제 가능)
6565
DELETE http://localhost:8080/api/v1/announcements/1
66-
Authorization: Bearer {{matsterToken}}
66+
Authorization: Bearer {{masterToken}}
6767

6868
### 보고서 작성
6969
POST http://localhost:8080/api/v1/reports
@@ -84,11 +84,11 @@ GET http://localhost:8080/api/v1/announcements?search=무신사
8484

8585
### 내 조직 조회
8686
GET http://localhost:8080/api/v1/organizations/me
87-
Authorization: Bearer {{matsterToken}}
87+
Authorization: Bearer {{masterToken}}
8888

8989
### 내 조직 수정
9090
PATCH http://localhost:8080/api/v1/organizations/me
91-
Authorization: Bearer {{matsterToken}}
91+
Authorization: Bearer {{masterToken}}
9292
Content-Type: application/json
9393

9494
{
@@ -109,7 +109,7 @@ Content-Type: application/json
109109

110110
### 내 조직 수정 2
111111
PATCH http://localhost:8080/api/v1/organizations/me
112-
Authorization: Bearer {{matsterToken}}
112+
Authorization: Bearer {{masterToken}}
113113
Content-Type: application/json
114114

115115
{
@@ -118,8 +118,18 @@ Content-Type: application/json
118118

119119
### 내 조직 삭제 [soft delete] (OrganizationStatus = INACTIVE)
120120
DELETE http://localhost:8080/api/v1/organizations/me
121-
Authorization: Bearer {{matsterToken}}
121+
Authorization: Bearer {{masterToken}}
122122

123123
### 조직 조회
124124
GET http://localhost:8080/api/v1/organizations/1
125-
Authorization: Bearer {{matsterToken}}
125+
Authorization: Bearer {{masterToken}}
126+
127+
### 태그 생성
128+
POST http://localhost:8080/api/v1/tags
129+
Authorization: Bearer {{masterToken}}
130+
Content-Type: application/json
131+
132+
{
133+
"name": "태그 name test2"
134+
}
135+

src/main/java/com/sponus/sponusbe/domain/organization/dto/OrganizationDetailGetResponse.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import com.sponus.sponusbe.domain.organization.entity.enums.OrganizationStatus;
77
import com.sponus.sponusbe.domain.organization.entity.enums.OrganizationType;
88
import com.sponus.sponusbe.domain.organization.entity.enums.SuborganizationType;
9+
import com.sponus.sponusbe.domain.tag.dto.TagGetResponse;
910

1011
public record OrganizationDetailGetResponse(
1112
Long organizationId,
@@ -29,7 +30,7 @@ public record OrganizationDetailGetResponse(
2930
List<OrganizationLinkGetResponse> links
3031
) {
3132
public static OrganizationDetailGetResponse from(Organization organization) {
32-
List<TagGetResponse> tagGetResponses = TagGetResponse.getTagResponses(organization);
33+
List<TagGetResponse> tagGetResponses = TagGetResponse.getTagResponse(organization);
3334
List<OrganizationLinkGetResponse> linkGetResponses = OrganizationLinkGetResponse.getOrganizationLinkResponses(
3435
organization);
3536
return new OrganizationDetailGetResponse(

src/main/java/com/sponus/sponusbe/domain/organization/dto/TagGetResponse.java

-23
This file was deleted.

src/main/java/com/sponus/sponusbe/domain/organization/entity/Organization.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import com.sponus.sponusbe.domain.organization.entity.enums.OrganizationStatus;
88
import com.sponus.sponusbe.domain.organization.entity.enums.OrganizationType;
99
import com.sponus.sponusbe.domain.organization.entity.enums.SuborganizationType;
10+
import com.sponus.sponusbe.domain.tag.entity.Tag;
1011
import com.sponus.sponusbe.global.common.BaseEntity;
1112

1213
import jakarta.persistence.Column;
@@ -91,7 +92,7 @@ public class Organization extends BaseEntity {
9192

9293
@Builder.Default
9394
@OneToMany(mappedBy = "organization", fetch = FetchType.EAGER)
94-
private List<OrganizationTag> organizationTags = new ArrayList<>();
95+
private List<Tag> tags = new ArrayList<>();
9596

9697
@Builder.Default
9798
@OneToMany(mappedBy = "organization", fetch = FetchType.EAGER)

src/main/java/com/sponus/sponusbe/domain/organization/entity/Tag.java

-30
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.sponus.sponusbe.domain.tag.controller;
2+
3+
import org.springframework.web.bind.annotation.PostMapping;
4+
import org.springframework.web.bind.annotation.RequestBody;
5+
import org.springframework.web.bind.annotation.RequestMapping;
6+
import org.springframework.web.bind.annotation.RestController;
7+
8+
import com.sponus.sponusbe.auth.annotation.AuthOrganization;
9+
import com.sponus.sponusbe.domain.organization.entity.Organization;
10+
import com.sponus.sponusbe.domain.tag.dto.request.TagCreateRequest;
11+
import com.sponus.sponusbe.domain.tag.dto.resposne.TagCreateResponse;
12+
import com.sponus.sponusbe.domain.tag.service.TagService;
13+
import com.sponus.sponusbe.global.common.ApiResponse;
14+
15+
import lombok.RequiredArgsConstructor;
16+
import lombok.extern.slf4j.Slf4j;
17+
18+
@Slf4j
19+
@RequiredArgsConstructor
20+
@RequestMapping("/api/v1/tags")
21+
@RestController
22+
public class TagController {
23+
private final TagService tagService;
24+
25+
@PostMapping
26+
public ApiResponse<TagCreateResponse> createTag(@AuthOrganization Organization organization,
27+
@RequestBody TagCreateRequest request) {
28+
return ApiResponse.onSuccess(tagService.createTag(organization.getId(), request));
29+
}
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.sponus.sponusbe.domain.tag.dto;
2+
3+
import java.util.List;
4+
5+
import com.sponus.sponusbe.domain.organization.entity.Organization;
6+
import com.sponus.sponusbe.domain.tag.entity.Tag;
7+
8+
public record TagGetResponse(
9+
Long id,
10+
String name
11+
) {
12+
public static TagGetResponse from(Tag tag) {
13+
return new TagGetResponse(tag.getId(), tag.getName());
14+
}
15+
16+
public static List<TagGetResponse> getTagResponse(Organization organization) {
17+
return organization.getTags()
18+
.stream()
19+
.map(TagGetResponse::from)
20+
.toList();
21+
}
22+
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.sponus.sponusbe.domain.tag.dto.request;
2+
3+
import com.sponus.sponusbe.domain.organization.entity.Organization;
4+
import com.sponus.sponusbe.domain.tag.entity.Tag;
5+
6+
public record TagCreateRequest(
7+
String name
8+
) {
9+
public Tag toEntity(Organization organization) {
10+
return Tag.builder()
11+
.name(name)
12+
.organization(organization)
13+
.build();
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.sponus.sponusbe.domain.tag.dto.resposne;
2+
3+
public record TagCreateResponse(
4+
Long tagId,
5+
Long organizationId
6+
) {
7+
}

src/main/java/com/sponus/sponusbe/domain/organization/entity/OrganizationTag.java src/main/java/com/sponus/sponusbe/domain/tag/entity/Tag.java

+10-9
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
package com.sponus.sponusbe.domain.organization.entity;
1+
package com.sponus.sponusbe.domain.tag.entity;
2+
3+
import com.sponus.sponusbe.domain.organization.entity.Organization;
24

35
import jakarta.persistence.Column;
46
import jakarta.persistence.ConstraintMode;
@@ -19,22 +21,21 @@
1921

2022
@Builder
2123
@NoArgsConstructor(access = AccessLevel.PROTECTED)
22-
@AllArgsConstructor(access = AccessLevel.PRIVATE)
24+
@AllArgsConstructor
2325
@Getter
2426
@Entity
25-
@Table(name = "organization_tag")
26-
public class OrganizationTag {
27+
@Table(name = "tag")
28+
public class Tag {
2729

2830
@Id
2931
@GeneratedValue(strategy = GenerationType.IDENTITY)
30-
@Column(name = "id")
32+
@Column(name = "tag_id")
3133
private Long id;
3234

33-
@ManyToOne(fetch = FetchType.LAZY, optional = false)
35+
@ManyToOne(fetch = FetchType.LAZY)
3436
@JoinColumn(name = "organization_id", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
3537
private Organization organization;
3638

37-
@ManyToOne(fetch = FetchType.LAZY, optional = false)
38-
@JoinColumn(name = "tag_id", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
39-
private Tag tag;
39+
@Column(name = "tag_name", nullable = false)
40+
private String name;
4041
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.sponus.sponusbe.domain.tag.exception;
2+
3+
import org.springframework.http.HttpStatus;
4+
5+
import com.sponus.sponusbe.global.common.ApiResponse;
6+
import com.sponus.sponusbe.global.common.BaseErrorCode;
7+
8+
import lombok.AllArgsConstructor;
9+
import lombok.Getter;
10+
11+
@Getter
12+
@AllArgsConstructor
13+
public enum TagErrorCode implements BaseErrorCode {
14+
TAG_ERROR(HttpStatus.BAD_REQUEST, "ORG4000", "태그 관련 에러"),
15+
INVALID_FORMAT(HttpStatus.BAD_REQUEST, "ORG4001", "잘못된 형식입니다."),
16+
TAG_ALREADY_EXIST(HttpStatus.BAD_REQUEST, "ORG4002", "중복된 태그입니다."),
17+
TAG_NOT_FOUND(HttpStatus.NOT_FOUND, "ORG4040", "존재하지 않는 태그입니다.");
18+
19+
private final HttpStatus httpStatus;
20+
private final String code;
21+
private final String message;
22+
23+
@Override
24+
public ApiResponse<Void> getErrorResponse() {
25+
return ApiResponse.onFailure(code, message);
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.sponus.sponusbe.domain.tag.exception;
2+
3+
import com.sponus.sponusbe.global.common.BaseErrorCode;
4+
import com.sponus.sponusbe.global.common.exception.CustomException;
5+
6+
public class TagException extends CustomException {
7+
public TagException(BaseErrorCode errorCode) {
8+
super(errorCode);
9+
}
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.sponus.sponusbe.domain.tag.repository;
2+
3+
import org.springframework.data.jpa.repository.JpaRepository;
4+
5+
import com.sponus.sponusbe.domain.tag.entity.Tag;
6+
7+
public interface TagRepository extends JpaRepository<Tag, Long> {
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.sponus.sponusbe.domain.tag.service;
2+
3+
import org.springframework.stereotype.Service;
4+
import org.springframework.transaction.annotation.Transactional;
5+
6+
import lombok.RequiredArgsConstructor;
7+
8+
@Transactional(readOnly = true)
9+
@RequiredArgsConstructor
10+
@Service
11+
public class TagQueryService {
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.sponus.sponusbe.domain.tag.service;
2+
3+
import static com.sponus.sponusbe.domain.organization.exception.OrganizationErrorCode.*;
4+
5+
import org.springframework.stereotype.Service;
6+
import org.springframework.transaction.annotation.Transactional;
7+
8+
import com.sponus.sponusbe.domain.organization.entity.Organization;
9+
import com.sponus.sponusbe.domain.organization.exception.OrganizationException;
10+
import com.sponus.sponusbe.domain.organization.repository.OrganizationRepository;
11+
import com.sponus.sponusbe.domain.tag.dto.request.TagCreateRequest;
12+
import com.sponus.sponusbe.domain.tag.dto.resposne.TagCreateResponse;
13+
import com.sponus.sponusbe.domain.tag.entity.Tag;
14+
import com.sponus.sponusbe.domain.tag.repository.TagRepository;
15+
16+
import lombok.RequiredArgsConstructor;
17+
18+
@Transactional(readOnly = true)
19+
@RequiredArgsConstructor
20+
@Service
21+
public class TagService {
22+
private final OrganizationRepository organizationRepository;
23+
private final TagRepository tagRepository;
24+
25+
@Transactional
26+
public TagCreateResponse createTag(Long organizationId, TagCreateRequest request) {
27+
Organization organization = organizationRepository.findById(organizationId)
28+
.orElseThrow(() -> new OrganizationException(ORGANIZATION_NOT_FOUND));
29+
30+
Tag tag = request.toEntity(organization);
31+
organization.getTags().add(tag);
32+
tag = tagRepository.save(tag);
33+
34+
return new TagCreateResponse(tag.getId(), organization.getId());
35+
}
36+
}

0 commit comments

Comments
 (0)