Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
dc15b75
[REFACTOR] OAuth 및 Cookie 관련 코드 리팩토링 refactor 리팩토링 (#93)
leegwichan Feb 11, 2025
279bf95
[FEAT] 단건 조회에 토론 형식을 같이 반환하도록 수정 (#98)
coli-geonwoo Feb 13, 2025
e13cd49
[FEAT] 의회식 토론 EXPORT Api 구현 (#99)
coli-geonwoo Feb 18, 2025
067b71f
[FEAT] 시간총량제 테이블 Entity 구현 (#102)
leegwichan Feb 21, 2025
84a0d8c
[REFACTOR] 스프링 시작 시 발생하는 예외 설정 (#103)
leegwichan Feb 21, 2025
ca21785
[FEAT] 시간총량제 API 구현 (#106)
unifolio0 Mar 3, 2025
c360901
[CHORE] DB 형상 관리 도구 도입 (#107)
leegwichan Mar 3, 2025
8562642
[CHORE] 애플리케이션 에러로깅 도입 (#109)
coli-geonwoo Mar 5, 2025
8f0b6f5
[FEAT] 멤버의 테이블 조회 시, 테이블 순서 구현 (#110)
leegwichan Mar 7, 2025
785224e
[FIX] time auditing이 되지 않는 문제 개선 (#113)
coli-geonwoo Mar 9, 2025
f5ba8a7
[REFACTOR] 멤버 테이블 조회에서 소요시간 대신 주제 반환 (#114)
coli-geonwoo Mar 10, 2025
286bf85
feat : 의회식 토론 진행 API 구현
leegwichan Mar 10, 2025
916dd92
fix : 시간 총량제 테이블 수정 API Http Method 변경
leegwichan Mar 10, 2025
94d4f1a
feat : 시간 총량제 토론 진행 API 구현
leegwichan Mar 10, 2025
191bec8
refactor : TimeBoxes 의 타입 파라미터 명시
leegwichan Mar 10, 2025
68742b5
refactor : 사용하지 않는 인자 제거, 필요 없는 Wrapper 타입 제거
leegwichan Mar 10, 2025
cdf5775
test: 테스트 메서드 네이밍 수정
leegwichan Mar 10, 2025
961d367
refactor: 컨트롤러 메서드 네이밍 수정
leegwichan Mar 10, 2025
c00aed0
test: Service Test 시 Thread.sleep() 제거
leegwichan Mar 10, 2025
5e5bb3c
[FEAT] 사용자 지정 테이블 Entity 구현 (#118)
unifolio0 Mar 11, 2025
4e19c6f
[FEAT] Debate API 구현 (#119)
leegwichan Mar 11, 2025
ead31d6
[REFACTOR] agenda nullable하게 변경 (#123)
unifolio0 Mar 12, 2025
ecff34d
[FEAT] 사용자 지정 테이블 CRUD 구현 (#124)
coli-geonwoo Mar 18, 2025
e17482c
[REFACTOR] 테이블 명, 팀 이름 검증 수정 (#129)
unifolio0 Mar 27, 2025
7243932
[FEAT] 디스코드 알림 로직 추가 (#126)
coli-geonwoo Mar 28, 2025
f3760af
[HOTFIX] NullException처리 (#132)
unifolio0 Mar 29, 2025
31d9cde
[FEAT] 토큰이 비어있을 때 400 에러 처리 (#135)
leegwichan Mar 31, 2025
754c38c
[FEAT] 발언자 길이 및 발언유형 길이 검증 (#137)
coli-geonwoo Apr 2, 2025
10c9c37
[REFACTOR] Blank 검증을 DTO에서 일관성있게 수행 (#141)
coli-geonwoo Apr 2, 2025
4094cc3
[FEAT] 자유 토론 타임 박스 nullable 수정 (#146)
leegwichan Apr 4, 2025
cf8f2ca
[REFACTOR] 회원 테이블 조회 시 커스텀 테이블 포함 (#143)
coli-geonwoo Apr 4, 2025
f41ac29
[REFACTOR] speakerNumber parseInt에 따른 NPE 문제 개선 (#145)
leegwichan Apr 4, 2025
2d092c5
[REFACTOR] time_based 일 때 time을 null로 반환 (#148)
coli-geonwoo Apr 5, 2025
f7ebdfb
[CHORE] 모니터링 설정 추가 (#149)
leegwichan Apr 8, 2025
23e74b7
Merge branch 'main' into develop
leegwichan Apr 8, 2025
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: 11 additions & 4 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,17 @@ dependencies {
implementation 'org.apache.poi:poi-ooxml:5.2.3'
implementation 'org.apache.poi:poi:5.2.3'

// Logging
implementation 'org.springframework.boot:spring-boot-starter-log4j2'
implementation "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml"

// Discord
implementation 'net.dv8tion:JDA:5.0.0-beta.24'

// Monitoring
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'io.micrometer:micrometer-registry-prometheus'

// DB schema manager
implementation 'org.flywaydb:flyway-mysql'

Expand All @@ -64,10 +75,6 @@ dependencies {
testImplementation 'org.springframework.restdocs:spring-restdocs-restassured'
testImplementation 'com.epages:restdocs-api-spec-mockmvc:0.18.2'
testImplementation 'com.epages:restdocs-api-spec-restassured:0.18.2'

// Logging
implementation 'org.springframework.boot:spring-boot-starter-log4j2'
implementation "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml"
}

bootJar {
Expand Down
2 changes: 1 addition & 1 deletion scripts/dev/replace-new-version.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ fi

JAR_FILE=$(ls /home/ubuntu/app/*.jar | head -n 1)

sudo nohup java -Dspring.profiles.active=dev -Duser.timezone=Asia/Seoul -Dserver.port=8080 -jar "$JAR_FILE" &
sudo nohup java -Dspring.profiles.active=dev,monitor -Duser.timezone=Asia/Seoul -Dserver.port=8080 -jar "$JAR_FILE" &
2 changes: 1 addition & 1 deletion scripts/prod/replace-new-version.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ fi

JAR_FILE=$(ls /home/ubuntu/app/*.jar | head -n 1)

sudo nohup java -Dspring.profiles.active=prod -Duser.timezone=Asia/Seoul -Dserver.port=8080 -jar "$JAR_FILE" &
sudo nohup java -Dspring.profiles.active=prod,monitor -Duser.timezone=Asia/Seoul -Dserver.port=8080 -jar "$JAR_FILE" &
23 changes: 23 additions & 0 deletions src/main/java/com/debatetimer/client/notifier/ConsoleNotifier.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.debatetimer.client.notifier;

import java.util.Arrays;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class ConsoleNotifier implements ErrorNotifier {

private static final String ERROR_SEND_MESSAGE = "에러 정보가 채널로 발송되었습니다";

@Override
public void sendErrorMessage(Throwable throwable) {
log.error("{} : {}", ERROR_SEND_MESSAGE, throwable);
log.error(getStackTraceAsString(throwable));
}

private String getStackTraceAsString(Throwable throwable) {
return Arrays.stream(throwable.getStackTrace())
.map(StackTraceElement::toString)
.collect(Collectors.joining(System.lineSeparator()));
}
}
56 changes: 56 additions & 0 deletions src/main/java/com/debatetimer/client/notifier/DiscordNotifier.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.debatetimer.client.notifier;

import com.debatetimer.exception.custom.DTInitializationException;
import com.debatetimer.exception.errorcode.InitializationErrorCode;
import java.util.Arrays;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.JDABuilder;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;

@Slf4j
public class DiscordNotifier implements ErrorNotifier {

private static final String NOTIFICATION_PREFIX = ":rotating_light: [**Error 발생!**]\n";
private static final String STACK_TRACE_AFFIX = "\n```\n";
private static final String DISCORD_LINE_SEPARATOR = "\n";
private static final int STACK_TRACE_LENGTH = 10;

private final DiscordProperties properties;
private final JDA jda;

public DiscordNotifier(DiscordProperties discordProperties) {
this.properties = discordProperties;
this.jda = initializeJda(properties.getToken());
}

private JDA initializeJda(String token) {
try {
return JDABuilder.createDefault(token).build().awaitReady();
} catch (InterruptedException e) {
throw new DTInitializationException(InitializationErrorCode.JDA_INITIALIZATION_FAIL);
}
}

public void sendErrorMessage(Throwable throwable) {
TextChannel channel = jda.getTextChannelById(properties.getChannelId());
String errorMessage = throwable.toString();
String stackTrace = getStackTraceAsString(throwable);

String errorNotification = NOTIFICATION_PREFIX
+ errorMessage
+ STACK_TRACE_AFFIX
+ stackTrace
+ STACK_TRACE_AFFIX;
channel.sendMessage(errorNotification).queue();
}

private String getStackTraceAsString(Throwable throwable) {
return Arrays.stream(throwable.getStackTrace())
.map(StackTraceElement::toString)
.limit(STACK_TRACE_LENGTH)
.collect(Collectors.joining(DISCORD_LINE_SEPARATOR));
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.debatetimer.client.notifier;

import com.debatetimer.exception.custom.DTInitializationException;
import com.debatetimer.exception.errorcode.InitializationErrorCode;
import lombok.Getter;
import org.springframework.boot.context.properties.ConfigurationProperties;

@Getter
@ConfigurationProperties(prefix = "discord")
public class DiscordProperties {

private final String token;
private final String channelId;

public DiscordProperties(String token, String channelId) {
validate(token);
validate(channelId);
this.token = token;
this.channelId = channelId;
}

private void validate(String element) {
if (element == null || element.isBlank()) {
throw new DTInitializationException(InitializationErrorCode.DISCORD_PROPERTIES_EMPTY);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.debatetimer.client.notifier;

public interface ErrorNotifier {

void sendErrorMessage(Throwable throwable);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.debatetimer.client;
package com.debatetimer.client.oauth;

import com.debatetimer.aop.logging.LoggingClient;
import com.debatetimer.dto.member.MemberCreateRequest;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.debatetimer.client;
package com.debatetimer.client.oauth;

import com.debatetimer.dto.member.MemberCreateRequest;
import com.debatetimer.exception.custom.DTInitializationException;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.debatetimer.config;

import com.debatetimer.client.OAuthProperties;
import com.debatetimer.client.oauth.OAuthProperties;
import com.debatetimer.controller.tool.jwt.JwtTokenProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
Expand Down
39 changes: 39 additions & 0 deletions src/main/java/com/debatetimer/config/NotifierConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.debatetimer.config;


import com.debatetimer.client.notifier.ConsoleNotifier;
import com.debatetimer.client.notifier.DiscordNotifier;
import com.debatetimer.client.notifier.DiscordProperties;
import com.debatetimer.client.notifier.ErrorNotifier;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;

public class NotifierConfig {

@Profile({"dev", "prod"})
@Configuration
@RequiredArgsConstructor
@EnableConfigurationProperties(DiscordProperties.class)
public static class DiscordNotifierConfig {

private final DiscordProperties discordProperties;

@Bean
public ErrorNotifier discordNotifier() {
return new DiscordNotifier(discordProperties);
}
}

@Profile({"test", "local"})
@Configuration
public static class ConsoleNotifierConfig {

@Bean
public ErrorNotifier consoleNotifier() {
return new ConsoleNotifier();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.debatetimer.controller.customize;

import com.debatetimer.controller.auth.AuthMember;
import com.debatetimer.domain.member.Member;
import com.debatetimer.dto.customize.request.CustomizeTableCreateRequest;
import com.debatetimer.dto.customize.response.CustomizeTableResponse;
import com.debatetimer.service.customize.CustomizeService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
public class CustomizeController {

private final CustomizeService customizeService;

@PostMapping("/api/table/customize")
@ResponseStatus(HttpStatus.CREATED)
public CustomizeTableResponse save(
@Valid @RequestBody CustomizeTableCreateRequest tableCreateRequest,
@AuthMember Member member
) {
return customizeService.save(tableCreateRequest, member);
}

@GetMapping("/api/table/customize/{tableId}")
@ResponseStatus(HttpStatus.OK)
public CustomizeTableResponse getTable(
@PathVariable Long tableId,
@AuthMember Member member
) {
return customizeService.findTable(tableId, member);
}

@PutMapping("/api/table/customize/{tableId}")
@ResponseStatus(HttpStatus.OK)
public CustomizeTableResponse updateTable(
@Valid @RequestBody CustomizeTableCreateRequest tableCreateRequest,
@PathVariable Long tableId,
@AuthMember Member member
) {
return customizeService.updateTable(tableCreateRequest, tableId, member);
}

@PatchMapping("/api/table/customize/{tableId}/debate")
@ResponseStatus(HttpStatus.OK)
public CustomizeTableResponse debate(
@PathVariable Long tableId,
@AuthMember Member member
) {
return customizeService.updateUsedAt(tableId, member);
}

@DeleteMapping("/api/table/customize/{tableId}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteTable(
@PathVariable Long tableId,
@AuthMember Member member
) {
customizeService.deleteTable(tableId, member);
}
}
4 changes: 2 additions & 2 deletions src/main/java/com/debatetimer/domain/DebateTable.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public abstract class DebateTable extends BaseTimeEntity {

private static final String NAME_REGEX = "^[a-zA-Z가-힣0-9 ]+$";
private static final String NAME_REGEX = "^[\\p{L}\\p{M}\\p{N}\\p{P}\\p{Z}\\s]+$";
public static final int NAME_MAX_LENGTH = 20;

@NotNull
Expand Down Expand Up @@ -68,7 +68,7 @@ protected final void updateTable(DebateTable renewTable) {
}

private void validate(String name) {
if (name.isBlank() || name.length() > NAME_MAX_LENGTH) {
if (name.length() > NAME_MAX_LENGTH) {
throw new DTClientErrorException(ClientErrorCode.INVALID_TABLE_NAME_LENGTH);
}
if (!name.matches(NAME_REGEX)) {
Expand Down
29 changes: 19 additions & 10 deletions src/main/java/com/debatetimer/domain/DebateTimeBox.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,39 @@
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public abstract class DebateTimeBox {

public static final int SPEAKER_MAX_LENGTH = 5;

private int sequence;

@NotNull
@Enumerated(EnumType.STRING)
private Stance stance;

private int time;
private Integer speaker;
private String speaker;

protected DebateTimeBox(int sequence, Stance stance, int time, Integer speaker) {
protected DebateTimeBox(int sequence, Stance stance, int time, String speaker) {
validateSpeaker(speaker);
validateSequence(sequence);
validateTime(time);
validateSpeakerNumber(speaker);

this.sequence = sequence;
this.stance = stance;
this.time = time;
this.speaker = speaker;
this.speaker = initializeSpeaker(speaker);
}

private String initializeSpeaker(String speaker) {
if (speaker == null || speaker.isBlank()) {
return null;
}
return speaker;
}

private void validateSpeaker(String speaker) {
if (speaker != null && speaker.length() > SPEAKER_MAX_LENGTH) {
throw new DTClientErrorException(ClientErrorCode.INVALID_TIME_BOX_SPEAKER_LENGTH);
}
}

private void validateSequence(int sequence) {
Expand All @@ -46,10 +61,4 @@ private void validateTime(int time) {
throw new DTClientErrorException(ClientErrorCode.INVALID_TIME_BOX_TIME);
}
}

private void validateSpeakerNumber(Integer speaker) {
if (speaker != null && speaker <= 0) {
throw new DTClientErrorException(ClientErrorCode.INVALID_TIME_BOX_SPEAKER);
}
}
}
Loading