Skip to content

Commit 59bc004

Browse files
authored
Merge pull request #14 from prgrms-aibe-devcourse/feat/#11
Feat/#11-Closet-structure
2 parents 9810fab + de1fb0a commit 59bc004

7 files changed

Lines changed: 206 additions & 22 deletions

File tree

README.md

Lines changed: 42 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<h1 align="center"> <프로젝트 이름> </h1>
1+
<h1 align="center"> 옷장..난감! </h1>
22

33

44
<p align="center">
@@ -7,7 +7,7 @@
77
</p>
88
<h2 align="center"> 🗂️ Project Overview </h2>
99
<p align="center">
10-
<프로젝트>은/는 사용자의 체형을 토대로 <프로젝트>만의 알고리즘을 적용해<br/>
10+
옷장난감🧸은/는 사용자의 체형을 토대로 알고리즘을 적용해<br/>
1111
<strong>사용자 맞춤형 스타일을 추천해주는 서비스를 제공하는 플랫폼입니다. </strong> <br/>
1212
<br />
1313
개발 기간 : 2026.05.19 ~ 2026.06.26 (39일)
@@ -21,38 +21,67 @@
2121
<table align="center">
2222
<tr align="center">
2323
<td>
24-
<a href="https://github.com/prgrms-aibe-devcourse/AIBE5_FinalProject_Team4_BE/wiki#1-getting-started">
24+
<a href="https://github.com/prgrms-aibe-devcourse/AIBE5_FinalProject_Team4_BE/wiki/BE%E2%80%90GettingStarted">
2525
<img src="https://img.shields.io/badge/Setup-⚙️-blue?style=for-the-badge" alt="Setup"/>
2626
</a>
2727
</td>
2828
<td>
29-
<a href="https://github.com/prgrms-aibe-devcourse/AIBE5_FinalProject_Team4_BE/wiki#2-branch-strategy">
29+
<a href="https://github.com/prgrms-aibe-devcourse/AIBE5_FinalProject_Team4_BE/wiki/BranchRule">
3030
<img src="https://img.shields.io/badge/Strategy-🌳-green?style=for-the-badge" alt="Strategy"/>
3131
</a>
3232
</td>
3333
<td>
34-
<a href="https://github.com/prgrms-aibe-devcourse/AIBE5_FinalProject_Team4_BE/wiki#3-team-convention">
34+
<a href="https://www.notion.so/35d3550b7b55811a912dca65f0d0fedd#35d3550b7b5581619fdef191421a3252">
3535
<img src="https://img.shields.io/badge/Convention-✅-orange?style=for-the-badge" alt="Convention"/>
3636
</a>
37+
</td>
38+
<td>
39+
<a href="https://github.com/prgrms-aibe-devcourse/AIBE5_FinalProject_Team4_BE/wiki/Folder%E2%80%90structure">
40+
<img src="https://img.shields.io/badge/Structure-📂-purple?style=for-the-badge" alt="Structure"/>
41+
</a>
3742
</td>
3843
</tr>
3944
<tr align="center">
40-
<td><a href="https://github.com/prgrms-aibe-devcourse/AIBE5_FinalProject_Team4_BE/wiki#1-getting-started"><strong>개발환경 세팅</strong></a></td>
41-
<td><a href="https://github.com/prgrms-aibe-devcourse/AIBE5_FinalProject_Team4_BE/wiki#2-branch-strategy"><strong>브랜치 전략</strong></a></td>
42-
<td><a href="https://github.com/prgrms-aibe-devcourse/AIBE5_FinalProject_Team4_BE/wiki#3-team-convention"><strong>팀 컨벤션</strong></a></td>
45+
<td><a href="https://github.com/prgrms-aibe-devcourse/AIBE5_FinalProject_Team4_BE/wiki/BE%E2%80%90GettingStarted"><strong>개발환경 세팅</strong></a></td>
46+
<td><a href="https://github.com/prgrms-aibe-devcourse/AIBE5_FinalProject_Team4_BE/wiki/BranchRule"><strong>브랜치 전략</strong></a></td>
47+
<td><a href="https://www.notion.so/35d3550b7b55811a912dca65f0d0fedd#35d3550b7b5581619fdef191421a3252"><strong>팀 컨벤션</strong></a></td>
48+
<td><a href="https://github.com/prgrms-aibe-devcourse/AIBE5_FinalProject_Team4_BE/wiki/Folder%E2%80%90structure"><strong>폴더 구조</strong></a></td>
4349
</tr>
4450
</table>
4551

