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
7 changes: 7 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ dependencies {
testImplementation 'org.testcontainers:mysql'
testImplementation 'io.rest-assured:rest-assured:5.3.2'
testImplementation 'org.springframework.boot:spring-boot-starter-test'

// presigned url
implementation platform('software.amazon.awssdk:bom:2.20.56')
implementation 'software.amazon.awssdk:s3'

// localstack
testImplementation 'org.testcontainers:localstack'
}

tasks.named('bootBuildImage') {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
package in.koreatech.koin.domain.ownershop.controller;

import static in.koreatech.koin.domain.user.model.UserType.OWNER;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

import in.koreatech.koin.domain.ownershop.dto.OwnerShopsRequest;
import in.koreatech.koin.domain.ownershop.dto.OwnerShopsResponse;
import static in.koreatech.koin.domain.user.model.UserType.OWNER;
import in.koreatech.koin.global.auth.Auth;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,33 +1,34 @@
package in.koreatech.koin.domain.ownershop.controller;

import static in.koreatech.koin.domain.user.model.UserType.OWNER;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

import in.koreatech.koin.domain.ownershop.dto.OwnerShopsRequest;
import in.koreatech.koin.domain.ownershop.dto.OwnerShopsResponse;
import in.koreatech.koin.domain.ownershop.service.OwnerShopService;
import static in.koreatech.koin.domain.user.model.UserType.OWNER;
import in.koreatech.koin.global.auth.Auth;
import lombok.RequiredArgsConstructor;

@Controller
@RequiredArgsConstructor
public class OwnerShopContoller implements OwnerShopApi {
public class OwnerShopController implements OwnerShopApi {
Comment on lines -18 to +19
Copy link
Collaborator

Choose a reason for hiding this comment

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

A

헐.. 대박


private final OwnerShopService ownerShopService;

@Override
@GetMapping("/owner/shops")
public ResponseEntity<OwnerShopsResponse> getOwnerShops(
@Auth(permit = {OWNER}) Long ownerId
) {
OwnerShopsResponse ownerShopsResponses = ownerShopService.getOwnerShops(ownerId);
return ResponseEntity.ok().body(ownerShopsResponses);
}

@Override
@PostMapping("/owner/shops")
public ResponseEntity<Void> createOwnerShops(
@Auth(permit = {OWNER}) Long ownerId,
@RequestBody OwnerShopsRequest ownerShopsRequest
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,7 @@
import in.koreatech.koin.domain.shop.dto.ShopResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;

import static io.swagger.v3.oas.annotations.enums.ParameterIn.PATH;

import java.util.List;

import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
package in.koreatech.koin.domain.shop.dto;

import static com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy;

import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.List;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonNaming;

import static com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy;
import in.koreatech.koin.domain.shop.model.MenuCategory;
import in.koreatech.koin.domain.shop.model.Shop;
import in.koreatech.koin.domain.shop.model.ShopCategoryMap;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@

import org.springframework.data.repository.Repository;

import in.koreatech.koin.domain.shop.exception.MenuNotFoundException;
import in.koreatech.koin.domain.shop.exception.ShopNotFoundException;
import in.koreatech.koin.domain.shop.model.Menu;
import in.koreatech.koin.domain.shop.model.Shop;

public interface ShopRepository extends Repository<Shop, Long> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;

import in.koreatech.koin.domain.timetable.dto.SemesterResponse;
import in.koreatech.koin.domain.timetable.dto.LectureResponse;
import in.koreatech.koin.domain.timetable.dto.SemesterResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
package in.koreatech.koin.domain.timetable.model;

import static jakarta.persistence.GenerationType.*;
import static lombok.AccessLevel.*;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import static jakarta.persistence.GenerationType.IDENTITY;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.AccessLevel;
import static lombok.AccessLevel.PROTECTED;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

import in.koreatech.koin.domain.user.dto.EmailCheckExistsRequest;
import in.koreatech.koin.domain.user.dto.NicknameCheckExistsRequest;
import in.koreatech.koin.domain.user.dto.StudentResponse;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package in.koreatech.koin.global.domain.email.config;
package in.koreatech.koin.global.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
Expand All @@ -14,14 +14,14 @@
public class AwsSesConfig {

@Value("${aws.ses.access-key}")
private String AWS_ACCESS_KEY_ID;
private String accessKey;

@Value("${aws.ses.secret-key}")
private String AWS_SECRET_KEY;
private String secretKey;

@Bean
public AmazonSimpleEmailServiceAsync amazonSimpleEmailServiceAsync() {
BasicAWSCredentials basicAWSCredentials = new BasicAWSCredentials(AWS_ACCESS_KEY_ID, AWS_SECRET_KEY);
BasicAWSCredentials basicAWSCredentials = new BasicAWSCredentials(accessKey, secretKey);

return AmazonSimpleEmailServiceAsyncClient.asyncBuilder()
.withCredentials(new AWSStaticCredentialsProvider(basicAWSCredentials))
Expand Down
23 changes: 23 additions & 0 deletions src/main/java/in/koreatech/koin/global/config/S3Config.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package in.koreatech.koin.global.config;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider;
import static software.amazon.awssdk.regions.Region.AP_NORTHEAST_2;
import software.amazon.awssdk.services.s3.presigner.S3Presigner;

@Configuration
public class S3Config {

/**
* S3Presigner 사용 후 close()를 권장하므로, Builder 를 반환하여 필요 시 객체를 만들어 사용 후 close 되도록 구현.
*/
@Bean
public S3Presigner.Builder s3PresignerBuilder() {
return S3Presigner.builder()
.credentialsProvider(InstanceProfileCredentialsProvider.create())
.region(AP_NORTHEAST_2);
}
}
7 changes: 7 additions & 0 deletions src/main/java/in/koreatech/koin/global/config/WebConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@
import java.util.List;

import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import in.koreatech.koin.global.auth.AuthArgumentResolver;
import in.koreatech.koin.global.auth.ExtractAuthenticationInterceptor;
import in.koreatech.koin.global.domain.upload.controller.ImageUploadDomainEnumConverter;
import in.koreatech.koin.global.ipaddress.IpAddressArgumentResolver;
import in.koreatech.koin.global.ipaddress.IpAddressInterceptor;
import lombok.RequiredArgsConstructor;
Expand Down Expand Up @@ -38,4 +40,9 @@ public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers)
resolvers.add(authArgumentResolver);
resolvers.add(ipAddressArgumentResolver);
}

@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new ImageUploadDomainEnumConverter());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@
public class SlackService {

@Value("${slack.notify_koin_event_url}")
private String notify_koin_event_url;
private String slackUrl;

private final RestTemplate restTemplate = new RestTemplate();

public void noticeEmailVerification(String email) {
SlackNotification slackNotification = SlackNotification.noticeEmailVerification(email, notify_koin_event_url);
SlackNotification slackNotification = SlackNotification.noticeEmailVerification(email, slackUrl);
noticeFor(slackNotification);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package in.koreatech.koin.global.domain.upload.controller;

import java.util.Arrays;

import org.springframework.core.convert.converter.Converter;

import in.koreatech.koin.global.domain.upload.exception.ImageUploadDomainNotFoundException;
import in.koreatech.koin.global.domain.upload.model.ImageUploadDomain;

public class ImageUploadDomainEnumConverter implements Converter<String, ImageUploadDomain> {

@Override
public ImageUploadDomain convert(String source) {
return Arrays.stream(ImageUploadDomain.values())
.filter(it -> it.name().equalsIgnoreCase(source))
.findAny()
.orElseThrow(() -> ImageUploadDomainNotFoundException.withDetail("source: " + source));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package in.koreatech.koin.global.domain.upload.controller;


import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

import in.koreatech.koin.domain.user.model.UserType;
import static in.koreatech.koin.domain.user.model.UserType.STUDENT;
import in.koreatech.koin.global.auth.Auth;
import in.koreatech.koin.global.domain.upload.dto.UploadUrlRequest;
import in.koreatech.koin.global.domain.upload.dto.UploadUrlResponse;
import in.koreatech.koin.global.domain.upload.model.ImageUploadDomain;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;

@Tag(name = "(Normal) Upload : 파일 업로드", description = "파일 업로드를 수행한다.")
public interface UploadApi {

@ApiResponses(
value = {
@ApiResponse(responseCode = "200"),
@ApiResponse(responseCode = "401", content = @Content(schema = @Schema(hidden = true))),
@ApiResponse(responseCode = "403", content = @Content(schema = @Schema(hidden = true))),
@ApiResponse(responseCode = "404", content = @Content(schema = @Schema(hidden = true))),
@ApiResponse(responseCode = "413", content = @Content(schema = @Schema(hidden = true))),
@ApiResponse(responseCode = "415", content = @Content(schema = @Schema(hidden = true))),
}
)
@Operation(summary = "파일을 업로드할 수 있는 URL 생성", description = """
- `items`
- `lands`
- `circles`
- `market`
- `shops`
- `members`
- `owners`
""")
@PostMapping("/{domain}/upload/url")
ResponseEntity<UploadUrlResponse> getPresignedUrl(
@PathVariable ImageUploadDomain domain,
@RequestBody @Valid UploadUrlRequest request,
@Auth(permit = {UserType.OWNER, STUDENT}) Long memberId
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package in.koreatech.koin.global.domain.upload.controller;

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

import in.koreatech.koin.domain.user.model.UserType;
import static in.koreatech.koin.domain.user.model.UserType.STUDENT;
import in.koreatech.koin.global.auth.Auth;
import in.koreatech.koin.global.domain.upload.dto.UploadUrlRequest;
import in.koreatech.koin.global.domain.upload.dto.UploadUrlResponse;
import in.koreatech.koin.global.domain.upload.model.ImageUploadDomain;
import in.koreatech.koin.global.domain.upload.service.UploadService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;

@RestController
@RequiredArgsConstructor
public class UploadController implements UploadApi {

private final UploadService uploadService;

@PostMapping("/{domain}/upload/url")
public ResponseEntity<UploadUrlResponse> getPresignedUrl(
@PathVariable ImageUploadDomain domain,
@RequestBody @Valid UploadUrlRequest request,
@Auth(permit = {UserType.OWNER, STUDENT}) Long memberId
) {
var response = uploadService.getPresignedUrl(domain, request);
return ResponseEntity.ok(response);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package in.koreatech.koin.global.domain.upload.dto;

import com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;

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

@JsonNaming(SnakeCaseStrategy.class)
public record UploadUrlRequest(
@Schema(description = "파일 크기", example = "1000")
Integer contentLength,

@Schema(description = "파일 타입", example = "image/png")
String contentType,

@Schema(description = "파일 이름", example = "hello.png")
String fileName
) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package in.koreatech.koin.global.domain.upload.dto;

import java.time.LocalDateTime;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;

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

@JsonNaming(SnakeCaseStrategy.class)
public record UploadUrlResponse(
@Schema(description = "파일을 업로드할 수 있는 url",
example = """
https://bucketname.ap-northeast-2.amazonaws.com/upload/domain/2000/00/00/d4cb13df-cf57-4612-b37d-80ecfa3f4621-1694847132589/image.jpg
?x-amz-acl=public-read
&X-Amz-Algorithm=AWS4-HMAC-SHA256
&X-Amz-Date=20000000T000000Z
&X-Amz-SignedHeaders=content-length%3Bcontent-type%3Bhost
&X-Amz-Expires=7199&X-Amz-Credential=AKIA6BRP3Q6L5PUD5W5Q%2F20230916%2Fap-northeast-2%2Fs3%2Faws4_request
&X-Amz-Signature=796esadfsadfxcv213f851431a88bc16c8db048f322b8993e21e4829c531
""")
String preSignedUrl,

@Schema(description = "업로드한 파일을 가져올 때 사용하는 url", example = "https://static.koreatech.in/2023/09/01/uuid/example.png")
String fileUrl,

@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "presigned url 만료 일자", example = "2023-01-01 12:34:56")
LocalDateTime expirationDate
) {

}
Loading