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
15 changes: 15 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,21 @@ dependencies {
implementation 'net.coobird:thumbnailator:0.4.20'

implementation 'software.amazon.awssdk:s3'

// WebClient
implementation 'org.springframework.boot:spring-boot-starter-webflux'
Comment on lines +66 to +67
Copy link
Contributor

Choose a reason for hiding this comment

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

RestClient 대안도 존재하는데 무거운 웹플럭스 사용하신 이유가 궁금합니다!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

찾아보니 webflux는 스프링부트 개발환경에 적합하지 않다고 나오네요..
추후 시간이 된다면 RestClient 로 변경해 보겠습니다!

Copy link
Contributor

Choose a reason for hiding this comment

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

webflux, RestClient 각 기술이 가진 장단점을 비교분석 후 선택했으면 더 좋지 않았을까 싶네요👍


// JWT
implementation 'io.jsonwebtoken:jjwt-api:0.12.3'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.3'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.3'

// Netty MacOS
implementation('io.netty:netty-resolver-dns-native-macos') {
artifact {
classifier = "osx-aarch_64" // Apple Silicon
}
}
}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package kr.kro.photoliner.common.dto.response;

public record JwtResponse(
String accessToken
) {

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import kr.kro.photoliner.domain.album.dto.response.AlbumPhotoMarkersResponse;
import kr.kro.photoliner.domain.album.dto.response.AlbumsResponse;
import kr.kro.photoliner.domain.album.service.AlbumService;
import kr.kro.photoliner.global.auth.Auth;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
Expand All @@ -25,7 +26,6 @@
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
Expand All @@ -37,48 +37,53 @@ public class AlbumController {

@PostMapping
public ResponseEntity<AlbumCreateResponse> createAlbum(
@Valid @RequestBody AlbumCreateRequest request
@Valid @RequestBody AlbumCreateRequest request,
@Auth Long userId
) {
AlbumCreateResponse response = albumService.createAlbum(request);
AlbumCreateResponse response = albumService.createAlbum(userId, request);
return ResponseEntity.status(HttpStatus.CREATED).body(response);
}

@GetMapping
public ResponseEntity<AlbumsResponse> getAlbums(
@RequestParam Long userId,
@PageableDefault(sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable
@PageableDefault(sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable,
@Auth Long userId
) {
return ResponseEntity.ok(albumService.getAlbums(userId, pageable));
}

@PatchMapping("/{albumId}/title")
public ResponseEntity<Void> updateAlbumTitle(
@PathVariable Long albumId,
@RequestBody @Valid AlbumTitleUpdateRequest request
@RequestBody @Valid AlbumTitleUpdateRequest request,
@Auth Long userId
) {
albumService.updateAlbumTitle(albumId, request);
return ResponseEntity.noContent().build();
}

@DeleteMapping
public ResponseEntity<Void> deletePhoto(
@Valid @RequestBody AlbumDeleteRequest request
@Valid @RequestBody AlbumDeleteRequest request,
@Auth Long userId
) {
albumService.deleteAlbums(request);
return ResponseEntity.noContent().build();
}

@GetMapping("/{albumId}/photos")
public ResponseEntity<AlbumPhotoItemsResponse> getAlbumItems(
@PathVariable Long albumId
@PathVariable Long albumId,
@Auth Long userId
) {
return ResponseEntity.ok(albumService.getAlbumPhotoItems(albumId));
}

@PostMapping("/{albumId}/photos")
public ResponseEntity<Void> createAlbumItems(
@PathVariable Long albumId,
@RequestBody @Valid AlbumItemCreateRequest request
@RequestBody @Valid AlbumItemCreateRequest request,
@Auth Long userId
) {
albumService.createAlbumItems(albumId, request);
return ResponseEntity.noContent().build();
Expand All @@ -87,7 +92,8 @@ public ResponseEntity<Void> createAlbumItems(
@DeleteMapping("/{albumId}/photos")
public ResponseEntity<Void> deleteAlbumItems(
@PathVariable Long albumId,
@RequestBody @Valid AlbumItemDeleteRequest request
@RequestBody @Valid AlbumItemDeleteRequest request,
@Auth Long userId
) {
albumService.deleteAlbumItems(albumId, request);
return ResponseEntity.noContent().build();
Expand All @@ -96,7 +102,8 @@ public ResponseEntity<Void> deleteAlbumItems(
@GetMapping("/{albumId}/markers")
public ResponseEntity<AlbumPhotoMarkersResponse> getAlbumPhotoMarkers(
@PathVariable Long albumId,
@Valid AlbumPhotoMarkersRequest request
@Valid AlbumPhotoMarkersRequest request,
@Auth Long userId
) {
return ResponseEntity.ok(albumService.getAlbumPhotoMarkers(albumId, request));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
package kr.kro.photoliner.domain.album.dto.request;

import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;

public record AlbumCreateRequest(
@NotNull
Long userId,
@NotEmpty
String title
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ public class AlbumService {
private final GeometryFactory geometryFactory;

@Transactional
public AlbumCreateResponse createAlbum(AlbumCreateRequest request) {
User user = userRepository.findUserById(request.userId())
.orElseThrow(() -> CustomException.of(ApiResponseCode.NOT_FOUND_USER, "user id: " + request.userId()));
public AlbumCreateResponse createAlbum(Long userId, AlbumCreateRequest request) {
User user = userRepository.findUserById(userId)
.orElseThrow(() -> CustomException.of(ApiResponseCode.NOT_FOUND_USER, "user id: " + userId));
Album album = Album.builder()
.title(request.title())
.user(user)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import kr.kro.photoliner.domain.photo.dto.response.PresignedUrlResponse;
import kr.kro.photoliner.domain.photo.infra.S3CustomClient;
import kr.kro.photoliner.domain.photo.service.PhotoService;
import kr.kro.photoliner.global.auth.Auth;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
Expand All @@ -39,7 +40,7 @@ public class PhotoController {

@GetMapping
public ResponseEntity<PhotosResponse> getPhotos(
@RequestParam Long userId,
@Auth Long userId,
@RequestParam(required = false) Boolean hasLocation,
@RequestParam(required = false) Boolean hasCapturedDate,
@PageableDefault(sort = "capturedDt", direction = Sort.Direction.DESC) Pageable pageable
Expand All @@ -48,8 +49,11 @@ public ResponseEntity<PhotosResponse> getPhotos(
}

@GetMapping("/markers")
public ResponseEntity<PhotoMarkersResponse> getPhotoMarkers(@Valid PhotoMarkersRequest request) {
return ResponseEntity.ok(photoService.getPhotoMarkers(request));
public ResponseEntity<PhotoMarkersResponse> getPhotoMarkers(
@Valid PhotoMarkersRequest request,
@Auth Long userId
) {
return ResponseEntity.ok(photoService.getPhotoMarkers(userId, request));
}

@PostMapping("/presigned-urls")
Expand All @@ -62,14 +66,16 @@ public ResponseEntity<List<PresignedUrlResponse>> getPresignedUrls(

@PostMapping
public ResponseEntity<Void> createPhotos(
@Valid @RequestBody CreatePhotosRequest request
@Valid @RequestBody CreatePhotosRequest request,
@Auth Long userId
) {
photoService.createPhotos(request);
photoService.createPhotos(userId, request);
return ResponseEntity.status(HttpStatus.CREATED).build();
}

@PatchMapping("/{photoId}/captured-date")
public ResponseEntity<Void> updatePhotoCapturedDate(
@Auth Long userId,
@PathVariable Long photoId,
@Valid @RequestBody PhotoCapturedDateUpdateRequest request
) {
Expand All @@ -80,15 +86,17 @@ public ResponseEntity<Void> updatePhotoCapturedDate(
@PatchMapping("/{photoId}/location")
public ResponseEntity<Void> updatePhotoLocation(
@PathVariable Long photoId,
@Valid @RequestBody PhotoLocationUpdateRequest request
@Valid @RequestBody PhotoLocationUpdateRequest request,
@Auth Long userId
) {
photoService.updatePhotoLocation(photoId, request);
return ResponseEntity.noContent().build();
}

@DeleteMapping
public ResponseEntity<Void> deletePhoto(
@Valid @RequestBody DeletePhotosRequest request
@Valid @RequestBody DeletePhotosRequest request,
@Auth Long userId
) {
photoService.deletePhotos(request);
return ResponseEntity.noContent().build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,6 @@
import org.locationtech.jts.geom.Coordinate;

public record CreatePhotosRequest(
@NotNull
Long userId,

@NotNull
@NotEmpty
List<InnerPhoto> photos
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,9 @@

import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import org.locationtech.jts.geom.Coordinate;

public record PhotoMarkersRequest(
@NotNull @Min(0)
Long userId,

@Min(0) @Max(90)
double swLat,

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,20 +44,20 @@ public PhotosResponse getPhotosByIds(Long userId, Boolean hasLocation, Boolean h
}

@Transactional(readOnly = true)
public PhotoMarkersResponse getPhotoMarkers(PhotoMarkersRequest request) {
public PhotoMarkersResponse getPhotoMarkers(Long userId, PhotoMarkersRequest request) {
Point sw = geometryFactory.createPoint(request.getSouthWestCoordinate());
Point ne = geometryFactory.createPoint(request.getNorthEastCoordinate());

Photos photos = photoRepository.getByUserIdInBox(request.userId(), sw, ne);
Photos photos = photoRepository.getByUserIdInBox(userId, sw, ne);

return PhotoMarkersResponse.from(photos);
}

@Transactional
public void createPhotos(CreatePhotosRequest request) {
public void createPhotos(Long userId, CreatePhotosRequest request) {
List<Photo> photos = request.photos().stream()
.map(photo -> Photo.builder()
.userId(request.userId())
.userId(userId)
.fileName(photo.fileName())
.filePath(cdnURL + ORIGINAL_BASE_PATH + photo.uploadFileName())
.thumbnailPath(cdnURL + THUMBNAIL_BASE_PATH + photo.uploadFileName())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,52 @@
package kr.kro.photoliner.domain.user.controller;

import java.net.URI;
import kr.kro.photoliner.common.dto.response.JwtResponse;
import kr.kro.photoliner.domain.user.dto.response.UserInfoResponse;
import kr.kro.photoliner.domain.user.service.UserService;
import kr.kro.photoliner.global.auth.Auth;
import kr.kro.photoliner.global.kakao.login.service.KakaoAuthService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/users")
public class UserController {

private final UserService userService;
private final KakaoAuthService kakaoAuthService;
private static final String LOGIN_REDIRECT_URL = "http://localhost:5173/login/kakao";

@GetMapping("/login/kakao")
public ResponseEntity<JwtResponse> login(@RequestParam(value = "code") String authorizationCode) {
JwtResponse jwtResponse = userService.oAuthLogin(authorizationCode);

String redirectUrl = LOGIN_REDIRECT_URL + "#accessToken=" + jwtResponse.accessToken();

return ResponseEntity
.status(HttpStatus.FOUND)
.location(URI.create(redirectUrl))
.build();
}

@GetMapping("/login/kakao/authorization")
public ResponseEntity<Void> authorize() {
return ResponseEntity
.status(HttpStatus.FOUND)
.location(URI.create(kakaoAuthService.getAuthorizationRedirectUrl()))
.build();
}

@GetMapping("/info")
public ResponseEntity<UserInfoResponse> getUserInfo(
@Auth Long userId
) {
return ResponseEntity.ok(userService.getUserInfo(userId));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package kr.kro.photoliner.domain.user.dto.request;

import com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import jakarta.validation.constraints.NotNull;

@JsonNaming(value = SnakeCaseStrategy.class)
public record UserRefreshTokenRequest(
@NotNull(message = "refresh token 을 입력해주세요.")
String refreshToken
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package kr.kro.photoliner.domain.user.dto.response;

import kr.kro.photoliner.domain.user.model.User;

public record UserInfoResponse(
String name,
String email
) {

public static UserInfoResponse from(User user) {
return new UserInfoResponse(
user.getName(),
user.getEmail()
);
}
}
18 changes: 18 additions & 0 deletions src/main/java/kr/kro/photoliner/domain/user/model/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import kr.kro.photoliner.common.model.BaseEntity;
import kr.kro.photoliner.global.kakao.login.dto.response.KakaoProfileResponse;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
Expand All @@ -30,4 +31,21 @@ public class User extends BaseEntity {
@Column(name = "name", nullable = false)
private String name;

@Column(name = "email", nullable = false)
private String email;

public User(String username, String name, String email) {
this.username = username;
this.name = name;
this.email = email;
}

public static User from(KakaoProfileResponse profileResponse) {
return new User(
profileResponse.id().toString(),
profileResponse.kakaoAccount().profile().nickname(),
profileResponse.kakaoAccount().email()
);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
import org.springframework.data.repository.Repository;

public interface UserRepository extends Repository<User, Long> {
boolean existsByEmail(String email);

User save(User user);

Optional<User> findUserById(Long userId);

Optional<User> findUserByEmail(String email);
}
Loading
Loading