Skip to content

Commit

Permalink
Merge pull request #19 from co-co-gong/feat#18
Browse files Browse the repository at this point in the history
feat: friend crud, exception handler, controller style
  • Loading branch information
Zerohertz authored Oct 31, 2024
2 parents ac6f8a1 + 03ae5d8 commit ae11a6b
Show file tree
Hide file tree
Showing 35 changed files with 927 additions and 217 deletions.
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
## Docs

- [docker-compose](docs/docker-compose.md)
- [package-structure](docs/package-structure.md)
- [Package 구조](docs/package-structure.md)
- [Service layer 개발 방법](docs/service.md)
- [Controller 개발 방법](docs/controller.md)
- [Docker Compose 사용 방법](docs/docker-compose.md)
- [Kubernetes 사용 방법](docs/kubernetes.md)
4 changes: 4 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ dependencies {
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'

// MapStruct
implementation 'org.mapstruct:mapstruct:1.5.5.Final'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.5.Final'

// Test
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'com.h2database:h2'
Expand Down
28 changes: 28 additions & 0 deletions docs/controller.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
## Controller 규약

```java
@ResponseStatus(HttpStatus.OK) // 정상적 응답에 대한 HTTP status 작성
@GetMapping("/${URI}") // API 접근 URI 명시
@Operation(summary = "${SUMMARY}", description = "${DESCRIPTION}") // Swagger 내 문서화를 위한 설명 작성
// Controller의 모든 응답에 ApiResponseDto class 사용
// Generic을 통해 응답의 data type 명시
public ApiResponseDto<T> methodName(
HttpServletRequest request, // 직접적인 요청 정보가 필요할 때 사용
@RequestParam String param1, // 쿼리 스트링 또는 폼 데이터의 요청 파라미터
@PathVariable Long param2, // URL 경로의 변수 매핑
@RequestBody Dto param3, // 요청 본문(JSON 등)을 객체로 받음
HttpServletResponse response // 직접적인 응답 설정이 필요할 때 사용
) {
// Request header 사용 예시
String authHeader = request.getHeader("Authorization");

// 결과를 위한 business logic 실행
T data = domainService.businessLogic();

// Response header 추가 예시
response.setHeader(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken);

// 정상적인 응답 시 사용할 HTTP status 및 응답 data
return ApiResponseDto.success(HttpStatus.OK.value(), data);
}
```
91 changes: 91 additions & 0 deletions docs/kubernetes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
## Run

```bash
$ kubectl create ns co-co-gong
namespace/co-co-gong created
$ kubectl apply -n co-co-gong -f k8s
deployment.apps/backend created
service/backend created
ingressroute.traefik.io/co-co-gong created
deployment.apps/postgres created
service/postgres created
configmap/postgres-config created
secret/postgres-secret created
secret/oauth-secret created
secret/jwt-secret created
$ kubectl get all -n co-co-gong
NAME READY STATUS RESTARTS AGE
pod/backend-7ff75486b8-dtmh2 1/1 Running 0 3m39s
pod/postgres-58585c55b4-7vsrs 1/1 Running 0 3m39s

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/backend ClusterIP 10.99.127.11 <none> 8080/TCP 3m39s
service/postgres ClusterIP 10.109.182.198 <none> 5432/TCP 3m39s

NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/backend 1/1 1 1 3m39s
deployment.apps/postgres 1/1 1 1 3m39s

NAME DESIRED CURRENT READY AGE
replicaset.apps/backend-7ff75486b8 1 1 1 3m39s
replicaset.apps/postgres-58585c55b4 1 1 1 3m39s
```

## Logs

```bash
$ kubectl logs -n co-co-gong deploy/backend
Downloading https://services.gradle.org/distributions/gradle-8.8-bin.zip
.............10%.............20%.............30%.............40%.............50%.............60%..............70%.............80%.............90%...........
..100%

Welcome to Gradle 8.8!

Here are the highlights of this release:
- Running Gradle on Java 22
- Configurable Gradle daemon JVM
- Improved IDE performance for large projects

For more details see https://docs.gradle.org/8.8/release-notes.html

Starting a Gradle Daemon (subsequent builds will be faster)
> Task :compileJava
> Task :processResources UP-TO-DATE
> Task :classes
> Task :resolveMainClassName
> Task :bootJar
> Task :jar
> Task :assemble
> Task :compileTestJava UP-TO-DATE
> Task :processTestResources NO-SOURCE
> Task :testClasses UP-TO-DATE
OpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended
2024-10-25T09:16:56.853+09:00 INFO 286 --- [co-co-gong-server] [ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactor
y for persistence unit 'default'
2024-10-25T09:16:56.855+09:00 INFO 286 --- [co-co-gong-server] [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiat
ed...
2024-10-25T09:16:56.857+09:00 INFO 286 --- [co-co-gong-server] [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown complet
ed.
> Task :test
> Task :check
> Task :build

BUILD SUCCESSFUL in 28s
7 actionable tasks: 5 executed, 2 up-to-date

. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.3.2)
```
## Stop
```bash
$ kubectl delete ns co-co-gong
namespace "co-co-gong" deleted
```
27 changes: 27 additions & 0 deletions docs/service.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
## Exception Handling

> Service layer의 exception handling은 [`GlobalExceptionHandler`](../src/main/java/com/server/global/error/handler/GlobalExceptionHandler.java)를 통해 관리
```java
@RestControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(AuthException.class)
public ResponseEntity<ApiResponseDto<Object>> handleFriendException(AuthException e) {
return ResponseEntity.status(e.getStatus()).body(ApiResponseDto.error(e.getStatus(), e.getMessage()));
}

@ExceptionHandler(BusinessException.class)
public ResponseEntity<ApiResponseDto<Object>> handleFriendException(BusinessException e) {
return ResponseEntity.status(e.getStatus()).body(ApiResponseDto.error(e.getStatus(), e.getMessage()));
}

}
```

> [`ErrorCode`](../src/main/java/com/server/global/error/code)[`Exception`](../src/main/java/com/server/global/error/exception) 정의 후 아래 예시와 같이 service layer에서 예외 생성
```java
throw new AuthException(AuthErrorCode.OAUTH_PROCESS_ERROR);
throw new BusinessException(FriendErrorCode.RECEIPT_ALREADY_EXISTS);
```
10 changes: 5 additions & 5 deletions k8s/backend.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,12 @@ spec:
secretKeyRef:
name: jwt-secret
key: JWT_SECRET_KEY
command:
- "scripts/local.sh"
# command:
# - "sh"
# - "-c"
# - "tail -f /dev/null"
# - "scripts/local.sh"
command:
- "sh"
- "-c"
- "tail -f /dev/null"
volumeMounts:
- mountPath: /home/zerohertz/workspace
name: backend-storage
Expand Down
9 changes: 8 additions & 1 deletion scripts/local.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
#!/bin/bash

./gradlew build
rm -r build

if ./gradlew clean build; then
echo "Build success!"
else
echo "Build failed..."
exit 1
fi
java -jar build/libs/*SNAPSHOT.jar
2 changes: 2 additions & 0 deletions src/main/java/com/server/CoCoGongApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@SpringBootApplication
@EnableJpaAuditing
public class CoCoGongApplication {

public static void main(String[] args) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package com.server.domain.friend.controller;

import java.util.List;

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.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import com.server.domain.friend.dto.GetFriendOutDto;
import com.server.domain.friend.enums.FriendState;
import com.server.domain.friend.service.FriendService;
import com.server.global.dto.ApiResponseDto;
import com.server.global.jwt.JwtService;

import io.swagger.v3.oas.annotations.Operation;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@RequiredArgsConstructor
@RestController
@RequestMapping("/api/friends")
public class FriendController {

private final JwtService jwtService;
private final FriendService friendService;

@ResponseStatus(HttpStatus.CREATED)
@PostMapping("/request")
@Operation(summary = "친구 신청 생성", description = "친구 신청할 사용자의 이름을 입력하여 친구 신청 생성")
public ApiResponseDto<String> createFriendRequest(HttpServletRequest request,
@RequestParam String receiptUsername) {
String requestUsername = jwtService.extractUsernameFromToken(request).get();
friendService.createFriendRequest(requestUsername, receiptUsername);
return ApiResponseDto.success(HttpStatus.CREATED.value(),
String.format("A friend request from User '%s' to User '%s' has been created.",
requestUsername, receiptUsername));
}

@ResponseStatus(HttpStatus.OK)
@GetMapping("/request")
@Operation(summary = "친구 내역 조회", description = "사용자 기준 신청 보낸 내역")
public ApiResponseDto<List<GetFriendOutDto>> getFriendRequest(
@RequestParam(required = false) FriendState state,
HttpServletRequest request) {
String username = jwtService.extractUsernameFromToken(request).get();
List<GetFriendOutDto> getUserOutDtos = friendService.getRequestUser(username, state);
return ApiResponseDto.success(HttpStatus.OK.value(), getUserOutDtos);
}

@ResponseStatus(HttpStatus.OK)
@DeleteMapping("/request")
@Operation(summary = "친구 신청 취소", description = "친구 신청할 사용자의 이름을 입력하여 친구 신청 삭제")
public ApiResponseDto<String> deleteFriendRequest(HttpServletRequest request,
@RequestParam String receiptUsername) {
String requestUsername = jwtService.extractUsernameFromToken(request).get();
friendService.deleteFriendRequest(requestUsername, receiptUsername);
return ApiResponseDto.success(HttpStatus.OK.value(), String
.format("The friend request from '%s' to '%s' has been deleted.", requestUsername, receiptUsername));
}

@ResponseStatus(HttpStatus.OK)
@GetMapping("/receipt")
@Operation(summary = "친구 내역 조회", description = "사용자 기준 신청 받은 내역")
public ApiResponseDto<List<GetFriendOutDto>> getFriendReceipt(HttpServletRequest request,
@RequestParam(required = false) FriendState state) {
String username = jwtService.extractUsernameFromToken(request).get();
List<GetFriendOutDto> getUserOutDtos = friendService.getReceiptUser(username, state);
return ApiResponseDto.success(HttpStatus.OK.value(), getUserOutDtos);
}

@ResponseStatus(HttpStatus.CREATED)
@PutMapping("/receipt")
@Operation(summary = "친구 신청 승인", description = "친구 신청을 승인할 사용자의 이름을 입력하여 친구 신청 승인")
public ApiResponseDto<String> approveFriendRequest(HttpServletRequest request,
@RequestParam String requestUsername) {
String receiptUsername = jwtService.extractUsernameFromToken(request).get();
friendService.acceptFriendRequest(requestUsername, receiptUsername);
return ApiResponseDto.success(HttpStatus.CREATED.value(), String.format(
"The friend request from User '%s' to User '%s' has been accepted.", requestUsername, receiptUsername));
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
package com.server.domain.friend.dto;

import java.util.UUID;

import com.server.domain.friend.enums.FriendState;
import com.server.domain.user.dto.UserDto;

import lombok.Getter;
import lombok.Setter;

@Getter
public class FriendListDto {
private UUID id;
private UUID requestUserId;
private UUID receiptUserId;
@Setter
public class GetFriendOutDto extends UserDto {
private FriendState state;
}
Loading

0 comments on commit ae11a6b

Please sign in to comment.