diff --git a/src/main/java/gg/agit/konect/domain/event/controller/EventApi.java b/src/main/java/gg/agit/konect/domain/event/controller/EventApi.java index 4f8c81630..3064e720a 100644 --- a/src/main/java/gg/agit/konect/domain/event/controller/EventApi.java +++ b/src/main/java/gg/agit/konect/domain/event/controller/EventApi.java @@ -8,6 +8,7 @@ import gg.agit.konect.domain.event.dto.EventBoothMapResponse; import gg.agit.konect.domain.event.dto.EventBoothsResponse; +import gg.agit.konect.domain.event.dto.EventContentsResponse; import gg.agit.konect.domain.event.dto.EventMiniEventsResponse; import gg.agit.konect.domain.event.dto.EventProgramsResponse; import gg.agit.konect.domain.event.enums.EventProgramType; @@ -54,4 +55,13 @@ ResponseEntity getEventMiniEvents( @RequestParam(defaultValue = "20") @Min(1) Integer limit, @UserId Integer userId ); + + @Operation(summary = "행사 콘텐츠 목록을 조회한다.") + @GetMapping("/{eventId}/contents") + ResponseEntity getEventContents( + @PathVariable Integer eventId, + @RequestParam(required = false) String category, + @RequestParam(defaultValue = "1") @Min(1) Integer page, + @RequestParam(defaultValue = "20") @Min(1) Integer limit + ); } diff --git a/src/main/java/gg/agit/konect/domain/event/controller/EventController.java b/src/main/java/gg/agit/konect/domain/event/controller/EventController.java index 74a841141..f7ecb7b40 100644 --- a/src/main/java/gg/agit/konect/domain/event/controller/EventController.java +++ b/src/main/java/gg/agit/konect/domain/event/controller/EventController.java @@ -6,6 +6,7 @@ import gg.agit.konect.domain.event.dto.EventBoothMapResponse; import gg.agit.konect.domain.event.dto.EventBoothsResponse; +import gg.agit.konect.domain.event.dto.EventContentsResponse; import gg.agit.konect.domain.event.dto.EventMiniEventsResponse; import gg.agit.konect.domain.event.dto.EventProgramsResponse; import gg.agit.konect.domain.event.enums.EventProgramType; @@ -42,4 +43,10 @@ public ResponseEntity getEventMiniEvents(Integer eventI Integer userId) { return ResponseEntity.ok(eventService.getEventMiniEvents(eventId, page, limit, userId)); } + + @Override + public ResponseEntity getEventContents(Integer eventId, String category, Integer page, + Integer limit) { + return ResponseEntity.ok(eventService.getEventContents(eventId, category, page, limit)); + } } diff --git a/src/main/java/gg/agit/konect/domain/event/dto/EventContentSummaryResponse.java b/src/main/java/gg/agit/konect/domain/event/dto/EventContentSummaryResponse.java new file mode 100644 index 000000000..c137ef301 --- /dev/null +++ b/src/main/java/gg/agit/konect/domain/event/dto/EventContentSummaryResponse.java @@ -0,0 +1,13 @@ +package gg.agit.konect.domain.event.dto; + +import java.time.LocalDateTime; + +public record EventContentSummaryResponse( + Integer contentId, + String title, + String thumbnailUrl, + String type, + String summary, + LocalDateTime publishedAt +) { +} diff --git a/src/main/java/gg/agit/konect/domain/event/dto/EventContentsResponse.java b/src/main/java/gg/agit/konect/domain/event/dto/EventContentsResponse.java new file mode 100644 index 000000000..459293fa1 --- /dev/null +++ b/src/main/java/gg/agit/konect/domain/event/dto/EventContentsResponse.java @@ -0,0 +1,12 @@ +package gg.agit.konect.domain.event.dto; + +import java.util.List; + +public record EventContentsResponse( + Long totalCount, + Integer currentCount, + Integer totalPage, + Integer currentPage, + List contents +) { +} diff --git a/src/main/java/gg/agit/konect/domain/event/enums/EventContentType.java b/src/main/java/gg/agit/konect/domain/event/enums/EventContentType.java new file mode 100644 index 000000000..082fca99c --- /dev/null +++ b/src/main/java/gg/agit/konect/domain/event/enums/EventContentType.java @@ -0,0 +1,7 @@ +package gg.agit.konect.domain.event.enums; + +public enum EventContentType { + ARTICLE, + IMAGE, + VIDEO +} diff --git a/src/main/java/gg/agit/konect/domain/event/model/EventContent.java b/src/main/java/gg/agit/konect/domain/event/model/EventContent.java new file mode 100644 index 000000000..0562797ab --- /dev/null +++ b/src/main/java/gg/agit/konect/domain/event/model/EventContent.java @@ -0,0 +1,59 @@ +package gg.agit.konect.domain.event.model; + +import static jakarta.persistence.EnumType.STRING; +import static jakarta.persistence.FetchType.LAZY; +import static jakarta.persistence.GenerationType.IDENTITY; +import static lombok.AccessLevel.PROTECTED; + +import java.time.LocalDateTime; + +import gg.agit.konect.domain.event.enums.EventContentType; +import gg.agit.konect.global.model.BaseEntity; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Entity +@Table(name = "event_content") +@NoArgsConstructor(access = PROTECTED) +public class EventContent extends BaseEntity { + + @Id + @GeneratedValue(strategy = IDENTITY) + @Column(name = "id", nullable = false, updatable = false, unique = true) + private Integer id; + + @ManyToOne(fetch = LAZY) + @JoinColumn(name = "event_id", nullable = false, updatable = false) + private Event event; + + @Column(name = "title", nullable = false, length = 100) + private String title; + + @Column(name = "summary", nullable = false, length = 255) + private String summary; + + @Column(name = "body", columnDefinition = "TEXT") + private String body; + + @Column(name = "thumbnail_url", length = 255) + private String thumbnailUrl; + + @Enumerated(STRING) + @Column(name = "type", nullable = false, length = 20) + private EventContentType type; + + @Column(name = "published_at") + private LocalDateTime publishedAt; + + @Column(name = "display_order", nullable = false) + private Integer displayOrder; +} diff --git a/src/main/java/gg/agit/konect/domain/event/repository/EventContentRepository.java b/src/main/java/gg/agit/konect/domain/event/repository/EventContentRepository.java new file mode 100644 index 000000000..10afa98ee --- /dev/null +++ b/src/main/java/gg/agit/konect/domain/event/repository/EventContentRepository.java @@ -0,0 +1,14 @@ +package gg.agit.konect.domain.event.repository; + +import java.util.List; + +import org.springframework.data.repository.Repository; + +import gg.agit.konect.domain.event.model.EventContent; + +public interface EventContentRepository extends Repository { + + List findAllByEventIdOrderByDisplayOrderAscIdAsc(Integer eventId); + + int countByEventId(Integer eventId); +} diff --git a/src/main/java/gg/agit/konect/domain/event/service/EventService.java b/src/main/java/gg/agit/konect/domain/event/service/EventService.java index f0f68d8dd..49682899c 100644 --- a/src/main/java/gg/agit/konect/domain/event/service/EventService.java +++ b/src/main/java/gg/agit/konect/domain/event/service/EventService.java @@ -10,6 +10,8 @@ import gg.agit.konect.domain.event.dto.EventBoothMapResponse; import gg.agit.konect.domain.event.dto.EventBoothSummaryResponse; import gg.agit.konect.domain.event.dto.EventBoothsResponse; +import gg.agit.konect.domain.event.dto.EventContentSummaryResponse; +import gg.agit.konect.domain.event.dto.EventContentsResponse; import gg.agit.konect.domain.event.dto.EventMiniEventSummaryResponse; import gg.agit.konect.domain.event.dto.EventMiniEventsResponse; import gg.agit.konect.domain.event.dto.EventProgramSummaryResponse; @@ -18,11 +20,13 @@ import gg.agit.konect.domain.event.model.EventBooth; import gg.agit.konect.domain.event.model.EventBoothMap; import gg.agit.konect.domain.event.model.EventBoothMapItem; +import gg.agit.konect.domain.event.model.EventContent; import gg.agit.konect.domain.event.model.EventMiniEvent; import gg.agit.konect.domain.event.model.EventProgram; import gg.agit.konect.domain.event.repository.EventBoothMapItemRepository; import gg.agit.konect.domain.event.repository.EventBoothMapRepository; import gg.agit.konect.domain.event.repository.EventBoothRepository; +import gg.agit.konect.domain.event.repository.EventContentRepository; import gg.agit.konect.domain.event.repository.EventMiniEventRepository; import gg.agit.konect.domain.event.repository.EventProgramRepository; import gg.agit.konect.domain.event.repository.EventRepository; @@ -40,6 +44,7 @@ public class EventService { private final EventBoothMapRepository eventBoothMapRepository; private final EventBoothMapItemRepository eventBoothMapItemRepository; private final EventMiniEventRepository eventMiniEventRepository; + private final EventContentRepository eventContentRepository; public EventProgramsResponse getEventPrograms(Integer eventId, EventProgramType type, Integer page, Integer limit, Integer userId) { @@ -99,6 +104,7 @@ public EventBoothMapResponse getEventBoothMap(Integer eventId) { .map(this::toEventBoothMapItemResponse) .toList(); + // 부스 목록 응답과 같은 구역 값을 맵에서도 그대로 내려 프론트가 별도 매핑 없이 재사용할 수 있게 맞춘다. List zones = booths.stream() .map(EventBoothMapResponse.BoothMapItemResponse::zone) .filter(zone -> zone != null && !zone.isBlank()) @@ -131,6 +137,31 @@ public EventMiniEventsResponse getEventMiniEvents(Integer eventId, Integer page, ); } + public EventContentsResponse getEventContents(Integer eventId, String category, Integer page, Integer limit) { + getEvent(eventId); + + List filteredContents = eventContentRepository.findAllByEventIdOrderByDisplayOrderAscIdAsc( + eventId).stream() + // 콘텐츠 타입 enum 이름과 동일한 문자열만 허용해 의도하지 않은 부분 일치를 막는다. + .filter(content -> category == null || category.isBlank() || content.getType() + .name() + .equalsIgnoreCase(category)) + .toList(); + + PagedResult pagedContents = paginate(filteredContents, page, limit); + List contents = pagedContents.items().stream() + .map(this::toEventContentSummaryResponse) + .toList(); + + return new EventContentsResponse( + (long)pagedContents.totalCount(), + contents.size(), + pagedContents.totalPage(), + page, + contents + ); + } + private void getEvent(Integer eventId) { eventRepository.findById(eventId) .orElseThrow(() -> CustomException.of(NOT_FOUND_EVENT)); @@ -196,6 +227,17 @@ private EventMiniEventSummaryResponse toEventMiniEventSummaryResponse(EventMiniE ); } + private EventContentSummaryResponse toEventContentSummaryResponse(EventContent content) { + return new EventContentSummaryResponse( + content.getId(), + content.getTitle(), + content.getThumbnailUrl(), + content.getType().name(), + content.getSummary(), + content.getPublishedAt() + ); + } + private record PagedResult(List items, int totalCount, int totalPage) { } } diff --git a/src/main/resources/db/migration/V70__add_event_tables.sql b/src/main/resources/db/migration/V70__add_event_tables.sql index caa894d5a..908eb9e35 100644 --- a/src/main/resources/db/migration/V70__add_event_tables.sql +++ b/src/main/resources/db/migration/V70__add_event_tables.sql @@ -94,3 +94,20 @@ CREATE TABLE IF NOT EXISTS event_mini_event FOREIGN KEY (event_id) REFERENCES event (id) ON DELETE CASCADE ); + +CREATE TABLE IF NOT EXISTS event_content +( + id INT AUTO_INCREMENT PRIMARY KEY, + event_id INT NOT NULL, + title VARCHAR(100) NOT NULL, + summary VARCHAR(255) NOT NULL, + body TEXT, + thumbnail_url VARCHAR(255), + type ENUM ('ARTICLE', 'IMAGE', 'VIDEO') NOT NULL, + published_at TIMESTAMP, + display_order INT NOT NULL DEFAULT 0, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP NOT NULL, + + FOREIGN KEY (event_id) REFERENCES event (id) ON DELETE CASCADE +);