Skip to content

Commit

Permalink
TOONPICK v 0.1
Browse files Browse the repository at this point in the history
TOONPICK : v 0.1
  • Loading branch information
ImGdevel authored Feb 3, 2025
2 parents fef092c + e7521e9 commit 0009815
Show file tree
Hide file tree
Showing 92 changed files with 1,278 additions and 232 deletions.
34 changes: 34 additions & 0 deletions .github/workflows/backend-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Backend Build

on:
push:
branches:
- main
- develop
pull_request:
branches:
- main
- develop

jobs:
build:
runs-on: ubuntu-latest

steps:
- name: Checkout Repository (저장소 체크아웃)
uses: actions/checkout@v3

- name: Set up JDK 17 (JDK 17 설정)
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '17'
cache: 'gradle'

- name: Grant execute permission for gradlew (Gradle 실행 권한 부여)
run: chmod +x backend/gradlew

- name: 프로젝트 빌드
run: |
cd backend
./gradlew clean build -x test
7 changes: 7 additions & 0 deletions backend/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.gradle
/build
/src/test
*.jar
*.log
.DS_Store

17 changes: 17 additions & 0 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
FROM eclipse-temurin:17-jdk as builder

WORKDIR /app

COPY . /app

RUN chmod +x ./gradlew

RUN ./gradlew clean bootJar

FROM eclipse-temurin:17-jre
WORKDIR /app

