Skip to content

Commit 2da650f

Browse files
authored
feature: 동아리 박람회 qr 이벤트 기능 구현 (#44)
1 parent 8f1f435 commit 2da650f

File tree

10 files changed

+260
-1
lines changed

10 files changed

+260
-1
lines changed

build.gradle

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ dependencies {
3030
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
3131
implementation 'org.springframework.boot:spring-boot-starter-web'
3232
implementation 'org.springframework.boot:spring-boot-starter-security'
33+
implementation 'org.springframework.boot:spring-boot-starter-validation'
34+
35+
implementation 'io.hypersistence:hypersistence-utils-hibernate-55:3.7.2'
3336

3437
implementation 'com.auth0:java-jwt:4.2.1'
3538

@@ -38,6 +41,7 @@ dependencies {
3841

3942
implementation 'org.apache.poi:poi:5.2.0'
4043
implementation 'org.apache.poi:poi-ooxml:5.2.0'
44+
implementation 'org.springframework.boot:spring-boot-configuration-processor'
4145

4246

4347
compileOnly 'org.projectlombok:lombok'

src/main/java/ddingdong/ddingdongBE/common/config/SecurityConfig.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ public SecurityFilterChain filterChain(HttpSecurity http, JwtAuthService authSer
2929
throws Exception {
3030
http
3131
.authorizeHttpRequests()
32-
.antMatchers(API_PREFIX + "/auth/**")
32+
.antMatchers(API_PREFIX + "/auth/**",
33+
API_PREFIX + "/qr-stamps/**")
3334
.permitAll()
3435
.antMatchers(GET,
3536
API_PREFIX + "/clubs/**",
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package ddingdong.ddingdongBE.domain.qrstamp.controller;
2+
3+
import ddingdong.ddingdongBE.domain.qrstamp.controller.dto.request.CollectStampRequest;
4+
import ddingdong.ddingdongBE.domain.qrstamp.controller.dto.response.CollectionResultResponse;
5+
import ddingdong.ddingdongBE.domain.qrstamp.service.QrStampService;
6+
import java.time.LocalDateTime;
7+
import lombok.RequiredArgsConstructor;
8+
import org.springframework.web.bind.annotation.GetMapping;
9+
import org.springframework.web.bind.annotation.PostMapping;
10+
import org.springframework.web.bind.annotation.RequestBody;
11+
import org.springframework.web.bind.annotation.RequestMapping;
12+
import org.springframework.web.bind.annotation.RequestParam;
13+
import org.springframework.web.bind.annotation.RestController;
14+
15+
@RestController
16+
@RequestMapping("/server/qr-stamps")
17+
@RequiredArgsConstructor
18+
public class QrStampController {
19+
20+
private final QrStampService qrStampService;
21+
22+
@PostMapping("/collect")
23+
public void collectStamp(@RequestBody CollectStampRequest request) {
24+
LocalDateTime collectedAt = LocalDateTime.now();
25+
qrStampService.collectStamp(request, collectedAt);
26+
}
27+
28+
@GetMapping()
29+
public CollectionResultResponse getCollectionResult(@RequestParam String studentName, @RequestParam String studentNumber) {
30+
return qrStampService.getCollectionResult(studentName, studentNumber);
31+
}
32+
33+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package ddingdong.ddingdongBE.domain.qrstamp.controller.dto.request;
2+
3+
import ddingdong.ddingdongBE.domain.qrstamp.entity.StampHistory;
4+
import javax.validation.constraints.Size;
5+
import lombok.AccessLevel;
6+
import lombok.Getter;
7+
import lombok.NoArgsConstructor;
8+
9+
@Getter
10+
@NoArgsConstructor(access = AccessLevel.PROTECTED)
11+
public class CollectStampRequest {
12+
13+
private String studentName;
14+
15+
@Size(min = 8, max = 8, message = "학번은 8자리입니다.")
16+
private String studentNumber;
17+
18+
private String clubCode;
19+
20+
public StampHistory toStampHistoryEntity() {
21+
return StampHistory.builder()
22+
.studentName(this.studentName)
23+
.studentNumber(this.studentNumber).build();
24+
}
25+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package ddingdong.ddingdongBE.domain.qrstamp.controller.dto.response;
2+
3+
import com.fasterxml.jackson.annotation.JsonFormat;
4+
import java.time.LocalDateTime;
5+
import lombok.AccessLevel;
6+
import lombok.AllArgsConstructor;
7+
import lombok.Builder;
8+
import lombok.Getter;
9+
10+
@Getter
11+
@AllArgsConstructor(access = AccessLevel.PROTECTED)
12+
@Builder
13+
public class CollectedStampsResponse {
14+
15+
private String stamp;
16+
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
17+
private LocalDateTime collectedAt;
18+
19+
public static CollectedStampsResponse of(String stampName, LocalDateTime collectedAt) {
20+
return CollectedStampsResponse.builder()
21+
.stamp(stampName)
22+
.collectedAt(collectedAt).build();
23+
}
24+
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package ddingdong.ddingdongBE.domain.qrstamp.controller.dto.response;
2+
3+
import ddingdong.ddingdongBE.domain.qrstamp.entity.StampHistory;
4+
import java.util.List;
5+
import lombok.AccessLevel;
6+
import lombok.AllArgsConstructor;
7+
import lombok.Builder;
8+
import lombok.Getter;
9+
10+
@Getter
11+
@AllArgsConstructor(access = AccessLevel.PROTECTED)
12+
@Builder
13+
public class CollectionResultResponse {
14+
15+
private boolean isCompleted;
16+
private List<CollectedStampsResponse> collections;
17+
18+
public static CollectionResultResponse of(boolean isCompleted,
19+
List<CollectedStampsResponse> collectedStampsResponse) {
20+
return CollectionResultResponse.builder()
21+
.isCompleted(isCompleted)
22+
.collections(collectedStampsResponse)
23+
.build();
24+
}
25+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package ddingdong.ddingdongBE.domain.qrstamp.entity;
2+
3+
import java.util.Arrays;
4+
import lombok.Getter;
5+
import lombok.RequiredArgsConstructor;
6+
7+
@Getter
8+
@RequiredArgsConstructor
9+
public enum ClubStamp {
10+
11+
COW("카우", "COW"),
12+
TEST("테스트", "TEST");
13+
14+
private final String name;
15+
private final String code;
16+
17+
public static ClubStamp getByClubCode(String code) {
18+
return Arrays.stream(ClubStamp.values())
19+
.filter(clubStamp -> clubStamp.getCode().equals(code))
20+
.findFirst()
21+
.orElseThrow(() -> new IllegalArgumentException("동아리 코드를 확인해주세요."));
22+
}
23+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package ddingdong.ddingdongBE.domain.qrstamp.entity;
2+
3+
import ddingdong.ddingdongBE.common.BaseEntity;
4+
import io.hypersistence.utils.hibernate.type.json.JsonType;
5+
import java.time.LocalDateTime;
6+
import java.util.HashMap;
7+
import java.util.Map;
8+
import javax.persistence.Column;
9+
import javax.persistence.Entity;
10+
import javax.persistence.GeneratedValue;
11+
import javax.persistence.GenerationType;
12+
import javax.persistence.Id;
13+
import javax.persistence.Table;
14+
import javax.persistence.UniqueConstraint;
15+
import lombok.AccessLevel;
16+
import lombok.Builder;
17+
import lombok.Getter;
18+
import lombok.NoArgsConstructor;
19+
import org.hibernate.annotations.Type;
20+
import org.hibernate.annotations.TypeDef;
21+
22+
@Getter
23+
@NoArgsConstructor(access = AccessLevel.PROTECTED)
24+
@Entity
25+
@Table(uniqueConstraints = @UniqueConstraint(columnNames = {"studentName", "studentNumber"}))
26+
@TypeDef(name = "json", typeClass = JsonType.class)
27+
public class StampHistory extends BaseEntity {
28+
29+
@Id
30+
@GeneratedValue(strategy = GenerationType.IDENTITY)
31+
private Long id;
32+
33+
@Column(nullable = false)
34+
private String studentName;
35+
36+
@Column(nullable = false)
37+
private String studentNumber;
38+
39+
@Type(type = "json")
40+
@Column(columnDefinition = "json")
41+
private final Map<ClubStamp, LocalDateTime> collectedStamps = new HashMap<>();
42+
43+
private LocalDateTime completedAt;
44+
45+
@Builder
46+
private StampHistory(Long id, String studentName, String studentNumber, LocalDateTime completedAt) {
47+
this.id = id;
48+
this.studentName = studentName;
49+
this.studentNumber = studentNumber;
50+
this.completedAt = completedAt;
51+
}
52+
53+
public void collectStamp(ClubStamp clubStamp, LocalDateTime collectedAt) {
54+
this.collectedStamps.put(clubStamp, collectedAt);
55+
if (this.collectedStamps.size() == ClubStamp.values().length) {
56+
this.completedAt = LocalDateTime.now();
57+
}
58+
}
59+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package ddingdong.ddingdongBE.domain.qrstamp.repository;
2+
3+
import ddingdong.ddingdongBE.domain.qrstamp.entity.StampHistory;
4+
import java.util.Optional;
5+
import org.springframework.data.jpa.repository.JpaRepository;
6+
import org.springframework.stereotype.Repository;
7+
8+
@Repository
9+
public interface StampHistoryRepository extends JpaRepository<StampHistory, Long> {
10+
11+
Optional<StampHistory> findStampHistoryByStudentNameAndStudentNumber(String studentName, String studentNumber);
12+
13+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package ddingdong.ddingdongBE.domain.qrstamp.service;
2+
3+
4+
import ddingdong.ddingdongBE.domain.qrstamp.controller.dto.request.CollectStampRequest;
5+
import ddingdong.ddingdongBE.domain.qrstamp.controller.dto.response.CollectedStampsResponse;
6+
import ddingdong.ddingdongBE.domain.qrstamp.controller.dto.response.CollectionResultResponse;
7+
import ddingdong.ddingdongBE.domain.qrstamp.entity.ClubStamp;
8+
import ddingdong.ddingdongBE.domain.qrstamp.entity.StampHistory;
9+
import ddingdong.ddingdongBE.domain.qrstamp.repository.StampHistoryRepository;
10+
import java.time.LocalDateTime;
11+
import java.util.List;
12+
import lombok.RequiredArgsConstructor;
13+
import org.springframework.stereotype.Service;
14+
import org.springframework.transaction.annotation.Transactional;
15+
16+
@Service
17+
@RequiredArgsConstructor
18+
@Transactional(readOnly = true)
19+
public class QrStampService {
20+
21+
private final StampHistoryRepository stampHistoryRepository;
22+
23+
@Transactional
24+
public void collectStamp(CollectStampRequest request, LocalDateTime collectedAt) {
25+
StampHistory stampHistory = stampHistoryRepository.findStampHistoryByStudentNameAndStudentNumber(
26+
request.getStudentName(),
27+
request.getStudentNumber())
28+
.orElse(request.toStampHistoryEntity());
29+
30+
ClubStamp clubStamp = ClubStamp.getByClubCode(request.getClubCode());
31+
stampHistory.collectStamp(clubStamp, collectedAt);
32+
33+
stampHistoryRepository.save(stampHistory);
34+
}
35+
36+
public CollectionResultResponse getCollectionResult(String studentNumber, String studentName) {
37+
StampHistory stampHistory = stampHistoryRepository.findStampHistoryByStudentNameAndStudentNumber(
38+
studentName, studentNumber)
39+
.orElse(StampHistory.builder()
40+
.studentNumber(studentNumber)
41+
.studentName(studentName).build());
42+
43+
List<CollectedStampsResponse> collectedStampsResponse = stampHistory.getCollectedStamps().keySet().stream()
44+
.map(stamp -> CollectedStampsResponse.of(stamp.getName(),
45+
stampHistory.getCollectedStamps().get(stamp)))
46+
.toList();
47+
boolean isCompleted = stampHistory.getCollectedStamps().size() == ClubStamp.values().length;
48+
return CollectionResultResponse.of(isCompleted, collectedStampsResponse);
49+
}
50+
51+
}

0 commit comments

Comments
 (0)