4652
<h2 align="center"> 📚 Stacks </h2>
4753
<table align="center">
4854
<thead>
49-
<tr>
50-
<th>작성예정</th>
51-
<th>작성예정</th>
52-
</tr>
55+
<tr>
56+
<th>언어</th>
57+
<th>설명</th>
58+
</tr>
5359
</thead>
5460
<tbody>
55-
61+
<tr>
62+
<td>Language</td>
63+
<td>Java 21</th>
64+
</tr>
65+
<tr>
66+
<td>Framework</td>
67+
<td>SpringBoot 3.5.14</th>
68+
</tr>
69+
<tr>
70+
<td>Project</td>
71+
<td>Gradle-Groovy</th>
72+
</tr>
73+
<tr>
74+
<td>Database</td>
75+
<td>MySQL 8.4</th>
76+
</tr>
77+
<tr>
78+
<td>Cache</td>
79+
<td>Redis 7</th>
80+
</tr>
81+
<tr>
82+
<td>Deployment</td>
83+
<td>EC2 / RDS / Docker</th>
84+
</tr>
5685
</tbody>
5786
</table>
5887

@@ -86,15 +115,6 @@
86115
</tbody>
87116
</table>
88117

89-
<h2 align="center"> 📂 Structure </h2>
90-
91-
~~~text
92-
```
93-
폴더 구조를 작성해주세요
94-
```
95-
~~~
96-
97-
98118
<h2 align="center"> 🦖 "Team 우주최강 공룡" 🚀</h2>
99119

