Skip to content
Open
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
29 changes: 29 additions & 0 deletions src/main/java/gg/agit/konect/domain/event/controller/EventApi.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package gg.agit.konect.domain.event.controller;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import gg.agit.konect.domain.event.dto.EventProgramsResponse;
import gg.agit.konect.domain.event.enums.EventProgramType;
import gg.agit.konect.global.auth.annotation.UserId;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.constraints.Min;

@Tag(name = "(Normal) Event: 행사", description = "행사 API")
@RequestMapping("/events")
public interface EventApi {

@Operation(summary = "행사 프로그램 목록을 조회한다.")
@GetMapping("/{eventId}/programs")
ResponseEntity<EventProgramsResponse> getEventPrograms(
@PathVariable Integer eventId,
@RequestParam(defaultValue = "ALL") EventProgramType type,
@RequestParam(defaultValue = "1") @Min(1) Integer page,
@RequestParam(defaultValue = "20") @Min(1) Integer limit,
Comment on lines +25 to +26
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

[LEVEL: medium]
문제: limit 파라미터에 상한 검증이 없어 limit=100000 같은 과도한 요청이 그대로 허용됩니다.
영향: 현재 서비스가 DB 레벨 페이징이 아니라 메모리 기반 페이징을 사용하므로 큰 limit 요청에서 응답 지연과 메모리 사용량 급증으로 이어질 수 있습니다.
제안: @Max(예: 100) 또는 공통 페이징 정책 상수를 추가해 상한을 강제해 주세요, As per coding guidelines, "보안, 트랜잭션 경계, 예외 처리, N+1, 성능 회귀 가능성을 우선 점검한다."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/gg/agit/konect/domain/event/controller/EventApi.java` around
lines 25 - 26, Add an upper bound to the "limit" request parameter to prevent
excessive memory usage: update the EventApi controller parameter annotation for
the method using "limit" to include a `@Max` constraint (e.g., `@Max`(100)) or
reference a shared paging constant (e.g., PagingConstants.MAX_LIMIT) instead of
a hardcoded value; ensure the import for javax.validation.constraints.Max is
present and the parameter declaration for "limit" (currently
`@RequestParam`(defaultValue = "20") `@Min`(1) Integer limit) is changed to include
`@Max` so the framework rejects overly large requests before processing.

@UserId Integer userId
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package gg.agit.konect.domain.event.controller;

import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RestController;

import gg.agit.konect.domain.event.dto.EventProgramsResponse;
import gg.agit.konect.domain.event.enums.EventProgramType;
import gg.agit.konect.domain.event.service.EventService;
import lombok.RequiredArgsConstructor;

@RestController
@Validated
@RequiredArgsConstructor
public class EventController implements EventApi {

private final EventService eventService;

@Override
public ResponseEntity<EventProgramsResponse> getEventPrograms(Integer eventId, EventProgramType type, Integer page,
Integer limit,
Integer userId) {
return ResponseEntity.ok(eventService.getEventPrograms(eventId, type, page, limit, userId));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package gg.agit.konect.domain.event.dto;

public record EventProgramSummaryResponse(
Integer programId,
String title,
String description,
String thumbnailUrl,
Integer rewardPoint,
String status,
boolean participated
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package gg.agit.konect.domain.event.dto;

import java.util.List;

import gg.agit.konect.domain.event.enums.EventProgramType;

public record EventProgramsResponse(
Long totalCount,
Integer currentCount,
Integer totalPage,
Integer currentPage,
EventProgramType type,
List<EventProgramSummaryResponse> programs
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package gg.agit.konect.domain.event.enums;

public enum EventProgramType {
ALL,
POINT,
RESONANCE
Comment on lines +3 to +6
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

[LEVEL: medium] DB enum과 도메인 enum 값이 불일치합니다.

Line 4의 ALLsrc/main/resources/db/migration/V70__add_event_tables.sqlevent_program.type ENUM('POINT','RESONANCE')에 없어 EventProgram 저장 시 무결성 예외를 유발할 수 있습니다. 이후 프로그램 생성/수정 기능이 붙으면 런타임 실패 시나리오가 발생합니다. As per coding guidelines "확장성: 새 요구사항 추가 시 변경 범위가 최소화되는 구조인지(하드코딩 분기/값 여부 포함) 점검한다.", API 필터 전용 enum(ALL 포함)과 영속 enum(POINT,RESONANCE)을 분리하거나 저장 전 ALL 유입을 차단하세요.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/gg/agit/konect/domain/event/enums/EventProgramType.java` around
lines 3 - 6, The EventProgramType enum currently includes ALL which does not
exist in the DB enum (migration V70__add_event_tables.sql defines only 'POINT'
and 'RESONANCE'), causing integrity errors when persisting; fix by either (A)
remove API/filter-only value ALL from the persistent enum and create a separate
API-facing enum (e.g., EventProgramFilterType) used only for filtering, or (B)
keep EventProgramType for persistence (only POINT, RESONANCE) and explicitly
block/translate ALL before any save/update path (check controllers/services that
accept EventProgramType), updating usages of EventProgramType and any save logic
in EventProgram-related service methods so only valid DB values are persisted.

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package gg.agit.konect.domain.event.enums;

public enum EventProgressStatus {
UPCOMING,
ONGOING,
ENDED
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package gg.agit.konect.domain.event.enums;

public enum EventStatus {
DRAFT,
PUBLISHED,
ENDED
}
52 changes: 52 additions & 0 deletions src/main/java/gg/agit/konect/domain/event/model/Event.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package gg.agit.konect.domain.event.model;

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

import java.time.LocalDateTime;

import gg.agit.konect.domain.event.enums.EventStatus;
import gg.agit.konect.global.model.BaseEntity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.Getter;
import lombok.NoArgsConstructor;

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

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

@Column(name = "title", nullable = false, length = 100)
private String title;

@Column(name = "subtitle", length = 255)
private String subtitle;

@Column(name = "poster_image_url", length = 255)
private String posterImageUrl;

@Column(name = "notice", columnDefinition = "TEXT")
private String notice;

@Column(name = "start_at", nullable = false)
private LocalDateTime startAt;

@Column(name = "end_at", nullable = false)
private LocalDateTime endAt;

@Enumerated(EnumType.STRING)
@Column(name = "status", nullable = false, length = 20)
private EventStatus status;
}
59 changes: 59 additions & 0 deletions src/main/java/gg/agit/konect/domain/event/model/EventProgram.java
Original file line number Diff line number Diff line change
@@ -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 gg.agit.konect.domain.event.enums.EventProgressStatus;
import gg.agit.konect.domain.event.enums.EventProgramType;
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_program")
@NoArgsConstructor(access = PROTECTED)
public class EventProgram 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;

@Enumerated(STRING)
@Column(name = "type", nullable = false, length = 20)
private EventProgramType type;
Comment on lines +37 to +39
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

[LEVEL: medium]
문제: 영속 컬럼 type이 API 필터 값 ALL을 포함한 EventProgramType을 그대로 사용해 DB ENUM 정의(POINT, RESONANCE)와 의미가 섞여 있습니다.
영향: 추후 생성/수정 경로에서 type=ALL이 들어오면 SQL enum 불일치로 저장 실패가 발생합니다(재현: EventProgram.type=ALL persist).
제안: DB 매핑용 enum과 조회 필터용 enum을 분리해 영속 계층에는 저장 가능한 값만 남겨 주세요, As per coding guidelines, "확장성: 새 요구사항 추가 시 변경 범위가 최소화되는 구조인지(하드코딩 분기/값 여부 포함) 점검한다."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/gg/agit/konect/domain/event/model/EventProgram.java` around
lines 37 - 39, The EventProgram.entity field "type" currently uses
EventProgramType (which contains API-only value ALL) causing DB enum mismatch;
change the persistence model to use a dedicated enum that only contains storable
values (e.g., EventProgramPersistentType with POINT and RESONANCE) and update
the field in EventProgram to that enum (or add a JPA AttributeConverter between
EventProgramType and EventProgramPersistentType). Keep the API/filter enum
(EventProgramType or rename to EventProgramFilterType) for request handling, and
perform explicit mapping in controller/service code when persisting or querying
so that API-only values like ALL are not written to the DB.


@Column(name = "title", nullable = false, length = 100)
private String title;

@Column(name = "description", nullable = false, length = 255)
private String description;

@Column(name = "thumbnail_url", length = 255)
private String thumbnailUrl;

@Column(name = "reward_point")
private Integer rewardPoint;

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

@Column(name = "display_order", nullable = false)
private Integer displayOrder;
}
Original file line number Diff line number Diff line change
@@ -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.EventProgram;

public interface EventProgramRepository extends Repository<EventProgram, Integer> {

List<EventProgram> findAllByEventIdOrderByDisplayOrderAscIdAsc(Integer eventId);

int countByEventId(Integer eventId);
Comment on lines +11 to +13
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 서비스가 전체 조회 후 메모리 필터/페이지네이션을 수행하는지 확인
rg -n -A8 -B4 'findAllByEventIdOrderByDisplayOrderAscIdAsc|paginate\(|\.filter\(program -> type == EventProgramType.ALL' --type=java

Repository: BCSDLab/KONECT_BACK_END

Length of output: 4573


🏁 Script executed:

# EventProgram 엔티티의 type 필드 및 구조 확인
fd -n 'EventProgram.java' | head -5

Repository: BCSDLab/KONECT_BACK_END

Length of output: 297


🏁 Script executed:

# 코드베이스의 Pageable 패턴 사용 사례 확인
rg -n 'Pageable|Page<' --type=java | head -20

Repository: BCSDLab/KONECT_BACK_END

Length of output: 2942


🏁 Script executed:

# EventProgram 엔티티 파일 위치 확인 및 내용 검토
fd 'EventProgram.java' --type f

Repository: BCSDLab/KONECT_BACK_END

Length of output: 132


🏁 Script executed:

# EventProgram 엔티티의 type 필드 확인
rg -A 20 'public class EventProgram' --type=java | head -40

Repository: BCSDLab/KONECT_BACK_END

Length of output: 2082


🏁 Script executed:

# EventService의 getEventPrograms 메서드 전체 서명 및 호출 컨텍스트 확인
rg -B 5 'findAllByEventIdOrderByDisplayOrderAscIdAsc' src/main/java/gg/agit/konect/domain/event/service/EventService.java

Repository: BCSDLab/KONECT_BACK_END

Length of output: 437


🏁 Script executed:

# EventProgramType enum 확인
rg -A 10 'enum EventProgramType' --type=java

Repository: BCSDLab/KONECT_BACK_END

Length of output: 483


🏁 Script executed:

# Repository의 다른 쿼리 메서드들이 Pageable을 어떻게 사용하는지 확인
rg -B 2 -A 4 'Page<' src/main/java/gg/agit/konect/domain/studytime/repository/StudyTimeRankingRepository.java | head -20

Repository: BCSDLab/KONECT_BACK_END

Length of output: 629


[LEVEL: medium] 저장소가 타입 필터링과 페이지네이션을 지원하지 않아 메모리 처리를 유도합니다.

findAllByEventIdOrderByDisplayOrderAscIdAsc()는 모든 프로그램을 조회한 후 서비스에서 타입 필터와 페이지 계산을 메모리에서 처리하도록 강제합니다. 데이터가 증가하면 메모리 사용량 증가 및 응답 지연으로 이어질 수 있습니다.

eventIdtype을 매개변수로 받아 DB에서 필터링하고 Pageable을 통해 페이지네이션하는 쿼리 메서드를 추가하세요. (예: Page<EventProgram> findByEventIdAndTypeOrderByDisplayOrderAscIdAsc(Integer eventId, EventProgramType type, Pageable pageable))

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/gg/agit/konect/domain/event/repository/EventProgramRepository.java`
around lines 11 - 13, The repository currently exposes
findAllByEventIdOrderByDisplayOrderAscIdAsc which forces in-memory type
filtering and pagination; update EventProgramRepository to add a DB-filtered,
pageable query such as Page<EventProgram>
findByEventIdAndTypeOrderByDisplayOrderAscIdAsc(Integer eventId,
EventProgramType type, Pageable pageable), and ensure Page and Pageable
(org.springframework.data.domain) and EventProgramType are imported/available so
callers can request DB-side filtering and pagination instead of loading all
results into memory.

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package gg.agit.konect.domain.event.repository;

import java.util.Optional;

import org.springframework.data.repository.Repository;

import gg.agit.konect.domain.event.model.Event;

public interface EventRepository extends Repository<Event, Integer> {

Optional<Event> findById(Integer id);

Event save(Event event);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package gg.agit.konect.domain.event.service;

import static gg.agit.konect.global.code.ApiResponseCode.NOT_FOUND_EVENT;

import java.util.List;

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

import gg.agit.konect.domain.event.dto.EventProgramSummaryResponse;
import gg.agit.konect.domain.event.dto.EventProgramsResponse;
import gg.agit.konect.domain.event.enums.EventProgramType;
import gg.agit.konect.domain.event.model.EventProgram;
import gg.agit.konect.domain.event.repository.EventProgramRepository;
import gg.agit.konect.domain.event.repository.EventRepository;
import gg.agit.konect.global.exception.CustomException;
import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class EventService {

private final EventRepository eventRepository;
private final EventProgramRepository eventProgramRepository;

public EventProgramsResponse getEventPrograms(Integer eventId, EventProgramType type, Integer page, Integer limit,
Integer userId) {
eventRepository.findById(eventId)
.orElseThrow(() -> CustomException.of(NOT_FOUND_EVENT));

List<EventProgram> filteredPrograms = eventProgramRepository.findAllByEventIdOrderByDisplayOrderAscIdAsc(
eventId).stream()
.filter(program -> type == EventProgramType.ALL || program.getType() == type)
.toList();

PagedResult<EventProgram> pagedPrograms = paginate(filteredPrograms, page, limit);
Comment on lines +32 to +37
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

[LEVEL: high]
문제: findAllByEventId...로 전체를 읽은 뒤 타입 필터링과 페이징을 메모리에서 처리하고 있습니다.
영향: 프로그램 수가 많은 행사(예: 1만 건)에서 요청마다 전량 로드/정렬이 발생해 지연과 GC 부하로 운영 장애 가능성이 있습니다.
제안: type 조건과 Pageable을 Repository 쿼리로 내리고 count도 DB에서 계산하도록 변경해 주세요, As per coding guidelines, "보안, 트랜잭션 경계, 예외 처리, N+1, 성능 회귀 가능성을 우선 점검한다."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/gg/agit/konect/domain/event/service/EventService.java` around
lines 32 - 37, The current EventService loads all programs via
eventProgramRepository.findAllByEventIdOrderByDisplayOrderAscIdAsc and then
applies in-memory filtering and paginate, causing performance and GC issues;
change this to push filtering and paging into the repository by adding/using a
method that accepts eventId, type (handle EventProgramType.ALL by calling a
no-type variant or using dynamic query) and a Pageable, e.g.,
findByEventIdAndTypeOrderByDisplayOrderAscIdAsc or
findByEventIdOrderByDisplayOrderAscIdAsc(Pageable), have it return a
Page<EventProgram> (so DB computes the count) and update EventService to call
that repository method and remove the in-memory filter + paginate usage so
PagedResult is constructed from the Page content and total elements returned by
the DB.

List<EventProgramSummaryResponse> programs = pagedPrograms.items().stream()
.map(this::toEventProgramSummaryResponse)
.toList();

return new EventProgramsResponse(
(long)pagedPrograms.totalCount(),
programs.size(),
pagedPrograms.totalPage(),
page,
type,
programs
);
}

private <T> PagedResult<T> paginate(List<T> items, Integer page, Integer limit) {
int totalCount = items.size();
int fromIndex = Math.max((page - 1) * limit, 0);
int toIndex = Math.min(fromIndex + limit, totalCount);
List<T> pagedItems = fromIndex >= totalCount ? List.of() : items.subList(fromIndex, toIndex);
int totalPage = totalCount == 0 ? 0 : (int)Math.ceil((double)totalCount / limit);
return new PagedResult<>(pagedItems, totalCount, totalPage);
}

private EventProgramSummaryResponse toEventProgramSummaryResponse(EventProgram program) {
return new EventProgramSummaryResponse(
program.getId(),
program.getTitle(),
program.getDescription(),
program.getThumbnailUrl(),
program.getRewardPoint(),
program.getStatus().name(),
false
);
}

private record PagedResult<T>(List<T> items, int totalCount, int totalPage) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ public enum ApiResponseCode {
NOT_FOUND_NOTIFICATION_TOKEN(HttpStatus.NOT_FOUND, "알림 토큰을 찾을 수 없습니다."),
NOT_FOUND_NOTIFICATION_INBOX(HttpStatus.NOT_FOUND, "알림을 찾을 수 없습니다."),
NOT_FOUND_ADVERTISEMENT(HttpStatus.NOT_FOUND, "광고를 찾을 수 없습니다."),
NOT_FOUND_EVENT(HttpStatus.NOT_FOUND, "행사를 찾을 수 없습니다."),
NOT_FOUND_CLUB_SHEET_ID(HttpStatus.NOT_FOUND, "등록된 스프레드시트 ID가 없습니다. 먼저 스프레드시트 ID를 등록해 주세요."),
NOT_FOUND_GOOGLE_DRIVE_AUTH(HttpStatus.NOT_FOUND, "Google Drive 권한이 연결되지 않았습니다. 먼저 Drive 권한을 연결해 주세요."),

Expand Down
30 changes: 30 additions & 0 deletions src/main/resources/db/migration/V70__add_event_tables.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
CREATE TABLE IF NOT EXISTS event
(
Comment on lines +1 to +2
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

[LEVEL: medium]
문제: 버전드 Flyway 마이그레이션에서 CREATE TABLE IF NOT EXISTS를 사용하면 예상과 다른 기존 스키마가 있어도 마이그레이션이 성공 처리됩니다.
영향: 환경별 스키마 드리프트가 숨겨져 런타임에서 엔티티/쿼리 불일치 오류가 늦게 터질 수 있습니다.
제안: 버전드 마이그레이션에서는 IF NOT EXISTS를 제거해 스키마 불일치를 즉시 실패시키고 필요 시 별도 보정 마이그레이션으로 처리해 주세요, As per coding guidelines, "src/main/resources/db/migration/**/*.sql: ... 운영 영향 관점에서 검토한다."

Also applies to: 15-16

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/resources/db/migration/V70__add_event_tables.sql` around lines 1 -
2, Remove the "IF NOT EXISTS" from the versioned Flyway migration CREATE TABLE
statement so the migration fails on schema drift; specifically update the SQL in
V70__add_event_tables.sql by changing "CREATE TABLE IF NOT EXISTS event" to
"CREATE TABLE event" (and remove any other "IF NOT EXISTS" occurrences around
the event table creation referenced at lines 15-16), ensuring any intentional
conditional behavior is handled in a separate repeatable/corrective migration.

id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(100) NOT NULL,
subtitle VARCHAR(255),
poster_image_url VARCHAR(255),
notice TEXT,
start_at TIMESTAMP NOT NULL,
end_at TIMESTAMP NOT NULL,
status ENUM ('DRAFT', 'PUBLISHED', 'ENDED') NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP NOT NULL
);

CREATE TABLE IF NOT EXISTS event_program
(
id INT AUTO_INCREMENT PRIMARY KEY,
event_id INT NOT NULL,
type ENUM ('POINT', 'RESONANCE') NOT NULL,
title VARCHAR(100) NOT NULL,
description VARCHAR(255) NOT NULL,
thumbnail_url VARCHAR(255),
reward_point INT,
status ENUM ('UPCOMING', 'ONGOING', 'ENDED') NOT NULL,
display_order INT NOT NULL DEFAULT 0,
Comment on lines +18 to +25
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

[LEVEL: medium]
문제: event_program 테이블에 조회 패턴(event_id 필터 + display_order, id 정렬)에 맞는 복합 인덱스가 없습니다.
영향: 데이터가 늘면 filesort/추가 정렬 비용으로 프로그램 목록 API 응답 시간이 급격히 느려질 수 있습니다(재현 조건: 특정 행사 프로그램 대량 적재).
제안: INDEX idx_event_program_event_order (event_id, display_order, id)를 추가해 현재 조회 경로를 커버해 주세요, As per coding guidelines, "보안, 트랜잭션 경계, 예외 처리, N+1, 성능 회귀 가능성을 우선 점검한다."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/resources/db/migration/V70__add_event_tables.sql` around lines 18 -
25, Add a composite index to the event_program table to cover the common query
pattern filtering by event_id and ordering by display_order and id: create index
idx_event_program_event_order on event_program(event_id, display_order, id).
Modify the V70__add_event_tables.sql migration to include this INDEX declaration
alongside the event_program table definition (or as a separate CREATE INDEX
statement within the same migration) so that queries using event_id + ORDER BY
display_order, id avoid filesort and scale efficiently.

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
);
Loading