Skip to content
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package gg.agit.konect.domain.club.controller;

import org.springframework.http.ResponseEntity;

import gg.agit.konect.domain.club.dto.ClubRegistrationRequestDto;
import io.swagger.v3.oas.annotations.parameters.RequestBody;

public interface ClubRegistrationRequestApi {

ResponseEntity<Void> registerClub(
@RequestBody ClubRegistrationRequestDto request
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package gg.agit.konect.domain.club.controller;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import gg.agit.konect.domain.club.dto.ClubRegistrationRequestDto;
import gg.agit.konect.domain.club.service.ClubRegistrationRequestService;
import gg.agit.konect.global.auth.annotation.PublicApi;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;

@Tag(name = "Club Registration", description = "동아리 등록 요청 API")
@RestController
@RequestMapping("/clubs")
@RequiredArgsConstructor
public class ClubRegistrationRequestController implements ClubRegistrationRequestApi {

private final ClubRegistrationRequestService clubRegistrationRequestService;

@Override
@Operation(summary = "동아리 등록 요청", description = "비로그인 사용자가 새 동아리 등록을 요청합니다.")
@PostMapping("/registration-requests")
@PublicApi
public ResponseEntity<Void> registerClub(
@Valid @RequestBody ClubRegistrationRequestDto request
) {
clubRegistrationRequestService.register(request);
return ResponseEntity.status(HttpStatus.CREATED).build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package gg.agit.konect.domain.club.dto;

import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.NOT_REQUIRED;
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;

import java.util.List;

import gg.agit.konect.domain.club.enums.ClubCategory;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;

@Schema(name = "ClubRegistrationRequest", description = "동아리 등록 요청")
public record ClubRegistrationRequestDto(

@Schema(description = "대학교 명", example = "한국기술교육대학교", requiredMode = REQUIRED)
@NotBlank(message = "대학교 명은 필수입니다.")
String universityName,

@Schema(description = "동아리 명", example = "BCSD Lab", requiredMode = REQUIRED)
@NotBlank(message = "동아리 명은 필수입니다.")
@Size(max = 50, message = "동아리 명은 최대 50자입니다.")
String clubName,

@Schema(description = "동아리 분과", example = "ACADEMIC", requiredMode = REQUIRED)
@NotNull(message = "동아리 분과는 필수입니다.")
ClubCategory clubCategory,

@Schema(description = "동아리 주제", example = "코딩", requiredMode = REQUIRED)
@NotBlank(message = "동아리 주제는 필수입니다.")
@Size(max = 20, message = "동아리 주제는 최대 20자입니다.")
String clubTopic,

@Schema(description = "동아리 이모지", example = "💻", requiredMode = REQUIRED)
@NotBlank(message = "동아리 이모지는 필수입니다.")
@Size(max = 10, message = "동아리 이모지는 최대 10자입니다.")
String clubEmoji,

@Schema(description = "한 줄 소개 (최대 30자)", example = "코딩 동아리입니다.", requiredMode = REQUIRED)
@NotBlank(message = "한 줄 소개는 필수입니다.")
@Size(max = 30, message = "한 줄 소개는 최대 30자입니다.")
String shortDescription,

@Schema(description = "동아리 소개 (최대 2000자)", example = "상세한 동아리 소개 내용...", requiredMode = REQUIRED)
@NotBlank(message = "동아리 소개는 필수입니다.")
@Size(max = 2000, message = "동아리 소개는 최대 2000자입니다.")
String fullIntroduction,

@Schema(
description = "사진 및 영상 URL 목록 (최대 5개)",
example = "[\"https://example.com/image1.jpg\"]",
requiredMode = NOT_REQUIRED
)
@Size(max = 5, message = "사진 및 영상은 최대 5개까지 업로드 가능합니다.")
List<String> imageUrls
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package gg.agit.konect.domain.club.event;

import gg.agit.konect.domain.club.model.ClubRegistrationRequest;

public record ClubRegistrationRequestedEvent(
Integer requestId,
String universityName,
String clubName,
String category,
String topic,
String emoji,
String description,
int imageCount
) {

public static ClubRegistrationRequestedEvent from(ClubRegistrationRequest request) {
return new ClubRegistrationRequestedEvent(
request.getId(),
request.getUniversityName(),
request.getClubName(),
request.getClubCategory().getDescription(),
request.getClubTopic(),
request.getClubEmoji(),
request.getShortDescription(),
request.getImages().size()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package gg.agit.konect.domain.club.model;

import static jakarta.persistence.EnumType.STRING;
import static jakarta.persistence.GenerationType.IDENTITY;
import static lombok.AccessLevel.PROTECTED;

import java.util.ArrayList;
import java.util.List;

import gg.agit.konect.domain.club.enums.ClubCategory;
import gg.agit.konect.global.model.BaseEntity;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import jakarta.persistence.OrderBy;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotNull;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@Entity
@Table(name = "club_registration_request")
@NoArgsConstructor(access = PROTECTED)
public class ClubRegistrationRequest extends BaseEntity {

@Id
@GeneratedValue(strategy = IDENTITY)
@Column(name = "id", nullable = false, updatable = false, unique = true)
private Integer id;

@NotNull
@Column(name = "university_name", nullable = false)
private String universityName;

@NotNull
@Column(name = "club_name", length = 50, nullable = false)
private String clubName;

@NotNull
@Enumerated(value = STRING)
@Column(name = "club_category", nullable = false)
private ClubCategory clubCategory;

@NotNull
@Column(name = "club_topic", length = 20, nullable = false)
private String clubTopic;

@NotNull
@Column(name = "club_emoji", length = 10, nullable = false)
private String clubEmoji;

@NotNull
@Column(name = "short_description", length = 30, nullable = false)
private String shortDescription;

@NotNull
@Column(name = "full_introduction", columnDefinition = "TEXT", nullable = false)
private String fullIntroduction;

@OneToMany(mappedBy = "request", cascade = CascadeType.ALL, orphanRemoval = true)
@OrderBy("displayOrder ASC")
private List<ClubRegistrationRequestImage> images = new ArrayList<>();

@NotNull
@Enumerated(value = STRING)
@Column(name = "status", length = 20, nullable = false)
private RegistrationStatus status;

@Builder
private ClubRegistrationRequest(
Integer id,
String universityName,
String clubName,
ClubCategory clubCategory,
String clubTopic,
String clubEmoji,
String shortDescription,
String fullIntroduction,
RegistrationStatus status
) {
this.id = id;
this.universityName = universityName;
this.clubName = clubName;
this.clubCategory = clubCategory;
this.clubTopic = clubTopic;
this.clubEmoji = clubEmoji;
this.shortDescription = shortDescription;
this.fullIntroduction = fullIntroduction;
this.status = status != null ? status : RegistrationStatus.PENDING;
}

public void addImages(List<String> imageUrls) {
for (int i = 0; i < imageUrls.size(); i++) {
ClubRegistrationRequestImage image = ClubRegistrationRequestImage.builder()
.request(this)
.imageUrl(imageUrls.get(i))
.displayOrder(i)
.build();
this.images.add(image);
}
}

public enum RegistrationStatus {
PENDING, APPROVED, REJECTED
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package gg.agit.konect.domain.club.model;

import static jakarta.persistence.FetchType.LAZY;
import static jakarta.persistence.GenerationType.IDENTITY;
import static lombok.AccessLevel.PROTECTED;

import gg.agit.konect.global.model.BaseEntity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotNull;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@Entity
@Table(name = "club_registration_request_image")
@NoArgsConstructor(access = PROTECTED)
public class ClubRegistrationRequestImage extends BaseEntity {

@Id
@GeneratedValue(strategy = IDENTITY)
@Column(name = "id", nullable = false, updatable = false, unique = true)
private Integer id;

@NotNull
@ManyToOne(fetch = LAZY)
@JoinColumn(name = "request_id", nullable = false)
private ClubRegistrationRequest request;

@NotNull
@Column(name = "image_url", length = 500, nullable = false)
private String imageUrl;

@NotNull
@Column(name = "display_order", nullable = false)
private Integer displayOrder;

@Builder
Comment thread
github-code-quality[bot] marked this conversation as resolved.
Fixed
private ClubRegistrationRequestImage(
Integer id,
ClubRegistrationRequest request,
String imageUrl,
Integer displayOrder
) {
this.id = id;
this.request = request;
this.imageUrl = imageUrl;
this.displayOrder = displayOrder;
}

public static class ClubRegistrationRequestImageBuilder {

@Override
public String toString() {
return "ClubRegistrationRequestImage.ClubRegistrationRequestImageBuilder("
+ "id=" + id
+ ", imageUrl=" + imageUrl
+ ", displayOrder=" + displayOrder
+ ")";
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package gg.agit.konect.domain.club.repository;

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

import gg.agit.konect.domain.club.model.ClubRegistrationRequest;

@Repository
public interface ClubRegistrationRequestRepository extends JpaRepository<ClubRegistrationRequest, Integer> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package gg.agit.konect.domain.club.service;

import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import gg.agit.konect.domain.club.dto.ClubRegistrationRequestDto;
import gg.agit.konect.domain.club.event.ClubRegistrationRequestedEvent;
import gg.agit.konect.domain.club.model.ClubRegistrationRequest;
import gg.agit.konect.domain.club.repository.ClubRegistrationRequestRepository;
import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
@Transactional
public class ClubRegistrationRequestService {

private final ClubRegistrationRequestRepository clubRegistrationRequestRepository;
private final ApplicationEventPublisher applicationEventPublisher;

public void register(ClubRegistrationRequestDto request) {
ClubRegistrationRequest entity = ClubRegistrationRequest.builder()
.universityName(request.universityName())
.clubName(request.clubName())
.clubCategory(request.clubCategory())
.clubTopic(request.clubTopic())
.clubEmoji(request.clubEmoji())
.shortDescription(request.shortDescription())
.fullIntroduction(request.fullIntroduction())
.status(ClubRegistrationRequest.RegistrationStatus.PENDING)
.build();

// 이미지 추가
if (request.imageUrls() != null && !request.imageUrls().isEmpty()) {
entity.addImages(request.imageUrls());
}

ClubRegistrationRequest saved = clubRegistrationRequestRepository.save(entity);
applicationEventPublisher.publishEvent(ClubRegistrationRequestedEvent.from(saved));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

import gg.agit.konect.domain.club.enums.ClubCategory;
import gg.agit.konect.domain.university.enums.UniversityRegion;
import gg.agit.konect.domain.web.model.WebClub;
import gg.agit.konect.domain.web.model.WebUniversity;
import gg.agit.konect.domain.website.model.WebClub;
import gg.agit.konect.domain.website.model.WebUniversity;
import io.swagger.v3.oas.annotations.media.Schema;

public record WebsiteClubDetailResponse(
Expand Down
Loading
Loading