COPY --from=builder /app/build/libs/*.jar app.jar

ENTRYPOINT ["java", "-jar", "/app/app.jar"]

4 changes: 4 additions & 0 deletions backend/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ plugins {
group = 'toonpick'
version = '0.0.1-SNAPSHOT'

bootJar {
archiveFileName = "toonpick-${version}.jar"
}

java {
sourceCompatibility = '17'
}
Expand Down
85 changes: 85 additions & 0 deletions backend/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
services:
spring-app:
image: toonpick-service-app:0.0.1
container_name: toonpick-service-app
build:
context: .
dockerfile: Dockerfile
ports:
- "8080:8080"
volumes:
- ./logs:/app/logs
environment:
LOG_FILE: /app/logs/application.log
SPRING_DATASOURCE_DATA_URL: jdbc:mariadb://toonpick-db:3306/toonpick-database?useSSL=false&useUnicode=true&serverTimezone=Asia/Seoul&allowPublicKeyRetrieval=true
SPRING_DATASOURCE_DATA_USERNAME: root
SPRING_DATASOURCE_DATA_PASSWORD: 1234
SPRING_DATASOURCE_META_URL: jdbc:mariadb://toonpick-db-meta:3306/meta?useSSL=false&useUnicode=true&serverTimezone=Asia/Seoul&allowPublicKeyRetrieval=true
SPRING_DATASOURCE_META_USERNAME: root
SPRING_DATASOURCE_META_PASSWORD: 1234
restart: always
depends_on:
mariadb:
condition: service_healthy
mariadb-meta:
condition: service_healthy
redis:
condition: service_started
networks:
- backend

mariadb:
image: mariadb:10.5
container_name: toonpick-db
environment:
MYSQL_ROOT_PASSWORD: 1234
MYSQL_DATABASE: toonpick-database
ports:
- "3306:3306"
volumes:
- mariadb-data:/var/lib/mysql
networks:
- backend
healthcheck:
test: ["CMD", "mysqladmin", "ping", "--host=localhost", "--user=root", "--password=1234"]
interval: 30s
retries: 5
timeout: 10s
start_period: 30s

mariadb-meta:
image: mariadb:10.5
container_name: toonpick-db-meta
environment:
MYSQL_ROOT_PASSWORD: 1234
MYSQL_DATABASE: meta
ports:
- "3307:3306"
volumes:
- mariadb-meta-data:/var/lib/mysql
networks:
- backend
healthcheck:
test: ["CMD", "mysqladmin", "ping", "--host=localhost", "--user=root", "--password=1234"]
interval: 30s
retries: 5
timeout: 10s
start_period: 30s

redis:
image: redis:6.0
container_name: toonpick-redis
ports:
- "6380:6379"
networks:
- backend

volumes:
mariadb-data:
driver: local
mariadb-meta-data:
driver: local

networks:
backend:
driver: bridge
Binary file removed backend/logs/info_2024-12-27.gz
Binary file not shown.
Binary file removed backend/logs/info_2025-01-03.gz
Binary file not shown.
Binary file added backend/logs/info_2025-01-28.gz
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
public class DataDBConfig {

@Bean
@ConfigurationProperties(prefix = "spring.datasource-data")
@ConfigurationProperties(prefix = "spring.datasource.data")
public DataSource dataDBSource() {
return DataSourceBuilder.create().build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public class MetaDBConfig {

@Primary
@Bean
@ConfigurationProperties(prefix = "spring.datasource-meta")
@ConfigurationProperties(prefix = "spring.datasource.meta")
public DataSource metaDBSource(){

return DataSourceBuilder.create().build();
Expand Down
31 changes: 31 additions & 0 deletions backend/src/main/java/toonpick/app/controller/HelloController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package toonpick.app.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;

@Controller
public class HelloController {

final private Logger logger = LoggerFactory.getLogger(HelloController.class);

@GetMapping("/api/public/ping")
private ResponseEntity<Map<String, Object>> testPing() {
Map<String, Object> response = new HashMap<>();

response.put("status", "success");
response.put("message", "TOONPICK 서비스가 정상적으로 작동 중입니다.");
response.put("timestamp", LocalDateTime.now());

logger.info("Ping 요청이 들어왔습니다. 응답: {}", response);

return ResponseEntity.status(HttpStatus.OK).body(response);
}
}
4 changes: 4 additions & 0 deletions backend/src/main/java/toonpick/app/exception/ErrorCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ public enum ErrorCode {
PERMISSION_DENIED(1002, "Permission denied"),

// 2xxx : Security error
AUTHENTICATION_FAILED(2000, "Authentication failed"),
INVALID_CREDENTIALS(2001, "Invalid username or password"),
INVALID_JSON_FORMAT(2002, "Invalid JSON format in request"),
REQUEST_BODY_READ_ERROR(2003, "Failed to read authentication request body"),

// 21xx : authorization error
USER_ALREADY_REGISTERED(2103, "User is already registered"),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package toonpick.app.exception.exception;

import lombok.Getter;
import toonpick.app.exception.ErrorCode;

@Getter
public class CustomAuthenticationException extends RuntimeException {
private final ErrorCode errorCode;

public CustomAuthenticationException(ErrorCode errorCode) {
super(errorCode.getMessage());
this.errorCode = errorCode;
}

public CustomAuthenticationException(ErrorCode errorCode, Throwable cause) {
super(errorCode.getMessage(), cause);
this.errorCode = errorCode;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import org.springframework.security.web.csrf.InvalidCsrfTokenException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import toonpick.app.exception.exception.CustomAuthenticationException;
import toonpick.app.exception.exception.UserAlreadyRegisteredException;

import java.nio.file.AccessDeniedException;
Expand All @@ -21,43 +22,44 @@ public class AuthenticationExceptionHandler {
@ExceptionHandler(UsernameNotFoundException.class)
public ResponseEntity<String> handleUsernameNotFoundException(UsernameNotFoundException ex) {
LOGGER.error("Username not found: {}", ex.getMessage());
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Username not found: " + ex.getMessage());
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getMessage());
}

@ExceptionHandler(AccessDeniedException.class)
public ResponseEntity<String> handleAccessDeniedException(AccessDeniedException ex) {
LOGGER.error("Access denied: {}", ex.getMessage());
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("Access is denied. " + ex.getMessage());
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(ex.getMessage());
}

@ExceptionHandler(CustomAuthenticationException.class)
public ResponseEntity<String> handleAuthenticationException(CustomAuthenticationException ex) {
LOGGER.error("Authentication Failed: {}", ex.getMessage());
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(ex.getMessage());
}

@ExceptionHandler(AuthenticationCredentialsNotFoundException.class)
public ResponseEntity<String> handleAuthenticationCredentialsNotFoundException(
AuthenticationCredentialsNotFoundException ex) {
LOGGER.error("Authentication credentials not found: {}", ex.getMessage());
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Authentication credentials are required.");
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(ex.getMessage());
}

@ExceptionHandler(UserAlreadyRegisteredException.class)
public ResponseEntity<String> handleUsernameAlreadyExistsException(UserAlreadyRegisteredException ex) {
LOGGER.error("Username already exists: {}", ex.getMessage());
return ResponseEntity.status(HttpStatus.CONFLICT).body("Username already exists: " + ex.getMessage());
return ResponseEntity.status(HttpStatus.CONFLICT).body(ex.getMessage());
}

@ExceptionHandler(InvalidCsrfTokenException.class)
public ResponseEntity<String> handleInvalidTokenException(InvalidCsrfTokenException ex) {
LOGGER.error("Invalid Token: {}", ex.getMessage());
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("Invalid CSRF token: " + ex.getMessage());
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(ex.getMessage());
}

@ExceptionHandler(NullPointerException.class)
public ResponseEntity<String> handleNullPointerException(NullPointerException ex) {
LOGGER.error("Null pointer exception: {}", ex.getMessage(), ex);
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Invalid request: " + ex.getMessage());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ex.getMessage());
}

@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleGeneralException(Exception ex) {
LOGGER.error("Unexpected error in authentication: {}", ex.getMessage(), ex);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("An unexpected error occurred: " + ex.getMessage());
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package toonpick.app.security.filter;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.FilterChain;
import jakarta.servlet.http.HttpServletRequest;
Expand All @@ -10,11 +11,14 @@
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.core.AuthenticationException;

import toonpick.app.exception.ErrorCode;
import toonpick.app.exception.exception.CustomAuthenticationException;
import toonpick.app.security.dto.LoginRequest;
import toonpick.app.security.handler.LoginFailureHandler;
import toonpick.app.security.handler.LoginSuccessHandler;
Expand Down Expand Up @@ -44,9 +48,18 @@ public Authentication attemptAuthentication(HttpServletRequest request, HttpServ

UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
return authenticationManager.authenticate(authenticationToken);
} catch (BadCredentialsException e) {
logger.error(ErrorCode.INVALID_CREDENTIALS.getMessage());
throw new CustomAuthenticationException(ErrorCode.INVALID_CREDENTIALS);
} catch (JsonProcessingException e) {
logger.error(ErrorCode.INVALID_JSON_FORMAT.getMessage());
throw new CustomAuthenticationException(ErrorCode.INVALID_JSON_FORMAT);
} catch (IOException e) {
logger.error(ErrorCode.REQUEST_BODY_READ_ERROR.getMessage());
throw new CustomAuthenticationException(ErrorCode.REQUEST_BODY_READ_ERROR);
} catch (Exception e) {
logger.error("Failed to parse authentication request body", e);
throw new AuthenticationServiceException("Failed to parse authentication request body", e);
logger.error(ErrorCode.UNKNOWN_ERROR.getMessage(), e);
throw new CustomAuthenticationException(ErrorCode.UNKNOWN_ERROR, e);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,8 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo
String role = authentication.getAuthorities().stream().
findFirst().map(GrantedAuthority::getAuthority).orElse("");

String refreshToken = tokenService.issueAccessToken(username, role);
String accessToken = tokenService.issueRefreshToken(username, role);

response.setHeader("Authorization", "Bearer " + accessToken);
response.addCookie(CookieUtils.createEmptyCookie(refreshToken));
String refreshToken = tokenService.issueRefreshToken(username, role);
response.addCookie(CookieUtils.createRefreshCookie(refreshToken));
response.setStatus(HttpStatus.OK.value());
logger.info("USER LOGIN SUCCESS (username-{})", username);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,12 @@ public void validateRefreshToken(String refreshToken) {
if (refreshToken == null || refreshToken.isEmpty()) {
throw new MissingJwtTokenException(ErrorCode.REFRESH_TOKEN_MISSING);
}

if (!"refresh".equals(jwtTokenProvider.getCategory(refreshToken))) {
throw new InvalidJwtTokenException(ErrorCode.REFRESH_TOKEN_INVALID, jwtTokenProvider.getCategory(refreshToken));
}
if (jwtTokenProvider.isExpired(refreshToken)) {
throw new ExpiredJwtTokenException(ErrorCode.EXPIRED_REFRESH_TOKEN);
}

if (!"refresh".equals(jwtTokenProvider.getCategory(refreshToken))) {
throw new InvalidJwtTokenException(ErrorCode.REFRESH_TOKEN_INVALID);
}
}

// 유저 정보 추출
Expand Down
Loading

0 comments on commit 0009815

Please sign in to comment.