Skip to content

Commit

Permalink
fix : 충돌 해결
Browse files Browse the repository at this point in the history
  • Loading branch information
jw427 committed Aug 26, 2024
2 parents 7f57a62 + 5e16349 commit 4d40bbb
Show file tree
Hide file tree
Showing 43 changed files with 669 additions and 64 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -267,3 +267,6 @@ gradle-app.setting
# End of https://www.toptal.com/developers/gitignore/api/macos,intellij,windows,java,gradle

src/main/resources/application-secret.yml
src/main/resources/application-test.yml
src/main/generated

38 changes: 38 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
buildscript {
ext {
queryDslVersion = "5.0.0"
}
}

plugins {
id 'java'
id 'org.springframework.boot' version '3.2.8'
Expand Down Expand Up @@ -28,16 +34,48 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'io.jsonwebtoken:jjwt:0.9.1'
implementation 'javax.xml.bind:jaxb-api:2.3.1'

// QueryDSL
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"

compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
runtimeOnly 'com.mysql:mysql-connector-j'

annotationProcessor 'org.projectlombok:lombok'

testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

tasks.named('test') {
useJUnitPlatform()
}

/** QueryDSL start **/

// Querydsl 설정부
def generated = 'src/main/generated'

// querydsl QClass 파일 생성 위치를 지정
tasks.withType(JavaCompile) {
options.getGeneratedSourceOutputDirectory().set(file(generated))
}

// java source set 에 querydsl QClass 위치 추가
sourceSets {
main.java.srcDirs += [generated]
}

// gradle clean 시에 QClass 디렉토리 삭제
clean {
delete file(generated)
}

/** QueryDSL end **/

This file was deleted.

25 changes: 25 additions & 0 deletions src/main/java/wanted/media/content/controller/StatController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package wanted.media.content.controller;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import lombok.RequiredArgsConstructor;
import wanted.media.content.domain.dto.StatParam;
import wanted.media.content.domain.dto.StatResponse;
import wanted.media.content.service.StatService;

@RestController
@RequestMapping("/api")
@RequiredArgsConstructor
public class StatController {
private final StatService statService;

@GetMapping("/statistics")
public ResponseEntity<StatResponse> statistics(@ModelAttribute StatParam param) {
StatResponse response = StatResponse.from(statService.statistics(param));
return ResponseEntity.ok().body(response);
}
}
5 changes: 5 additions & 0 deletions src/main/java/wanted/media/content/domain/CountValueType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package wanted.media.content.domain;

public enum CountValueType {
COUNT, LIKE_COUNT, VIEW_COUNT, SHARE_COUNT;
}
5 changes: 5 additions & 0 deletions src/main/java/wanted/media/content/domain/StatDateType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package wanted.media.content.domain;

public enum StatDateType {
DATE, HOUR;
}
59 changes: 59 additions & 0 deletions src/main/java/wanted/media/content/domain/dto/StatParam.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package wanted.media.content.domain.dto;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Optional;

import wanted.media.content.domain.CountValueType;
import wanted.media.content.domain.StatDateType;
import wanted.media.exception.BadRequestException;
import wanted.media.exception.ErrorCode;

/**
*
* @param hashtag 계정, defaultValue = 본인 계정
* @param type DATE or HOUR, 필수 값
* @param start 검색 시작 기간, defaultValue = 오늘로 부터 7일 전
* @param end 검색 끝 기간, defaultValue = 오늘
* @param value COUNT or VIEW_COUNT, LIKE_COUNT, SHARE_COUNT, defaultValue = count
*/
public record StatParam(
String hashtag,
StatDateType type,
LocalDateTime start,
LocalDateTime end,
CountValueType value) {

public StatParam(String hashtag, StatDateType type, LocalDateTime start, LocalDateTime end, CountValueType value) {
// JWT 구현시 default 값 변경 예정
this.hashtag = (hashtag == null) ? "me" : hashtag;

// Type(DATE, HOUR)에 맞춰 기본 값 설정
this.start = switch (type) {
case DATE -> (start == null) ? LocalDateTime.of(LocalDate.now().minusDays(7), LocalTime.MIN) : start;
case HOUR -> (start == null) ? LocalDateTime.now().minusDays(7) : start;
};
this.end = switch (type) {
case DATE -> (end == null) ? LocalDateTime.of(LocalDate.now(), LocalTime.MAX) : end;
case HOUR -> (end == null) ? LocalDateTime.now() : end;
};
validateDateRange(start, end);

// Default 값 설정
this.value = (value == null) ? CountValueType.COUNT : value;

// Default 값 설정
this.type = Optional.ofNullable(type)
.orElseThrow(() -> new BadRequestException(ErrorCode.INVALID_PARAMETER));
}

/**
* start 일자가 end 일자보다 앞인지 검증
*/
private void validateDateRange(LocalDateTime start, LocalDateTime end) {
if (start.isAfter(end)) {
throw new BadRequestException(ErrorCode.INVALID_PARAMETER);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package wanted.media.content.domain.dto;

public record StatResponse(Long count) {

public static StatResponse from(Long count) {
return new StatResponse(count);
}
}

This file was deleted.

41 changes: 41 additions & 0 deletions src/main/java/wanted/media/content/repository/StatRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package wanted.media.content.repository;

import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import wanted.media.content.domain.dto.StatParam;

import static com.querydsl.core.types.ExpressionUtils.count;
import static wanted.media.content.domain.QPost.*;
import static wanted.media.user.domain.QUser.user;

@Repository
@RequiredArgsConstructor
public class StatRepository {
private final JPAQueryFactory queryFactory;

/**
* ex.
* SELECT SUM(view_count)
* FROM post p
* LEFT JOIN members m ON p.user_id = m.user_id
* where p.created_at between '2024-08-18 00:00:00' and '2024-08-25 23:59:59'
* and m.account = 'user1';
*/
public Long statistics(StatParam param) {
var selectQuery = switch (param.value()) {
case COUNT -> queryFactory.select(count(post.id));
case LIKE_COUNT -> queryFactory.select(post.likeCount.sum());
case VIEW_COUNT -> queryFactory.select(post.viewCount.sum());
case SHARE_COUNT -> queryFactory.select(post.shareCount.sum());
};

return selectQuery.from(post)
.leftJoin(user).on(post.user.userId.eq(user.userId))
.where(
post.createdAt.between(param.start(), param.end()),
post.user.account.eq(param.hashtag())
)
.fetchFirst();
}
}

This file was deleted.

19 changes: 19 additions & 0 deletions src/main/java/wanted/media/content/service/StatService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package wanted.media.content.service;

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

import lombok.RequiredArgsConstructor;
import wanted.media.content.domain.dto.StatParam;
import wanted.media.content.repository.StatRepository;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class StatService {
private final StatRepository statRepository;

public Long statistics(StatParam param) {
return statRepository.statistics(param);
}
}
8 changes: 8 additions & 0 deletions src/main/java/wanted/media/exception/BadRequestException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package wanted.media.exception;

public class BadRequestException extends BaseException {

public BadRequestException(ErrorCode errorCode) {
super(errorCode);
}
}
10 changes: 10 additions & 0 deletions src/main/java/wanted/media/exception/BaseException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package wanted.media.exception;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public class BaseException extends RuntimeException {
private final ErrorCode errorCode;
}
3 changes: 2 additions & 1 deletion src/main/java/wanted/media/exception/ErrorCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
@Getter
@RequiredArgsConstructor
public enum ErrorCode {
ENTITY_NOT_FOUND(HttpStatus.NOT_FOUND, "존재하지 않는 엔티티입니다.");
ENTITY_NOT_FOUND(HttpStatus.NOT_FOUND, "존재하지 않는 엔티티입니다."),
INVALID_PARAMETER(HttpStatus.BAD_REQUEST, "잘못된 요청입니다.");

private final HttpStatus status;
private final String message;
Expand Down
9 changes: 1 addition & 8 deletions src/main/java/wanted/media/exception/ErrorResponse.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,4 @@
package wanted.media.exception;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public class ErrorResponse {

private final int statusCode;
private final String message;
public record ErrorResponse(int statusCode, String message) {
}
9 changes: 6 additions & 3 deletions src/main/java/wanted/media/exception/NotFoundException.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package wanted.media.exception;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public class NotFoundException extends RuntimeException {
public NotFoundException(String message) {
super(message);
}
private final ErrorCode errorCode;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import wanted.media.exception.ErrorCode;
import wanted.media.exception.ErrorResponse;
import wanted.media.exception.NotFoundException;

Expand All @@ -18,7 +19,12 @@ public ResponseEntity<ErrorResponse> handleBadRequestException(BadRequestExcepti
}

@ExceptionHandler(NotFoundException.class)
public ResponseEntity<String> handleNotFoundException(NotFoundException e) {
return new ResponseEntity<>(e.getMessage(), HttpStatus.NOT_FOUND);
public ResponseEntity<ErrorResponse> handlePostNotFound(NotFoundException e) {
ErrorCode errorCode = e.getErrorCode();
ErrorResponse errorResponse = new ErrorResponse(
errorCode.getStatus().value(),
errorCode.getMessage()
);
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorResponse);
}
}
17 changes: 17 additions & 0 deletions src/main/java/wanted/media/global/config/QueryDslConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package wanted.media.global.config;

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

import com.querydsl.jpa.impl.JPAQueryFactory;

import jakarta.persistence.EntityManager;

@Configuration
public class QueryDslConfig {

@Bean
public JPAQueryFactory jpaQueryFactory(EntityManager em) {
return new JPAQueryFactory(em);
}
}
16 changes: 16 additions & 0 deletions src/main/java/wanted/media/global/config/WebConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package wanted.media.global.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import wanted.media.global.converter.StringToLocalDateTimeConverter;

@Configuration
public class WebConfig implements WebMvcConfigurer {

@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new StringToLocalDateTimeConverter());
}
}
Loading

0 comments on commit 4d40bbb

Please sign in to comment.