diff --git a/build.gradle b/build.gradle index 0c2a1a94..9ed4fb25 100644 --- a/build.gradle +++ b/build.gradle @@ -52,6 +52,8 @@ dependencies { implementation("software.amazon.awssdk:bom:2.21.0") implementation("software.amazon.awssdk:s3:2.21.0") + // swagger + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2' } tasks.named('test') { diff --git a/src/main/java/com/tiki/server/auth/config/SecurityConfig.java b/src/main/java/com/tiki/server/auth/config/SecurityConfig.java index 43173b47..00e720c5 100644 --- a/src/main/java/com/tiki/server/auth/config/SecurityConfig.java +++ b/src/main/java/com/tiki/server/auth/config/SecurityConfig.java @@ -16,6 +16,7 @@ import org.springframework.security.web.DefaultSecurityFilterChain; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; @Configuration @EnableWebSecurity @@ -29,6 +30,7 @@ public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + permitSwaggerUri(http); return http .csrf(AbstractHttpConfigurer::disable) .formLogin(AbstractHttpConfigurer::disable) @@ -56,4 +58,11 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { ) .build(); } + + private void permitSwaggerUri(HttpSecurity http) throws Exception { + http.authorizeHttpRequests(authorizeHttpRequests -> authorizeHttpRequests + .requestMatchers(new AntPathRequestMatcher("/v3/api-docs/**")).permitAll() + .requestMatchers(new AntPathRequestMatcher("/swagger-ui/**")).permitAll() + .requestMatchers(new AntPathRequestMatcher("/docs/**")).permitAll()); + } } diff --git a/src/main/java/com/tiki/server/common/config/SwaggerConfig.java b/src/main/java/com/tiki/server/common/config/SwaggerConfig.java new file mode 100644 index 00000000..40804cbe --- /dev/null +++ b/src/main/java/com/tiki/server/common/config/SwaggerConfig.java @@ -0,0 +1,43 @@ +package com.tiki.server.common.config; + +import static io.swagger.v3.oas.models.security.SecurityScheme.Type.HTTP; + +import java.util.List; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +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 lombok.val; + +@Configuration +public class SwaggerConfig { + + @Bean + public OpenAPI openApi() { + val securityScheme = new SecurityScheme(); + securityScheme.setType(HTTP); + securityScheme.setScheme("bearer"); + securityScheme.setBearerFormat("JWT"); + + val components = new Components(); + components.addSecuritySchemes("BearerAuthentication", securityScheme); + + val securityRequirement = new SecurityRequirement(); + securityRequirement.addList("BearerAuthentication"); + + val info = new Info(); + info.setTitle("TIKI API Document"); + info.setDescription("티키 API 명세서"); + info.setVersion("1.0.0"); + + return new OpenAPI() + .components(components) + .security(List.of(securityRequirement)) + .info(info); + } +} diff --git a/src/main/java/com/tiki/server/external/controller/S3Controller.java b/src/main/java/com/tiki/server/external/controller/S3Controller.java index 993651d5..48f1f12c 100644 --- a/src/main/java/com/tiki/server/external/controller/S3Controller.java +++ b/src/main/java/com/tiki/server/external/controller/S3Controller.java @@ -10,6 +10,7 @@ import org.springframework.web.bind.annotation.RestController; import com.tiki.server.common.dto.SuccessResponse; +import com.tiki.server.external.controller.docs.S3ControllerDocs; import com.tiki.server.external.dto.request.PreSignedUrlRequest; import com.tiki.server.external.dto.response.PreSignedUrlResponse; import com.tiki.server.external.util.S3Service; @@ -20,10 +21,11 @@ @RestController @RequestMapping("api/v1/file") @RequiredArgsConstructor -public class S3Controller { +public class S3Controller implements S3ControllerDocs { private final S3Service s3Service; + @Override @GetMapping("/upload") public ResponseEntity> getPreSignedUrl(@RequestBody PreSignedUrlRequest request) { val response = s3Service.getUploadPreSignedUrl(request); diff --git a/src/main/java/com/tiki/server/external/controller/docs/S3ControllerDocs.java b/src/main/java/com/tiki/server/external/controller/docs/S3ControllerDocs.java new file mode 100644 index 00000000..3c5e2426 --- /dev/null +++ b/src/main/java/com/tiki/server/external/controller/docs/S3ControllerDocs.java @@ -0,0 +1,40 @@ +package com.tiki.server.external.controller.docs; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestBody; + +import com.tiki.server.common.dto.ErrorResponse; +import com.tiki.server.common.dto.SuccessResponse; +import com.tiki.server.external.dto.request.PreSignedUrlRequest; +import com.tiki.server.external.dto.response.PreSignedUrlResponse; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; + +@Tag(name = "S3", description = "AWS S3 API") +public interface S3ControllerDocs { + + @Operation( + summary = "Presigned Url 생성", + description = "s3로부터 Presigned Url을 생성한다.", + responses = { + @ApiResponse( + responseCode = "200", + description = "성공", + content = @Content(schema = @Schema(implementation = SuccessResponse.class))), + @ApiResponse( + responseCode = "4xx", + description = "클라이언트(요청) 오류", + content = @Content(schema = @Schema(implementation = ErrorResponse.class))), + @ApiResponse( + responseCode = "500", + description = "S3 PRESIGNED URL 불러오기 실패", + content = @Content(schema = @Schema(implementation = ErrorResponse.class)))} + ) + ResponseEntity> getPreSignedUrl( + @RequestBody PreSignedUrlRequest request + ); +} diff --git a/src/main/java/com/tiki/server/member/adapter/MemberFinder.java b/src/main/java/com/tiki/server/member/adapter/MemberFinder.java index 70e06408..6315e188 100644 --- a/src/main/java/com/tiki/server/member/adapter/MemberFinder.java +++ b/src/main/java/com/tiki/server/member/adapter/MemberFinder.java @@ -1,14 +1,11 @@ package com.tiki.server.member.adapter; import static com.tiki.server.member.message.ErrorCode.INVALID_MEMBER; -import static com.tiki.server.team.message.ErrorCode.INVALID_TEAM; import com.tiki.server.common.support.RepositoryAdapter; import com.tiki.server.member.entity.Member; import com.tiki.server.member.exception.MemberException; import com.tiki.server.member.repository.MemberRepository; -import com.tiki.server.team.entity.Team; -import com.tiki.server.team.exception.TeamException; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/tiki/server/timeblock/controller/TimeBlockController.java b/src/main/java/com/tiki/server/timeblock/controller/TimeBlockController.java index fb7ed866..087281d9 100644 --- a/src/main/java/com/tiki/server/timeblock/controller/TimeBlockController.java +++ b/src/main/java/com/tiki/server/timeblock/controller/TimeBlockController.java @@ -15,6 +15,7 @@ import com.tiki.server.common.dto.SuccessResponse; import com.tiki.server.common.support.UriGenerator; +import com.tiki.server.timeblock.controller.docs.TimeBlockControllerDocs; import com.tiki.server.timeblock.dto.request.TimeBlockCreationRequest; import com.tiki.server.timeblock.dto.response.TimeBlockCreationResponse; import com.tiki.server.timeblock.service.TimeBlockService; @@ -25,14 +26,15 @@ @RestController @RequiredArgsConstructor @RequestMapping("api/v1/time-blocks") -public class TimeBlockController { +public class TimeBlockController implements TimeBlockControllerDocs { private final TimeBlockService timeBlockService; + @Override @PostMapping("/team/{teamId}/time-block") public ResponseEntity> createTimeBlock( Principal principal, - @PathVariable("teamId") long teamId, + @PathVariable long teamId, @RequestParam String type, @RequestBody TimeBlockCreationRequest request ) { diff --git a/src/main/java/com/tiki/server/timeblock/controller/docs/TimeBlockControllerDocs.java b/src/main/java/com/tiki/server/timeblock/controller/docs/TimeBlockControllerDocs.java new file mode 100644 index 00000000..9b9c06ce --- /dev/null +++ b/src/main/java/com/tiki/server/timeblock/controller/docs/TimeBlockControllerDocs.java @@ -0,0 +1,76 @@ +package com.tiki.server.timeblock.controller.docs; + +import java.security.Principal; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; + +import com.tiki.server.common.dto.ErrorResponse; +import com.tiki.server.common.dto.SuccessResponse; +import com.tiki.server.timeblock.dto.request.TimeBlockCreationRequest; +import com.tiki.server.timeblock.dto.response.TimeBlockCreationResponse; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; + +@Tag(name = "time-blocks", description = "타임 블록 API") +public interface TimeBlockControllerDocs { + + @Operation( + summary = "타임 블록 생성", + description = "타임 블록을 생성한다.", + responses = { + @ApiResponse( + responseCode = "201", + description = "성공", + content = @Content(schema = @Schema(implementation = SuccessResponse.class))), + @ApiResponse( + responseCode = "400", + description = "타입 오류", + content = @Content(schema = @Schema(implementation = ErrorResponse.class))), + @ApiResponse( + responseCode = "403", + description = "접근 권한 없음", + content = @Content(schema = @Schema(implementation = ErrorResponse.class))), + @ApiResponse( + responseCode = "404", + description = "팀에 존재하지 않는 회원", + content = @Content(schema = @Schema(implementation = ErrorResponse.class))), + @ApiResponse( + responseCode = "404", + description = "유효하지 않은 팀", + content = @Content(schema = @Schema(implementation = ErrorResponse.class))), + @ApiResponse( + responseCode = "4xx", + description = "클라이언트(요청) 오류", + content = @Content(schema = @Schema(implementation = ErrorResponse.class))), + @ApiResponse( + responseCode = "500", + description = "서버 내부 오류", + content = @Content(schema = @Schema(implementation = ErrorResponse.class)))} + ) + ResponseEntity> createTimeBlock( + @Parameter(hidden = true) Principal principal, + @Parameter( + name = "teamId", + description = "팀 id", + in = ParameterIn.PATH, + example = "1" + ) + @PathVariable long teamId, + @Parameter( + name = "type", + description = "타임라인 타입", + in = ParameterIn.QUERY, + example = "executive, member" + ) @RequestParam String type, + @RequestBody TimeBlockCreationRequest request + ); +}