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
6 changes: 4 additions & 2 deletions .github/workflows/deploy-feature.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ on:
name: "Deploy feature to Cloud Run"

env:
PROJECT_ID: mayb-api
PROJECT_ID: mayb-api-458206
GAR_LOCATION: asia-northeast3
REPOSITORY: mayb-repo
IMAGE_NAME: mayb-api
Expand All @@ -28,11 +28,12 @@ jobs:

- uses: 'google-github-actions/auth@v2'
with:
credentials_json: ${{ secrets.GCP_SA_KEY }}
credentials_json: ${{ secrets.GCP_DEPLOY_SA_KEY }}

- uses: 'google-github-actions/setup-gcloud@v2'
with:
project_id: '${{ env.PROJECT_ID }}'

- name: 'Set variables'
run: |-
branch_name=$(echo "${{ github.head_ref }}" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/-/g' | sed -E 's/^-+|-+$//g')
Expand All @@ -45,6 +46,7 @@ jobs:
with:
distribution: temurin
java-version: 21

- uses: gradle/gradle-build-action@v2
- name: Make gradlew executable
run: chmod +x ./gradlew
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/remove-feature.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ on:
name: "Remove feature from Cloud Run"

env:
PROJECT_ID: mayb-api
PROJECT_ID: mayb-api-458206
REGION: asia-northeast1

jobs:
Expand All @@ -24,7 +24,7 @@ jobs:

- uses: 'google-github-actions/auth@v2'
with:
credentials_json: ${{ secrets.GCP_SA_KEY }}
credentials_json: ${{ secrets.GCP_DEPLOY_SA_KEY }}

- uses: 'google-github-actions/setup-gcloud@v1'
with:
Expand Down
11 changes: 11 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,16 @@ repositories {

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-validation'
developmentOnly 'org.springframework.boot:spring-boot-devtools'

// GCP
implementation platform('com.google.cloud:libraries-bom:26.59.0')
implementation 'com.google.cloud:google-cloud-storage'

// Security
implementation 'org.springframework.boot:spring-boot-starter-security'
testImplementation 'org.springframework.security:spring-security-test'
Expand All @@ -52,6 +57,12 @@ dependencies {
// Utils
implementation 'org.apache.commons:commons-lang3'

// WebP
// https://mvnrepository.com/artifact/com.sksamuel.scrimage/scrimage-core
implementation "com.sksamuel.scrimage:scrimage-core:4.3.1"
// https://mvnrepository.com/artifact/com.sksamuel.scrimage/scrimage-webp
implementation "com.sksamuel.scrimage:scrimage-webp:4.3.1"

// swagger
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0'

Expand Down
18 changes: 18 additions & 0 deletions src/main/java/kr/mayb/config/CommonConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.http.converter.FormHttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.security.task.DelegatingSecurityContextAsyncTaskExecutor;
import org.springframework.web.client.RestTemplate;

import java.nio.charset.StandardCharsets;
Expand All @@ -24,4 +27,19 @@ public RestTemplate restTemplate(RestTemplateBuilder builder) {
.readTimeout(Duration.ofSeconds(3))
.build();
}

@Primary
@Bean(name = "mayb-taskExecutor")
public ThreadPoolTaskExecutor maybThreadPoolTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(100);
executor.setQueueCapacity(20);
return executor;
}

@Bean
public DelegatingSecurityContextAsyncTaskExecutor maybAsyncTaskExecutor(ThreadPoolTaskExecutor delegate) {
return new DelegatingSecurityContextAsyncTaskExecutor(delegate);
}
}
17 changes: 17 additions & 0 deletions src/main/java/kr/mayb/config/GcsConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package kr.mayb.config;

import com.google.cloud.storage.Storage;
import com.google.cloud.storage.StorageOptions;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Slf4j
@Configuration
public class GcsConfig {
@Bean
public Storage storage() {
StorageOptions defaultInstance = StorageOptions.getDefaultInstance();
return defaultInstance.getService();
}
}
15 changes: 11 additions & 4 deletions src/main/java/kr/mayb/controller/AuthController.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,17 @@
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import kr.mayb.dto.AuthDto;
import kr.mayb.dto.MemberDto;
import kr.mayb.dto.MemberSignupRequest;
import kr.mayb.security.DenyAll;
import kr.mayb.security.PermitAll;
import kr.mayb.security.PermitAuthenticated;
import kr.mayb.service.AuthService;
import kr.mayb.util.response.ApiResponse;
import kr.mayb.util.response.Responses;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;

@Tag(name = "Auth", description = "인증 관련 API")
@DenyAll
Expand All @@ -27,6 +26,14 @@ public class AuthController {

private final AuthService authService;

@Operation(summary = "유저정보")
@PermitAuthenticated
@GetMapping("/auth/me")
public ResponseEntity<ApiResponse<MemberDto>> me() {
MemberDto response = authService.getInfo();
return Responses.ok(response);
}

@Operation(summary = "회원가입")
@PermitAll
@PostMapping("/auth/members")
Expand Down
44 changes: 44 additions & 0 deletions src/main/java/kr/mayb/controller/MemberController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package kr.mayb.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import kr.mayb.dto.MemberDto;
import kr.mayb.dto.MemberUpdateRequest;
import kr.mayb.facade.MemberFacade;
import kr.mayb.security.DenyAll;
import kr.mayb.security.PermitAuthenticated;
import kr.mayb.util.response.ApiResponse;
import kr.mayb.util.response.Responses;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

@Tag(name = "Member", description = "유저 관련 API")
@DenyAll
@RestController
@RequiredArgsConstructor
public class MemberController {

private final MemberFacade memberFacade;

@Operation(summary = "유저 프로필 이미지 업데이트")
@PermitAuthenticated
@PutMapping("/members/profile")
public ResponseEntity<ApiResponse<MemberDto>> updateProfile(@RequestParam("profile") MultipartFile file) {
MemberDto response = memberFacade.updateProfile(file);
return Responses.ok(response);
}

@Operation(summary = "유저 정보 업데이트")
@PermitAuthenticated
@PutMapping("/members/me")
public ResponseEntity<ApiResponse<MemberDto>> updateMember(@RequestBody @Valid MemberUpdateRequest request) {
MemberDto response = memberFacade.updateMember(request);
return Responses.ok(response);
}
}
9 changes: 6 additions & 3 deletions src/main/java/kr/mayb/data/model/Member.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,15 @@
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*;
import kr.mayb.enums.AccountStatus;
import kr.mayb.enums.Gender;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.BatchSize;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;

import kr.mayb.enums.Gender;
import org.hibernate.annotations.BatchSize;

@Getter
@Setter
@Table(schema = "mayb")
Expand All @@ -32,6 +31,9 @@ public class Member extends BaseEntity{
@Column
private String name;

@Column
private String profileUrl;

@Column
@Enumerated(EnumType.STRING)
private Gender gender;
Expand All @@ -51,6 +53,7 @@ public class Member extends BaseEntity{
@Column
private String idealType;


@JsonIgnore
@BatchSize(size = 10)
@ManyToMany(fetch = FetchType.LAZY)
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/kr/mayb/dto/MemberDto.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ public class MemberDto {

private String name;

private String profileUrl;

private Gender gender;

private LocalDate birthdate;
Expand All @@ -37,6 +39,7 @@ public static MemberDto of(Member member, String contact) {
member.getId(),
member.getEmail(),
member.getName(),
member.getProfileUrl(),
member.getGender(),
member.getBirthdate(),
member.getOccupation(),
Expand Down
13 changes: 13 additions & 0 deletions src/main/java/kr/mayb/dto/MemberUpdateRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package kr.mayb.dto;

import jakarta.validation.constraints.NotBlank;

public record MemberUpdateRequest(
@NotBlank
String name,

String introduction,

String idealType
) {
}
23 changes: 23 additions & 0 deletions src/main/java/kr/mayb/enums/GcsBucketPath.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package kr.mayb.enums;

import kr.mayb.error.BadRequestException;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
public enum GcsBucketPath {
PROFILE("profile/"),
PRODUCT_PROFILE("product_profile/"),
PRODUCT_DETAIL("product_detail/"),
;

private final String value;

public static String getPath(GcsBucketPath pathType) {
return switch (pathType) {
case PROFILE -> PROFILE.value;
case PRODUCT_PROFILE -> PRODUCT_PROFILE.value;
case PRODUCT_DETAIL -> PRODUCT_DETAIL.value;
default -> throw new BadRequestException("Invalid GcsPathType" + pathType);
};
}
}
14 changes: 14 additions & 0 deletions src/main/java/kr/mayb/error/ExternalApiException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package kr.mayb.error;

import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus
public class ExternalApiException extends RuntimeException {
public ExternalApiException() {
this("External API Exception");
}

public ExternalApiException(String message) {
super(message);
}
}
11 changes: 11 additions & 0 deletions src/main/java/kr/mayb/error/ImageConversionException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package kr.mayb.error;

public class ImageConversionException extends RuntimeException {
public ImageConversionException() {
this("Fail to compress img");
}

public ImageConversionException(String message) {
super(message);
}
}
38 changes: 38 additions & 0 deletions src/main/java/kr/mayb/facade/MemberFacade.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package kr.mayb.facade;

import io.micrometer.common.util.StringUtils;
import kr.mayb.dto.MemberDto;
import kr.mayb.dto.MemberUpdateRequest;
import kr.mayb.enums.GcsBucketPath;
import kr.mayb.service.ImageService;
import kr.mayb.service.MemberService;
import kr.mayb.util.ContextUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

@Component
@RequiredArgsConstructor
public class MemberFacade {


private final ImageService imageService;
private final MemberService memberService;

public MemberDto updateProfile(MultipartFile file) {
MemberDto member = ContextUtils.loadMember();
String profileUrl = imageService.upload(file, GcsBucketPath.PROFILE);

if (StringUtils.isNotBlank(member.getProfileUrl())) {
// Delete the old profile image if it exists
imageService.delete(member.getProfileUrl(), GcsBucketPath.PROFILE);
}

return memberService.updateProfile(member.getMemberId(), profileUrl);
}

public MemberDto updateMember(MemberUpdateRequest request) {
MemberDto member = ContextUtils.loadMember();
return memberService.updateMember(member.getMemberId(), request);
}
}
Loading
Loading