Skip to content

Commit

Permalink
[JT-38] 회차 등록 구현
Browse files Browse the repository at this point in the history
  • Loading branch information
kmebin committed Sep 5, 2023
2 parents eda2ab4 + a4f8744 commit 9d4a0c3
Show file tree
Hide file tree
Showing 9 changed files with 178 additions and 12 deletions.
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ project(':module-domain') {

dependencies {
implementation project(':module-core')
implementation project(':module-domain-s3')
implementation project(':module-domain-smtp')
implementation project(':module-internal')
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,28 @@
package com.devtoon.jtoon.webtoon.presentation;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import com.devtoon.jtoon.webtoon.application.WebtoonService;
import com.devtoon.jtoon.webtoon.request.CreateEpisodeReq;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;

@RestController
@RequiredArgsConstructor
@RequestMapping("/webtoons")
public class WebtoonController {

private final WebtoonService webtoonService;

@PostMapping("/{webtoonId}")
@ResponseStatus(HttpStatus.CREATED)
public void createEpisode(@PathVariable Long webtoonId, @Valid CreateEpisodeReq req) {
webtoonService.createEpisode(webtoonId, req);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.devtoon.jtoon.application;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import com.devtoon.jtoon.common.FileName;
import com.devtoon.jtoon.common.ImageType;
import com.devtoon.jtoon.util.S3Uploader;

import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class S3Service {

private final S3Uploader s3Uploader;

@Value("${spring.cloud.aws.cloud-front.url}")
private String IMAGE_URL;

//TODO 확장자 포함
public String upload(ImageType imageType, String webtoonTitle, FileName fileName, MultipartFile image) {
String key = imageType.getPath(webtoonTitle, fileName.getValue());
s3Uploader.upload(key, image);

return IMAGE_URL + key;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.devtoon.jtoon.common;

import java.util.UUID;

import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public class FileName {

private final String value;

public static FileName forWebtoon() {
return new FileName(UUID.randomUUID().toString());
}

public static FileName forEpisode(int no) {
return new FileName(String.format("%04d_", no) + UUID.randomUUID());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.devtoon.jtoon.common;

import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public enum ImageType {

WEBTOON_THUMBNAIL("webtoons/%s/thumbnail/%s"),
EPISODE_MAIN("webtoons/%s/episodes/main/%s"),
EPISODE_THUMBNAIL("webtoons/%s/episodes/thumbnail/%s"),
;

private final String pathFormat;

public String getPath(String webtoonTitle, String filename) {
return String.format(pathFormat, webtoonTitle, filename);
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package com.devtoon.jtoon;
package com.devtoon.jtoon.util;

import java.io.File;
import java.io.IOException;
import java.util.UUID;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
Expand All @@ -21,9 +19,7 @@ public class S3Uploader {
@Value("${spring.cloud.aws.s3.bucket}")
private String BUCKET;

public void upload(String directory, MultipartFile file) {
String key = directory + File.separator + UUID.randomUUID();

public void upload(String key, MultipartFile file) {
try {
s3Template.upload(
BUCKET,
Expand All @@ -35,4 +31,6 @@ public void upload(String directory, MultipartFile file) {
throw new RuntimeException(e);
}
}

//TODO 업로드는 성공했지만, 비즈니스 로직 실패 시 delete 처리 추가
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,50 @@
package com.devtoon.jtoon.webtoon.application;

import static com.devtoon.jtoon.common.ImageType.*;

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

import com.devtoon.jtoon.application.S3Service;
import com.devtoon.jtoon.common.FileName;
import com.devtoon.jtoon.webtoon.entity.Episode;
import com.devtoon.jtoon.webtoon.entity.Webtoon;
import com.devtoon.jtoon.webtoon.repository.EpisodeRepository;
import com.devtoon.jtoon.webtoon.repository.WebtoonRepository;
import com.devtoon.jtoon.webtoon.request.CreateEpisodeReq;

import lombok.RequiredArgsConstructor;

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

private final WebtoonRepository webtoonRepository;
private final EpisodeRepository episodeRepository;
private final S3Service s3Service;

@Transactional
public void createEpisode(Long webtoonId, CreateEpisodeReq req) {
Webtoon webtoon = getWebtoonById(webtoonId);
String mainUrl = s3Service.upload(
EPISODE_MAIN,
webtoon.getTitle(),
FileName.forEpisode(req.no()),
req.mainImage()
);
String thumbnailUrl = s3Service.upload(
EPISODE_THUMBNAIL,
webtoon.getTitle(),
FileName.forEpisode(req.no()),
req.thumbnailImage()
);
Episode episode = req.toEntity(webtoon, mainUrl, thumbnailUrl);
episodeRepository.save(episode);
}

private Webtoon getWebtoonById(Long webtoonId) {
return webtoonRepository.findById(webtoonId)
.orElseThrow(() -> new RuntimeException("존재하는 웹툰이 아닙니다."));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,16 @@ public class Episode extends BaseTimeEntity {
@Column(name = "no", nullable = false)
private int no;

@Column(name = "title", nullable = false, unique = true, length = 30)
@Column(name = "title", nullable = false, length = 30)
private String title;

@Column(name = "main_url", nullable = false, length = 65535)
private String mainUrl;

@ColumnDefault("'default thumbnail url'")
@Column(name = "thumbnail_url", nullable = false, length = 65535)
private String thumbnailUrl = "default thumbnail url";

@Column(name = "webtoon_url", nullable = false, length = 65535)
private String webtoonUrl;

@ColumnDefault("1")
@Column(name = "has_comment", nullable = false)
private boolean hasComment;
Expand All @@ -62,8 +62,8 @@ public class Episode extends BaseTimeEntity {
private Episode(
int no,
String title,
String mainUrl,
String thumbnailUrl,
String webtoonUrl,
boolean hasComment,
LocalDateTime openedAt,
Webtoon webtoon
Expand All @@ -74,8 +74,8 @@ private Episode(

this.no = no;
this.title = requireNonNull(title, "title is null");
this.mainUrl = requireNonNull(mainUrl, "mainUrl is null");
this.thumbnailUrl = thumbnailUrl;
this.webtoonUrl = requireNonNull(webtoonUrl, "webtoonUrl is null");
this.hasComment = hasComment;
this.openedAt = requireNonNull(openedAt, "openedAt is null");
this.webtoon = requireNonNull(webtoon, "webtoon is null");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.devtoon.jtoon.webtoon.request;

import java.time.LocalDateTime;

import org.springframework.web.multipart.MultipartFile;

import com.devtoon.jtoon.webtoon.entity.Episode;
import com.devtoon.jtoon.webtoon.entity.Webtoon;

import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;

public record CreateEpisodeReq(
@Min(1) int no,
@NotBlank @Size(max = 30) String title,
boolean hasComment,
@NotNull LocalDateTime openedAt,
@NotNull MultipartFile mainImage,
MultipartFile thumbnailImage
) {

public Episode toEntity(Webtoon webtoon, String mainUrl, String thumbnailUrl) {
return Episode.builder()
.no(no)
.title(title)
.hasComment(hasComment)
.openedAt(openedAt)
.mainUrl(mainUrl)
.thumbnailUrl(thumbnailUrl)
.webtoon(webtoon)
.build();
}
}

0 comments on commit 9d4a0c3

Please sign in to comment.