100120
<table align="center">
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.closetnangam.be.domain.user.repository;
2+
3+
import com.closetnangam.be.domain.user.entity.User;
4+
import org.springframework.data.jpa.repository.JpaRepository;
5+
6+
public interface UserRepository extends JpaRepository<User, Long> {
7+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.closetnangam.be.domain.wardrobe.controller;
2+
3+
import com.closetnangam.be.domain.wardrobe.dto.response.WardrobeResponse;
4+
import com.closetnangam.be.domain.wardrobe.service.WardrobeService;
5+
import io.swagger.v3.oas.annotations.Operation;
6+
import io.swagger.v3.oas.annotations.tags.Tag;
7+
import lombok.RequiredArgsConstructor;
8+
import org.springframework.http.HttpStatus;
9+
import org.springframework.web.bind.annotation.GetMapping;
10+
import org.springframework.web.bind.annotation.PathVariable;
11+
import org.springframework.web.bind.annotation.PostMapping;
12+
import org.springframework.web.bind.annotation.RequestMapping;
13+
import org.springframework.web.bind.annotation.ResponseStatus;
14+
import org.springframework.web.bind.annotation.RestController;
15+
16+
@Tag(name = "Wardrobe", description = "옷장 API")
17+
@RestController
18+
@RequestMapping("/api/wardrobes")
19+
@RequiredArgsConstructor
20+
public class WardrobeController {
21+
22+
private final WardrobeService wardrobeService;
23+
24+
@Operation(summary = "회원 옷장 조회", description = "회원당 1개의 옷장을 조회합니다.")
25+
@GetMapping("/users/{userId}")
26+
public WardrobeResponse getWardrobeByUserId(@PathVariable Long userId) {
27+
return wardrobeService.getWardrobeByUserId(userId);
28+
}
29+
30+
@Operation(summary = "회원 옷장 생성", description = "회원 가입 후 옷장을 1회 생성합니다.")
31+
@PostMapping("/users/{userId}")
32+
@ResponseStatus(HttpStatus.CREATED)
33+
public WardrobeResponse createWardrobe(@PathVariable Long userId) {
34+
return wardrobeService.createWardrobe(userId);
35+
}
36+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.closetnangam.be.domain.wardrobe.dto.response;
2+
3+
import com.closetnangam.be.domain.wardrobe.entity.Wardrobe;
4+
5+
public record WardrobeResponse(
6+
Long wardrobeId,
7+
Long userId
8+
) {
9+
10+
public static WardrobeResponse from(Wardrobe wardrobe) {
11+
return new WardrobeResponse(
12+
wardrobe.getId(),
13+
wardrobe.getUser().getId()
14+
);
15+
}
16+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package com.closetnangam.be.domain.wardrobe.entity;
2+
3+
import com.closetnangam.be.domain.user.entity.User;
4+
import jakarta.persistence.Column;
5+
import jakarta.persistence.Entity;
6+
import jakarta.persistence.FetchType;
7+
import jakarta.persistence.GeneratedValue;
8+
import jakarta.persistence.GenerationType;
9+
import jakarta.persistence.Id;
10+
import jakarta.persistence.JoinColumn;
11+
import jakarta.persistence.OneToOne;
12+
import jakarta.persistence.Table;
13+
import jakarta.persistence.UniqueConstraint;
14+
import lombok.AccessLevel;
15+
import lombok.Builder;
16+
import lombok.Getter;
17+
import lombok.NoArgsConstructor;
18+
19+
@Entity
20+
@Getter
21+
@NoArgsConstructor(access = AccessLevel.PROTECTED)
22+
@Table(
23+
name = "wardrobes",
24+
uniqueConstraints = @UniqueConstraint(name = "uk_wardrobes_user_id", columnNames = "user_id")
25+
)
26+
public class Wardrobe {
27+
28+
@Id
29+
@GeneratedValue(strategy = GenerationType.IDENTITY)
30+
@Column(name = "wardrobe_id")
31+
private Long id;
32+
33+
@OneToOne(fetch = FetchType.LAZY, optional = false)
34+
@JoinColumn(name = "user_id", nullable = false, unique = true)
35+
private User user;
36+
37+
@Builder
38+
private Wardrobe(User user) {
39+
this.user = user;
40+
}
41+
42+
public static Wardrobe create(User user) {
43+
return Wardrobe.builder()
44+
.user(user)
45+
.build();
46+
}
47+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.closetnangam.be.domain.wardrobe.repository;
2+
3+
import com.closetnangam.be.domain.wardrobe.entity.Wardrobe;
4+
import org.springframework.data.jpa.repository.JpaRepository;
5+
6+
import java.util.Optional;
7+
8+
public interface WardrobeRepository extends JpaRepository<Wardrobe, Long> {
9+
10+
Optional<Wardrobe> findByUser_Id(Long userId);
11+
12+
boolean existsByUser_Id(Long userId);
13+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package com.closetnangam.be.domain.wardrobe.service;
2+
3+
import com.closetnangam.be.domain.user.entity.User;
4+
import com.closetnangam.be.domain.user.repository.UserRepository;
5+
import com.closetnangam.be.domain.wardrobe.dto.response.WardrobeResponse;
6+
import com.closetnangam.be.domain.wardrobe.entity.Wardrobe;
7+
import com.closetnangam.be.domain.wardrobe.repository.WardrobeRepository;
8+
import lombok.RequiredArgsConstructor;
9+
import org.springframework.stereotype.Service;
10+
import org.springframework.transaction.annotation.Transactional;
11+
12+
@Service
13+
@RequiredArgsConstructor
14+
@Transactional(readOnly = true)
15+
public class WardrobeService {
16+
17+
private final WardrobeRepository wardrobeRepository;
18+
private final UserRepository userRepository;
19+
20+
public WardrobeResponse getWardrobeByUserId(Long userId) {
21+
Wardrobe wardrobe = wardrobeRepository.findByUser_Id(userId)
22+
.orElseThrow(() -> new IllegalArgumentException("옷장을 찾을 수 없습니다."));
23+
return WardrobeResponse.from(wardrobe);
24+
}
25+
26+
@Transactional
27+
public WardrobeResponse createWardrobe(Long userId) {
28+
User user = userRepository.findById(userId)
29+
.orElseThrow(() -> new IllegalArgumentException("사용자를 찾을 수 없습니다."));
30+
31+
if (wardrobeRepository.existsByUser_Id(userId)) {
32+
throw new IllegalStateException("이미 옷장이 존재합니다.");
33+
}
34+
35+
Wardrobe wardrobe = wardrobeRepository.save(Wardrobe.create(user));
36+
return WardrobeResponse.from(wardrobe);
37+
}
38+
39+
@Transactional
40+
public WardrobeResponse getOrCreateWardrobe(Long userId) {
41+
return wardrobeRepository.findByUser_Id(userId)
42+
.map(WardrobeResponse::from)
43+
.orElseGet(() -> createWardrobe(userId));
44+
}
45+
}

0 commit comments

Comments
 (0)