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
4 changes: 4 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ dependencies {

// AWS S3
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'

// Image
implementation("com.sksamuel.scrimage:scrimage-core:4.3.5")
implementation("com.sksamuel.scrimage:scrimage-webp:4.3.5")
}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* Copyright (c) SKU 다시입을Lab
*/
package com.sku.refit.domain.event.controller;

import java.util.List;

import jakarta.validation.Valid;

import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import com.sku.refit.domain.event.dto.request.EventRequest.*;
import com.sku.refit.domain.event.dto.response.EventResponse.*;
import com.sku.refit.global.response.BaseResponse;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.tags.Tag;

@Tag(name = "행사", description = "행사 관련 API")
@RequestMapping("/api/events")
public interface EventController {

/* =========================
* Admin
* ========================= */

@PostMapping(value = "/admin", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@Operation(summary = "[관리자] 행사 생성", description = "행사를 생성합니다.")
ResponseEntity<BaseResponse<EventDetailResponse>> createEvent(
@Parameter(
description = "행사 정보",
content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))
@RequestPart("request")
@Valid
EventInfoRequest request,
@Parameter(
description = "대표 사진(썸네일)",
content = @Content(mediaType = MediaType.MULTIPART_FORM_DATA_VALUE))
@RequestPart("thumbnail")
MultipartFile thumbnail);

@PutMapping(value = "/admin/{id}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@Operation(summary = "[관리자] 행사 수정", description = "행사를 수정합니다.")
ResponseEntity<BaseResponse<EventDetailResponse>> updateEvent(
@PathVariable Long id,
@RequestPart("request") @Valid EventInfoRequest request,
@RequestPart(value = "thumbnail", required = false) MultipartFile thumbnail);

@DeleteMapping("/admin/{id}")
@Operation(summary = "[관리자] 행사 삭제", description = "행사 뿐만 아니라 대표사진 및 이미지들까지 모두 삭제합니다.")
ResponseEntity<BaseResponse<Void>> deleteEvent(@PathVariable Long id);

/* =========================
* Public List
* ========================= */

@GetMapping("/upcoming")
@Operation(summary = "예정된 행사 리스트", description = "예정된 행사만 조회합니다. D-day가 가까운 순(오름차순)으로 정렬합니다.")
ResponseEntity<BaseResponse<List<EventCardResponse>>> getUpcomingEvents();

@GetMapping("/end")
@Operation(summary = "종료된 행사 리스트", description = "종료된 행사만 조회합니다. 최근 종료 순(내림차순)으로 정렬합니다.")
ResponseEntity<BaseResponse<List<EventSimpleResponse>>> getEndedEvents();

@GetMapping
@Operation(
summary = "다가오는/예정된/종료된 행사 조회",
description = "다가오는(가장 가까운 D-day 1개) / 예정된(그 다음 1개) / 종료된(가장 최근 종료 1개)로 반환합니다.")
ResponseEntity<BaseResponse<EventGroupResponse>> getEventGroups();

/* =========================
* Detail
* ========================= */

@GetMapping("/{id}")
@Operation(
summary = "행사 상세 조회",
description = "행사 예약 여부 + 행사 정보 + 최근 예약 이미지 4장 + 4장 제외 의류수를 반환합니다.")
ResponseEntity<BaseResponse<EventDetailResponse>> getEventDetail(@PathVariable Long id);

@GetMapping("/{id}/img")
@Operation(summary = "행사 더보기 이미지 조회", description = "해당 행사의 예약에서 업로드된 모든 옷 사진을 최신 등록순으로 반환합니다.")
ResponseEntity<BaseResponse<List<EventImageResponse>>> getEventAllReservationImages(
@PathVariable Long id);

/* =========================
* Reservation
* ========================= */

@PostMapping(value = "/{id}/rsv", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@Operation(summary = "행사 예약", description = "예약 정보를 저장하고 업로드 이미지는 WebP로 저장합니다.")
ResponseEntity<BaseResponse<EventReservationResponse>> reserveEvent(
@PathVariable Long id,
@RequestPart("request") @Valid EventRsvRequest request,
@RequestPart(value = "clothImageList", required = false) List<MultipartFile> clothImageList);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* Copyright (c) SKU 다시입을Lab
*/
package com.sku.refit.domain.event.controller;

import java.util.List;

import jakarta.validation.Valid;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import com.sku.refit.domain.event.dto.request.EventRequest.EventInfoRequest;
import com.sku.refit.domain.event.dto.request.EventRequest.EventRsvRequest;
import com.sku.refit.domain.event.dto.response.EventResponse.EventCardResponse;
import com.sku.refit.domain.event.dto.response.EventResponse.EventDetailResponse;
import com.sku.refit.domain.event.dto.response.EventResponse.EventGroupResponse;
import com.sku.refit.domain.event.dto.response.EventResponse.EventImageResponse;
import com.sku.refit.domain.event.dto.response.EventResponse.EventReservationResponse;
import com.sku.refit.domain.event.dto.response.EventResponse.EventSimpleResponse;
import com.sku.refit.domain.event.service.EventService;
import com.sku.refit.global.response.BaseResponse;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@RestController
@RequiredArgsConstructor
@Slf4j
public class EventControllerImpl implements EventController {

private final EventService eventService;

@Override
public ResponseEntity<BaseResponse<EventDetailResponse>> createEvent(
@RequestPart("request") @Valid EventInfoRequest request,
@RequestPart("thumbnail") MultipartFile thumbnail) {

return ResponseEntity.ok(BaseResponse.success(eventService.createEvent(request, thumbnail)));
}

@Override
public ResponseEntity<BaseResponse<EventDetailResponse>> updateEvent(
@PathVariable Long id,
@RequestPart("request") @Valid EventInfoRequest request,
@RequestPart(value = "thumbnail", required = false) MultipartFile thumbnail) {

return ResponseEntity.ok(
BaseResponse.success(eventService.updateEvent(id, request, thumbnail)));
}

@Override
public ResponseEntity<BaseResponse<Void>> deleteEvent(@PathVariable Long id) {
eventService.deleteEvent(id);
return ResponseEntity.ok(BaseResponse.success(null));
}

@Override
public ResponseEntity<BaseResponse<List<EventCardResponse>>> getUpcomingEvents() {
return ResponseEntity.ok(BaseResponse.success(eventService.getUpcomingEvents()));
}

@Override
public ResponseEntity<BaseResponse<List<EventSimpleResponse>>> getEndedEvents() {
return ResponseEntity.ok(BaseResponse.success(eventService.getEndedEvents()));
}

@Override
public ResponseEntity<BaseResponse<EventGroupResponse>> getEventGroups() {
return ResponseEntity.ok(BaseResponse.success(eventService.getEventGroups()));
}

@Override
public ResponseEntity<BaseResponse<EventDetailResponse>> getEventDetail(@PathVariable Long id) {
return ResponseEntity.ok(BaseResponse.success(eventService.getEventDetail(id)));
}

@Override
public ResponseEntity<BaseResponse<List<EventImageResponse>>> getEventAllReservationImages(
@PathVariable Long id) {

return ResponseEntity.ok(BaseResponse.success(eventService.getEventAllReservationImages(id)));
}

@Override
public ResponseEntity<BaseResponse<EventReservationResponse>> reserveEvent(
@PathVariable Long id,
@RequestPart("request") @Valid EventRsvRequest request,
@RequestPart(value = "clothImageList", required = false) List<MultipartFile> clothImageList) {

return ResponseEntity.ok(
BaseResponse.success(eventService.reserveEvent(id, request, clothImageList)));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright (c) SKU 다시입을Lab
*/
package com.sku.refit.domain.event.dto.request;

import java.time.LocalDate;

import jakarta.validation.constraints.*;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;

public class EventRequest {

@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(title = "EventInfoRequest DTO", description = "행사 생성 및 수정 요청 데이터")
public static class EventInfoRequest {

@NotBlank(message = "행사명은 필수입니다.")
@Schema(description = "행사명", example = "2025 21% 파티")
private String name;

@NotBlank(message = "행사 설명은 필수입니다.")
@Schema(description = "행사 설명", example = "파티에 대한 짧은 설명입니다.")
private String description;

@NotNull(message = "행사 날짜는 필수입니다.") @Schema(description = "행사 날짜", example = "2025-12-24")
private LocalDate date;

@NotBlank(message = "행사 장소는 필수입니다.")
@Schema(description = "행사 장소", example = "서울 성동구")
private String location;

@Schema(description = "행사 상세 링크", example = "https://wearagain.org/48")
private String detailLink;
}

@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(title = "EventRsvRequest DTO", description = "행사 예약 요청 데이터")
public static class EventRsvRequest {

@NotBlank(message = "예약자 이름은 필수입니다.")
@Schema(description = "예약자 이름", example = "김재생")
private String name;

@NotBlank(message = "연락처는 필수입니다.")
@Schema(description = "연락처", example = "010-1234-5678")
private String phone;

@Email(message = "이메일 형식이 올바르지 않습니다.")
@NotBlank(message = "이메일은 필수입니다.")
@Schema(description = "이메일", example = "test@example.com")
private String email;

@Min(value = 0, message = "옷 수량은 0 이상이어야 합니다.")
@Schema(description = "가져올 옷 수량", example = "3")
private Integer clothCount;

@NotNull(message = "수신 동의 여부는 필수입니다.") @Schema(description = "마케팅/알림 수신 동의 여부", example = "true")
private Boolean marketingConsent;
}
}
Loading