Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
6 changes: 5 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-validation'

// Security
implementation 'org.springframework.boot:spring-boot-starter-security'
// implementation 'org.springframework.boot:spring-boot-starter-security'

// Database & Cache
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
Expand All @@ -46,6 +46,10 @@ dependencies {
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

//swagger
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.0'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-api:2.8.0'
}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.whereyouad.WhereYouAd.domain.example.application.dto.request;

public record ExampleRequest(
String name
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.whereyouad.WhereYouAd.domain.example.application.dto.response;

import com.whereyouad.WhereYouAd.domain.example.persistence.entity.ExampleEntity;

public record ExampleResponse(
Long id,
String name
) {
public static ExampleResponse from(ExampleEntity example) {
return new ExampleResponse(
example.getId(),
example.getName()
);
}
}

Copy link
Collaborator

@jinnieusLab jinnieusLab Jan 17, 2026

Choose a reason for hiding this comment

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

P3: usecase๋Š” ์ €๋ฒˆ ์ปจ๋ฒค์…˜ ํšŒ์˜ ๋•Œ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ธฐ๋กœ ๋…ผ์˜ํ–ˆ๋˜ ๊ฒƒ ๊ฐ™์•„์„œ ๋นผ๋„ ๋  ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

๋นผ๊ฒ ์Šต๋‹ˆ๋‹ค!

Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.whereyouad.WhereYouAd.domain.example.domain.service;

import com.whereyouad.WhereYouAd.domain.example.application.dto.response.ExampleResponse;
import com.whereyouad.WhereYouAd.domain.example.exception.ExampleException;
import com.whereyouad.WhereYouAd.domain.example.exception.code.ExampleErrorCode;
import com.whereyouad.WhereYouAd.domain.example.persistence.entity.ExampleEntity;
import com.whereyouad.WhereYouAd.domain.example.persistence.repository.ExampleRepository;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
public class ExampleService {

private final ExampleRepository exampleRepository;

@Transactional
public Long save(String name) {
ExampleEntity example = ExampleEntity.builder()
.name(name)
.build();
return exampleRepository.save(example).getId();
}

public ExampleResponse findById(Long id) {
return ExampleResponse.from(
exampleRepository.findById(id)
.orElseThrow(() -> new ExampleException(ExampleErrorCode.EXAMPLE_NOT_FOUND))
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.whereyouad.WhereYouAd.domain.example.exception;

import com.whereyouad.WhereYouAd.global.exception.AppException;
import com.whereyouad.WhereYouAd.global.exception.BaseErrorCode;

public class ExampleException extends AppException {
public ExampleException(BaseErrorCode code) {
super(code);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.whereyouad.WhereYouAd.domain.example.exception.code;

import com.whereyouad.WhereYouAd.global.exception.BaseErrorCode;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.http.HttpStatus;

@Getter
@AllArgsConstructor
public enum ExampleErrorCode implements BaseErrorCode {

EXAMPLE_NOT_FOUND(HttpStatus.NOT_FOUND, "EXAMPLE-001", "์—”ํ‹ฐํ‹ฐ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.")
;

private final HttpStatus httpStatus;
private final String code;
private final String message;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.whereyouad.WhereYouAd.domain.example.persistence.entity;

import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Getter
@Table(name = "example_entity")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class ExampleEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "example_id")
private Long id;

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

@Builder
public ExampleEntity(String name) {
this.name = name;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.whereyouad.WhereYouAd.domain.example.persistence.repository;

import com.whereyouad.WhereYouAd.domain.example.persistence.entity.ExampleEntity;
import org.springframework.data.jpa.repository.JpaRepository;

public interface ExampleRepository extends JpaRepository<ExampleEntity, Long> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.whereyouad.WhereYouAd.domain.example.presentation;

import com.whereyouad.WhereYouAd.domain.example.application.dto.request.ExampleRequest;
import com.whereyouad.WhereYouAd.domain.example.application.dto.response.ExampleResponse;
import com.whereyouad.WhereYouAd.domain.example.domain.service.ExampleService;
import com.whereyouad.WhereYouAd.domain.example.presentation.docs.ExampleControllerDocs;
import com.whereyouad.WhereYouAd.global.response.DataResponse;
import com.whereyouad.WhereYouAd.global.response.DefaultIdResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
@RequestMapping("/api/examples")
public class ExampleController implements ExampleControllerDocs {

private final ExampleService exampleService;

@PostMapping
public ResponseEntity<DataResponse<DefaultIdResponse>> save(@RequestBody ExampleRequest request) {
return ResponseEntity.ok(
DataResponse.created(
DefaultIdResponse.of(exampleService.save(request.name()))
)
);
}

@GetMapping("/{id}")
public ResponseEntity<DataResponse<ExampleResponse>> findById(@PathVariable Long id) {
return ResponseEntity.ok(
DataResponse.from(exampleService.findById(id))
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.whereyouad.WhereYouAd.domain.example.presentation.docs;

import com.whereyouad.WhereYouAd.domain.example.application.dto.request.ExampleRequest;
import com.whereyouad.WhereYouAd.domain.example.application.dto.response.ExampleResponse;
import com.whereyouad.WhereYouAd.global.response.DataResponse;
import com.whereyouad.WhereYouAd.global.response.DefaultIdResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;

public interface ExampleControllerDocs {
@Operation(
summary = "์˜ˆ์‹œ ์ด๋ฆ„ ์ €์žฅ API",
description = "์ด๋ฆ„์„ ๋ฐ›์•„์™€์„œ DB์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค."
)
@ApiResponses({
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "์„ฑ๊ณต"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "์‹คํŒจ")
})
public ResponseEntity<DataResponse<DefaultIdResponse>> save(@RequestBody ExampleRequest request);

@Operation(
summary = "์˜ˆ์‹œ ์ด๋ฆ„ ์กฐํšŒ API",
description = "id๊ฐ’์— ํ•ด๋‹นํ•˜๋Š” ์ด๋ฆ„์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค."
)
@ApiResponses({
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "์„ฑ๊ณต"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "์‹คํŒจ")
})
public ResponseEntity<DataResponse<ExampleResponse>> findById(@PathVariable Long id);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.whereyouad.WhereYouAd.global.config;

import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import io.swagger.v3.oas.models.servers.Server;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SwaggerConfig {

@Bean
public OpenAPI swagger() {
Info info = new Info()
.title("Where you AD API Document")
.description("Where you AD(๋„ˆ์˜ ๊ด‘๊ณ ๋Š”) ํ”„๋กœ์ ํŠธ API ๋ช…์„ธ์„œ")
.version("0.0.1");

// JWT ํ† ํฐ ํ—ค๋” ๋ฐฉ์‹
String securityScheme = "JWT TOKEN";
SecurityRequirement securityRequirement = new SecurityRequirement().addList(securityScheme);

Components components = new Components()
.addSecuritySchemes(securityScheme, new SecurityScheme()
.name(securityScheme)
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT"));

return new OpenAPI()
.info(info)
.addServersItem(new Server().url("/"))
.addSecurityItem(securityRequirement)
.components(components);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.whereyouad.WhereYouAd.global.exception;

import lombok.Getter;

@Getter
public class AppException extends RuntimeException {

private final BaseErrorCode errorCode;

public AppException(BaseErrorCode errorCode) {
super(errorCode.getMessage());
this.errorCode = errorCode;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.whereyouad.WhereYouAd.global.exception;

import org.springframework.http.HttpStatus;

public interface BaseErrorCode {
HttpStatus getHttpStatus();
String getCode();
String getMessage();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.whereyouad.WhereYouAd.global.exception;


import lombok.AllArgsConstructor;
import org.springframework.http.HttpStatus;

import lombok.Getter;

@Getter
@AllArgsConstructor
public enum ErrorCode implements BaseErrorCode{

//400
BAD_REQUEST(HttpStatus.BAD_REQUEST, "์ž˜๋ชป๋œ ์š”์ฒญ์ž…๋‹ˆ๋‹ค.", "COMMON-001"),
INVALID_PARAMETER(HttpStatus.BAD_REQUEST, "์š”์ฒญ ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ์ž˜๋ชป๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", "COMMON-002"),
NOT_FOUND(HttpStatus.NOT_FOUND, "์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.", "COMMON-003"),

//500
INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "์„œ๋ฒ„ ๋‚ด๋ถ€์—์„œ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜์˜€์Šต๋‹ˆ๋‹ค.", "COMMON-004"),
;

private final HttpStatus httpStatus;
private final String message;
private final String code;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.whereyouad.WhereYouAd.global.exception;

import com.whereyouad.WhereYouAd.global.response.ErrorResponse;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;


import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;


@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

// ์ฒ˜๋ฆฌ๋˜์ง€ ์•Š์€ ๋ชจ๋“  ์˜ˆ์™ธ๋ฅผ ์žก๋Š” ํ•ธ๋“ค๋Ÿฌ
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleAllException(Exception e, HttpServletRequest request) {
log.error("์ฒ˜๋ฆฌ๋˜์ง€ ์•Š์€ ์˜ˆ์™ธ ๋ฐœ์ƒ: ", e);
log.error("์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ ์ง€์  {}, {}", request.getMethod(), request.getRequestURI());
ErrorResponse errorResponse = ErrorResponse.of(
ErrorCode.INTERNAL_SERVER_ERROR,
request
);
return ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(errorResponse);
}

@ExceptionHandler(AppException.class)
public ResponseEntity<ErrorResponse> handleAppCustomException(AppException e, HttpServletRequest request) {
log.error("AppException ๋ฐœ์ƒ: {}", e.getErrorCode().getMessage());
log.error("์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ ์ง€์  {}, {}", request.getMethod(), request.getRequestURI());
ErrorResponse errorResponse = ErrorResponse.of(e.getErrorCode(), request);
return ResponseEntity
.status(e.getErrorCode().getHttpStatus())
.body(errorResponse);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.whereyouad.WhereYouAd.global.response;

import java.time.LocalDateTime;

import org.springframework.http.HttpStatus;

import com.fasterxml.jackson.annotation.JsonFormat;

import lombok.Getter;

@Getter
public abstract class BaseResponse {

private final String status;

protected BaseResponse(HttpStatus status) {
this.status = status.getReasonPhrase();
}
}
Loading