diff --git a/build.gradle b/build.gradle index 6ba5a327..3b5bdc6f 100644 --- a/build.gradle +++ b/build.gradle @@ -1,138 +1,141 @@ plugins { - id 'java' - id 'org.springframework.boot' version '3.3.1' - id 'io.spring.dependency-management' version '1.1.5' + id 'java' + id 'org.springframework.boot' version '3.3.1' + id 'io.spring.dependency-management' version '1.1.5' } jar.enabled = false bootJar.enabled = false java { - toolchain { - languageVersion = JavaLanguageVersion.of(17) - } + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } } repositories { - mavenCentral() + mavenCentral() } subprojects { - group 'org.layer' - version '0.0.1' - sourceCompatibility = '17' + group 'org.layer' + version '0.0.1' + sourceCompatibility = '17' - apply plugin: 'java' - apply plugin: 'java-library' - apply plugin: 'idea' - apply plugin: 'org.springframework.boot' - apply plugin: 'io.spring.dependency-management' + apply plugin: 'java' + apply plugin: 'java-library' + apply plugin: 'idea' + apply plugin: 'org.springframework.boot' + apply plugin: 'io.spring.dependency-management' - configurations { - compileOnly { - extendsFrom annotationProcessor - } - } + configurations { + compileOnly { + extendsFrom annotationProcessor + } + } - repositories { - mavenCentral() - } + repositories { + mavenCentral() + } - dependencies { // 모든 하위 모듈에 추가 될 의존성 목록입니다. + dependencies { // 모든 하위 모듈에 추가 될 의존성 목록입니다. - compileOnly 'org.projectlombok:lombok' - annotationProcessor 'org.projectlombok:lombok' + compileOnly 'org.projectlombok:lombok' + annotationProcessor 'org.projectlombok:lombok' - testImplementation platform('org.junit:junit-bom:5.9.1') - testImplementation 'org.junit.jupiter:junit-jupiter' + testImplementation platform('org.junit:junit-bom:5.9.1') + testImplementation 'org.junit.jupiter:junit-jupiter' - implementation 'org.springframework.boot:spring-boot-starter' - implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter' + implementation 'org.springframework.boot:spring-boot-starter-web' - } + } - test { - useJUnitPlatform() - } + test { + useJUnitPlatform() + } } project(":layer-api") { - version '0.0.1' - dependencies { - implementation project(path: ':layer-common') - implementation project(path: ':layer-domain') - implementation project(path: ':layer-external') + version '0.0.1' + dependencies { + implementation project(path: ':layer-common') + implementation project(path: ':layer-domain') + implementation project(path: ':layer-external') - implementation 'org.springframework.boot:spring-boot-starter-actuator' - implementation 'org.springframework.boot:spring-boot-starter-validation' + implementation 'org.springframework.boot:spring-boot-starter-actuator' + implementation 'org.springframework.boot:spring-boot-starter-validation' - //== jwt ==// - implementation 'io.jsonwebtoken:jjwt-api:0.12.5' - runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.5' - runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.5' + //== jwt ==// + implementation 'io.jsonwebtoken:jjwt-api:0.12.5' + runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.5' + runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.5' - // oauth2-client 라이브러리 - implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' + // oauth2-client 라이브러리 + implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' - // redis - implementation 'org.springframework.boot:spring-boot-starter-data-redis' - testImplementation 'org.springframework.boot:spring-boot-starter-test' + // redis + implementation 'org.springframework.boot:spring-boot-starter-data-redis' + testImplementation 'org.springframework.boot:spring-boot-starter-test' - // swagger - implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0") + // swagger + implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0") - // jpa - implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + // jpa + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' - // mysql - runtimeOnly 'com.mysql:mysql-connector-j' - } + // mysql + runtimeOnly 'com.mysql:mysql-connector-j' + } - jar.enabled = false - bootJar.enabled = true + jar.enabled = false + bootJar.enabled = true } project(":layer-common") { - bootJar.enabled = false - jar.enabled = true + bootJar.enabled = false + jar.enabled = true - dependencies { - implementation 'org.springframework.boot:spring-boot-starter-web' + dependencies { + // swagger + implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0") + + implementation 'org.springframework.boot:spring-boot-starter-web' // implementation 'org.springframework.boot:spring-boot-starter-data-jpa' - } + } } -project(":layer-domain"){ - bootJar.enabled = false - jar.enabled = true +project(":layer-domain") { + bootJar.enabled = false + jar.enabled = true - dependencies { - implementation project(path: ':layer-common') - implementation 'org.springframework.boot:spring-boot-starter-data-jpa' - implementation 'org.springframework.boot:spring-boot-starter-validation' - implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' + dependencies { + implementation project(path: ':layer-common') + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'org.springframework.boot:spring-boot-starter-validation' + implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' - // h2 - runtimeOnly 'com.h2database:h2' + // h2 + runtimeOnly 'com.h2database:h2' - testImplementation platform('org.junit:junit-bom:5.9.1') - testImplementation 'org.junit.jupiter:junit-jupiter' - } + testImplementation platform('org.junit:junit-bom:5.9.1') + testImplementation 'org.junit.jupiter:junit-jupiter' + } } -project(":layer-external"){ - bootJar.enabled = false - jar.enabled = true +project(":layer-external") { + bootJar.enabled = false + jar.enabled = true dependencies { implementation project(path: ':layer-common') implementation project(path: ':layer-domain') - testImplementation platform('org.junit:junit-bom:5.9.1') - testImplementation 'org.junit.jupiter:junit-jupiter' - } + testImplementation platform('org.junit:junit-bom:5.9.1') + testImplementation 'org.junit.jupiter:junit-jupiter' + } } \ No newline at end of file diff --git a/layer-api/src/main/java/org/layer/config/SecurityConfig.java b/layer-api/src/main/java/org/layer/config/SecurityConfig.java index e9a6c31a..3dd515ad 100644 --- a/layer-api/src/main/java/org/layer/config/SecurityConfig.java +++ b/layer-api/src/main/java/org/layer/config/SecurityConfig.java @@ -1,8 +1,12 @@ package org.layer.config; +import io.swagger.v3.oas.models.Operation; import lombok.RequiredArgsConstructor; +import org.layer.common.annotation.DisableSwaggerSecurity; import org.layer.domain.jwt.JwtAuthenticationFilter; +import org.springdoc.core.customizers.OperationCustomizer; +import org.springframework.boot.autoconfigure.security.ConditionalOnDefaultWebSecurity; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; @@ -12,10 +16,14 @@ import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.web.method.HandlerMethod; + +import java.util.Collections; @RequiredArgsConstructor @Configuration @EnableWebSecurity +@ConditionalOnDefaultWebSecurity public class SecurityConfig { private final JwtAuthenticationFilter jwtAuthenticationFilter; @@ -53,4 +61,15 @@ private void permitSwaggerUri(HttpSecurity http) throws Exception { .requestMatchers(new AntPathRequestMatcher("/swagger-ui/**")).permitAll() .requestMatchers(new AntPathRequestMatcher("/docs/**")).permitAll()); } + + @Bean + public OperationCustomizer customize() { + return (Operation operation, HandlerMethod handlerMethod) -> { + DisableSwaggerSecurity methodAnnotation = handlerMethod.getMethodAnnotation(DisableSwaggerSecurity.class); + if (methodAnnotation != null) { + operation.setSecurity(Collections.emptyList()); + } + return operation; + }; + } } diff --git a/layer-api/src/main/java/org/layer/config/SwaggerConfig.java b/layer-api/src/main/java/org/layer/config/SwaggerConfig.java index ce007325..aefe1b76 100644 --- a/layer-api/src/main/java/org/layer/config/SwaggerConfig.java +++ b/layer-api/src/main/java/org/layer/config/SwaggerConfig.java @@ -18,16 +18,18 @@ @Configuration public class SwaggerConfig { + private static final String AUTH_TOKEN = "Authorization"; + SecurityScheme apiAuth = new SecurityScheme() .type(SecurityScheme.Type.APIKEY) .in(SecurityScheme.In.HEADER) - .name("authorization-token"); + .name(AUTH_TOKEN); SecurityRequirement addSecurityItem = new SecurityRequirement() - .addList("authorization-token"); + .addList(AUTH_TOKEN); @Bean - public OpenAPI openAPI(){ + public OpenAPI openAPI() { var info = new Info(); info.title("Layer API"); info.description("Layer API 문서에요."); @@ -39,7 +41,7 @@ public OpenAPI openAPI(){ info.license(new License().name("MIT")); return new OpenAPI() .components(new Components() - .addSecuritySchemes("authorization-token", apiAuth) + .addSecuritySchemes(AUTH_TOKEN, apiAuth) ) .addSecurityItem(addSecurityItem) .info(info); diff --git a/layer-api/src/main/java/org/layer/domain/auth/controller/AuthController.java b/layer-api/src/main/java/org/layer/domain/auth/controller/AuthController.java index 1a61ed9e..c0d522f5 100644 --- a/layer-api/src/main/java/org/layer/domain/auth/controller/AuthController.java +++ b/layer-api/src/main/java/org/layer/domain/auth/controller/AuthController.java @@ -2,6 +2,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.layer.common.annotation.DisableSwaggerSecurity; import org.layer.domain.auth.controller.dto.*; import org.layer.domain.auth.service.AuthService; import org.layer.domain.auth.service.dto.SignInServiceResponse; @@ -12,9 +13,6 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; @Slf4j @RequiredArgsConstructor @@ -26,16 +24,20 @@ public class AuthController implements AuthApi { private final KakaoService kakaoService; private final MemberRepository memberRepository; + private static final String SOCIAL_TOKEN_NAME = "X-AUTH-TOKEN"; + // 로그인 + @DisableSwaggerSecurity @PostMapping("/sign-in") - public ResponseEntity signIn(@RequestHeader("Authorization") final String socialAccessToken, @RequestBody final SignInRequest signInRequest) { + public ResponseEntity signIn(@RequestHeader(SOCIAL_TOKEN_NAME) final String socialAccessToken, @RequestBody final SignInRequest signInRequest) { SignInServiceResponse signInServiceResponse = authService.signIn(socialAccessToken, signInRequest.socialType()); return new ResponseEntity<>(SignInResponse.of(signInServiceResponse), HttpStatus.OK); } // 회원가입 => 소셜로그인 했는데 유효한 유저가 없을 때 이름 입력하고 회원가입하는 과정 + @DisableSwaggerSecurity @PostMapping("/sign-up") - public ResponseEntity signUp(@RequestHeader("Authorization") final String socialAccessToken, @RequestBody final SignUpRequest signUpRequest) { + public ResponseEntity signUp(@RequestHeader(SOCIAL_TOKEN_NAME) final String socialAccessToken, @RequestBody final SignUpRequest signUpRequest) { SignUpServiceResponse signUpServiceResponse = authService.signUp(socialAccessToken, signUpRequest); return new ResponseEntity<>(SignUpResponse.of(signUpServiceResponse), HttpStatus.CREATED); } @@ -63,12 +65,14 @@ public ResponseEntity reissueToken(ReissueTokenRequest rei HttpStatus.CREATED); } + @DisableSwaggerSecurity //== google OAuth2 test용 API 액세스 토큰 발급 ==// @GetMapping("oauth2/google") public String googleTest(@RequestParam("code") String code) { return googleService.getToken(code); } + @DisableSwaggerSecurity //== kakao OAuth2 test용 API 액세스 토큰 발급 ==// @GetMapping("oauth2/kakao") public Object kakaoLogin(@RequestParam(value = "code", required = false) String code) { diff --git a/layer-api/src/main/java/org/layer/retrospect/controller/RetrospectApi.java b/layer-api/src/main/java/org/layer/domain/retrospect/controller/RetrospectApi.java similarity index 51% rename from layer-api/src/main/java/org/layer/retrospect/controller/RetrospectApi.java rename to layer-api/src/main/java/org/layer/domain/retrospect/controller/RetrospectApi.java index 1e5515ca..2fbc25b4 100644 --- a/layer-api/src/main/java/org/layer/retrospect/controller/RetrospectApi.java +++ b/layer-api/src/main/java/org/layer/domain/retrospect/controller/RetrospectApi.java @@ -1,6 +1,6 @@ -package org.layer.retrospect.controller; +package org.layer.domain.retrospect.controller; -import org.layer.retrospect.controller.dto.request.RetrospectCreateRequest; +import org.layer.domain.retrospect.controller.dto.request.RetrospectCreateRequest; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; @@ -12,7 +12,7 @@ @Tag(name = "회고", description = "회고 관련 API") public interface RetrospectApi { - @Operation(summary = "회고 생성", description = "") - ResponseEntity createRetrospect(@PathVariable("spaceId") Long spaceId, - @RequestBody @Valid RetrospectCreateRequest request); + @Operation(summary = "회고 생성", description = "") + ResponseEntity createRetrospect(@PathVariable("spaceId") Long spaceId, + @RequestBody @Valid RetrospectCreateRequest request); } diff --git a/layer-api/src/main/java/org/layer/domain/retrospect/controller/RetrospectController.java b/layer-api/src/main/java/org/layer/domain/retrospect/controller/RetrospectController.java new file mode 100644 index 00000000..bceab944 --- /dev/null +++ b/layer-api/src/main/java/org/layer/domain/retrospect/controller/RetrospectController.java @@ -0,0 +1,30 @@ +package org.layer.domain.retrospect.controller; + +import org.layer.domain.retrospect.service.RetrospectService; +import org.layer.domain.retrospect.controller.dto.request.RetrospectCreateRequest; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/space/{spaceId}/retrospect") +public class RetrospectController implements RetrospectApi { + + private final RetrospectService retrospectService; + + @Override + @PostMapping + public ResponseEntity createRetrospect(@PathVariable("spaceId") Long spaceId, + @RequestBody @Valid RetrospectCreateRequest request) { + + retrospectService.create(spaceId, request.formId(), request.title(), request.introduction()); + return ResponseEntity.ok(null); + } +} diff --git a/layer-api/src/main/java/org/layer/retrospect/controller/dto/request/RetrospectCreateRequest.java b/layer-api/src/main/java/org/layer/domain/retrospect/controller/dto/request/RetrospectCreateRequest.java similarity index 90% rename from layer-api/src/main/java/org/layer/retrospect/controller/dto/request/RetrospectCreateRequest.java rename to layer-api/src/main/java/org/layer/domain/retrospect/controller/dto/request/RetrospectCreateRequest.java index bdbcd9ff..a0c97524 100644 --- a/layer-api/src/main/java/org/layer/retrospect/controller/dto/request/RetrospectCreateRequest.java +++ b/layer-api/src/main/java/org/layer/domain/retrospect/controller/dto/request/RetrospectCreateRequest.java @@ -1,4 +1,4 @@ -package org.layer.retrospect.controller.dto.request; +package org.layer.domain.retrospect.controller.dto.request; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; diff --git a/layer-api/src/main/java/org/layer/retrospect/service/RetrospectService.java b/layer-api/src/main/java/org/layer/domain/retrospect/service/RetrospectService.java similarity index 94% rename from layer-api/src/main/java/org/layer/retrospect/service/RetrospectService.java rename to layer-api/src/main/java/org/layer/domain/retrospect/service/RetrospectService.java index 2880e2a2..0470774a 100644 --- a/layer-api/src/main/java/org/layer/retrospect/service/RetrospectService.java +++ b/layer-api/src/main/java/org/layer/domain/retrospect/service/RetrospectService.java @@ -1,4 +1,4 @@ -package org.layer.retrospect.service; +package org.layer.domain.retrospect.service; import org.layer.domain.retrospect.entity.Retrospect; import org.layer.domain.retrospect.entity.RetrospectStatus; diff --git a/layer-api/src/main/java/org/layer/retrospect/service/dto/RetrospectCreateServiceRequest.java b/layer-api/src/main/java/org/layer/domain/retrospect/service/dto/RetrospectCreateServiceRequest.java similarity index 75% rename from layer-api/src/main/java/org/layer/retrospect/service/dto/RetrospectCreateServiceRequest.java rename to layer-api/src/main/java/org/layer/domain/retrospect/service/dto/RetrospectCreateServiceRequest.java index a8677ac5..223f8ece 100644 --- a/layer-api/src/main/java/org/layer/retrospect/service/dto/RetrospectCreateServiceRequest.java +++ b/layer-api/src/main/java/org/layer/domain/retrospect/service/dto/RetrospectCreateServiceRequest.java @@ -1,4 +1,4 @@ -package org.layer.retrospect.service.dto; +package org.layer.domain.retrospect.service.dto; import java.util.List; diff --git a/layer-api/src/main/java/org/layer/domain/space/api/SpaceApi.java b/layer-api/src/main/java/org/layer/domain/space/api/SpaceApi.java new file mode 100644 index 00000000..d00ce6aa --- /dev/null +++ b/layer-api/src/main/java/org/layer/domain/space/api/SpaceApi.java @@ -0,0 +1,52 @@ +package org.layer.domain.space.api; + +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.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.layer.common.annotation.MemberId; +import org.layer.domain.space.dto.SpaceRequest; +import org.layer.domain.space.dto.SpaceResponse; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; + +@Tag(name = "스페이스 API") +public interface SpaceApi { + @Operation(summary = "내가 속한 스페이스 조회하기", method = "GET", description = """ + 내가 속한 모든 스페이스를 반환합니다.
+ 접속한 회원이 아닐 경우 조회가 불가능 합니다. + """) + @ApiResponses({ + @ApiResponse(responseCode = "200", + content = { + @Content( + mediaType = "application/json", + schema = @Schema(implementation = SpaceResponse.SpacePage.class) + ) + } + ) + } + ) + ResponseEntity getMySpaceList(@MemberId Long memberId, @RequestParam @Validated SpaceRequest.GetSpaceRequest getSpaceRequest); + + @Operation(summary = "스페이스 생성하기", method = "POST", description = """ + 스페이스를 생성합니다.
+ 생성 성공 시 저장된 스페이스 정보를 반환합니다. + """) + @ApiResponses({ + @ApiResponse(responseCode = "201", + content = { + @Content( + mediaType = "application/json", + schema = @Schema(implementation = Boolean.class) + ) + } + ) + } + ) + ResponseEntity createSpace(@MemberId Long memberId, @RequestBody @Validated SpaceRequest.CreateSpaceRequest createSpaceRequest); +} diff --git a/layer-api/src/main/java/org/layer/domain/space/controller/SpaceController.java b/layer-api/src/main/java/org/layer/domain/space/controller/SpaceController.java new file mode 100644 index 00000000..70f172d6 --- /dev/null +++ b/layer-api/src/main/java/org/layer/domain/space/controller/SpaceController.java @@ -0,0 +1,39 @@ +package org.layer.domain.space.controller; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.layer.common.annotation.MemberId; +import org.layer.domain.space.api.SpaceApi; +import org.layer.domain.space.dto.SpaceRequest; +import org.layer.domain.space.dto.SpaceResponse; +import org.layer.domain.space.service.SpaceService; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + + +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/space") +public class SpaceController implements SpaceApi { + + private final SpaceService spaceService; + + @Override + @GetMapping("/list") + @PreAuthorize("isAuthenticated()") + public ResponseEntity getMySpaceList(@MemberId Long memberId, SpaceRequest.GetSpaceRequest getSpaceRequest) { + var response = spaceService.getSpaceListFromMemberId(memberId, getSpaceRequest); + return ResponseEntity.ok(response); + } + + @Override + @PutMapping("/") + @PreAuthorize("isAuthenticated()") + public ResponseEntity createSpace(@MemberId Long memberId, @RequestBody @Validated SpaceRequest.CreateSpaceRequest createSpaceRequest) { + var response = spaceService.createSpace(memberId, createSpaceRequest); + return ResponseEntity.ok(response); + } +} diff --git a/layer-api/src/main/java/org/layer/domain/space/dto/SpaceRequest.java b/layer-api/src/main/java/org/layer/domain/space/dto/SpaceRequest.java new file mode 100644 index 00000000..00059dc1 --- /dev/null +++ b/layer-api/src/main/java/org/layer/domain/space/dto/SpaceRequest.java @@ -0,0 +1,54 @@ +package org.layer.domain.space.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import org.layer.domain.space.entity.Space; +import org.layer.domain.space.entity.SpaceCategory; +import org.layer.domain.space.entity.SpaceField; + +import java.util.Optional; + +public class SpaceRequest { + + // @Builder + @Schema(description = "스페이스 생성하기") + public record CreateSpaceRequest( + @Schema(description = "프로젝트 유형 카테고리", example = "INDIVIDUAL") + @NotNull + SpaceCategory category, + @Schema(description = "진행중인 프로젝트 유형") + @NotNull + SpaceField field, + @Schema(description = "이름") + @NotNull + String name, + + @Schema(description = "공간 설명") + String introduction + ) { + public Space toEntity(Long memberId) { + return Space.builder() + .category(category) + .field(field) + .name(name) + .introduction(introduction) + .leaderId(memberId) + .build(); + } + } + + @Schema(description = "내가 속한 스페이스 조회") + public record GetSpaceRequest( + @Schema(description = "커서 아이디") + Long cursorId, + + @Schema(description = "조회하고자 하는 스페이스 타입") + Optional category, + + @Schema(description = "페이지 사이즈") + int pageSize + + ) { + + } +} diff --git a/layer-api/src/main/java/org/layer/domain/space/dto/SpaceResponse.java b/layer-api/src/main/java/org/layer/domain/space/dto/SpaceResponse.java new file mode 100644 index 00000000..33f7e603 --- /dev/null +++ b/layer-api/src/main/java/org/layer/domain/space/dto/SpaceResponse.java @@ -0,0 +1,68 @@ +package org.layer.domain.space.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Builder; +import org.layer.common.dto.Meta; +import org.layer.common.exception.BaseCustomException; +import org.layer.domain.space.entity.SpaceCategory; +import org.layer.domain.space.entity.SpaceField; + +import java.util.List; +import java.util.Optional; + +import static org.layer.domain.auth.exception.TokenExceptionType.INVALID_REFRESH_TOKEN; + +public class SpaceResponse { + + + @Builder + @Schema(description = "스페이스 정보 응답") + public record SpaceWithUserCountInfo( + @Schema(description = "스페이스 ID") + @NotNull + Long id, + @Schema(description = "프로젝트 유형 카테고리") + @NotNull + SpaceCategory category, + @Schema(description = "진행중인 프로젝트 유형") + @NotNull + SpaceField field, + + @Schema(description = "이름") + @NotNull + String name, + + @Schema(description = "공간 설명") + String introduction, + + @Schema(description = "설정된 회고 폼 아이디") + Long formId, + + @Schema(description = "소속된 회원 수") + Long userCount + ) { + public static SpaceWithUserCountInfo toResponse(SpaceWithMemberCount space) { + return Optional.ofNullable(space) + .map(it -> SpaceWithUserCountInfo.builder().id(it.getId()).category(it.getCategory()) + .field(it.getField()).name(it.getName()).introduction(it.getIntroduction()) + .formId(it.getFormId()).userCount(it.getUserCount()).build()) + .orElseThrow(() -> new BaseCustomException(INVALID_REFRESH_TOKEN)); + } + } + + @Builder + @Schema() + public record SpacePage( + @Schema() + List data, + + @Schema() + Meta meta + ) { + + public static SpacePage toResponse(List spaceInfo, Meta meta) { + return SpacePage.builder().data(spaceInfo).meta(meta).build(); + } + } +} diff --git a/layer-api/src/main/java/org/layer/domain/space/service/SpaceService.java b/layer-api/src/main/java/org/layer/domain/space/service/SpaceService.java new file mode 100644 index 00000000..848f50dd --- /dev/null +++ b/layer-api/src/main/java/org/layer/domain/space/service/SpaceService.java @@ -0,0 +1,51 @@ +package org.layer.domain.space.service; + +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.layer.common.dto.Meta; +import org.layer.domain.space.dto.SpaceRequest; +import org.layer.domain.space.dto.SpaceResponse; +import org.layer.domain.space.dto.SpaceWithMemberCount; +import org.layer.domain.space.entity.MemberSpaceRelation; +import org.layer.domain.space.entity.Space; +import org.layer.domain.space.repository.MemberSpaceRelationRepository; +import org.layer.domain.space.repository.SpaceRepository; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.stereotype.Service; + +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class SpaceService { + private final SpaceRepository spaceRepository; + private final MemberSpaceRelationRepository memberSpaceRelationRepository; + + public SpaceResponse.SpacePage getSpaceListFromMemberId(Long memberId, SpaceRequest.GetSpaceRequest getSpaceRequest) { + PageRequest pageRequest = PageRequest.of(0, getSpaceRequest.pageSize()); + Page spacePages; + if (getSpaceRequest.category().isPresent()) { + spacePages = spaceRepository.findAllSpacesByMemberIdAndCategoryAndCursor(memberId, getSpaceRequest.cursorId(), getSpaceRequest.category().get(), pageRequest); + } else { + spacePages = spaceRepository.findAllSpacesByMemberIdAndCursor(memberId, getSpaceRequest.cursorId(), pageRequest); + } + + var spaceList = spacePages.stream().map(SpaceResponse.SpaceWithUserCountInfo::toResponse).collect(Collectors.toList()); + boolean hasNextPage = spacePages.hasNext(); + Long newCursor = hasNextPage ? spacePages.getContent().get(spacePages.getNumberOfElements() - 1).getId() : null; + + var meta = Meta.builder().cursor(newCursor).hasNextPage(hasNextPage).build(); + return SpaceResponse.SpacePage.toResponse(spaceList, meta); + } + + @Transactional + public Boolean createSpace(Long memberId, SpaceRequest.CreateSpaceRequest createSpaceRequest) { + Space newSpace = createSpaceRequest.toEntity(memberId); + var memberSpaceRelation = MemberSpaceRelation.builder().memberId(memberId).spaceId(newSpace.getId()).build(); + spaceRepository.save(newSpace); + memberSpaceRelationRepository.save(memberSpaceRelation); + return true; + } + +} diff --git a/layer-api/src/main/java/org/layer/resolver/MemberIdResolver.java b/layer-api/src/main/java/org/layer/resolver/MemberIdResolver.java index 10fb2b3c..47775847 100644 --- a/layer-api/src/main/java/org/layer/resolver/MemberIdResolver.java +++ b/layer-api/src/main/java/org/layer/resolver/MemberIdResolver.java @@ -33,12 +33,11 @@ public boolean supportsParameter(MethodParameter parameter) { @Override public Long resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { - try{ + try { var securityContext = SecurityContextHolder.getContext(); var memberId = Optional.ofNullable(securityContext).map(it -> it.getAuthentication().getName()).orElseThrow(() -> new BaseCustomException(UNAUTHORIZED_USER)); - return Long.parseLong(memberId); - } catch(Exception e) { + } catch (Exception e) { throw new BaseCustomException(UNAUTHORIZED_USER); } } diff --git a/layer-api/src/main/java/org/layer/retrospect/controller/RetrospectController.java b/layer-api/src/main/java/org/layer/retrospect/controller/RetrospectController.java deleted file mode 100644 index b5378da8..00000000 --- a/layer-api/src/main/java/org/layer/retrospect/controller/RetrospectController.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.layer.retrospect.controller; - -import org.layer.retrospect.controller.dto.request.RetrospectCreateRequest; -import org.layer.retrospect.service.RetrospectService; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; - -@RestController -@RequiredArgsConstructor -@RequestMapping("/space/{spaceId}/retrospect") -public class RetrospectController implements RetrospectApi { - - private final RetrospectService retrospectService; - - @Override - @PostMapping - public ResponseEntity createRetrospect(@PathVariable("spaceId") Long spaceId, - @RequestBody @Valid RetrospectCreateRequest request) { - - retrospectService.create(spaceId, request.formId(), request.title(), request.introduction()); - return ResponseEntity.ok(null); - } -} diff --git a/layer-common/src/main/java/org/layer/common/annotation/DisableSwaggerSecurity.java b/layer-common/src/main/java/org/layer/common/annotation/DisableSwaggerSecurity.java new file mode 100644 index 00000000..cca67e80 --- /dev/null +++ b/layer-common/src/main/java/org/layer/common/annotation/DisableSwaggerSecurity.java @@ -0,0 +1,11 @@ +package org.layer.common.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface DisableSwaggerSecurity { +} diff --git a/layer-api/src/main/java/org/layer/common/annotation/MemberId.java b/layer-common/src/main/java/org/layer/common/annotation/MemberId.java similarity index 100% rename from layer-api/src/main/java/org/layer/common/annotation/MemberId.java rename to layer-common/src/main/java/org/layer/common/annotation/MemberId.java diff --git a/layer-common/src/main/java/org/layer/common/dto/Meta.java b/layer-common/src/main/java/org/layer/common/dto/Meta.java new file mode 100644 index 00000000..b9c9d907 --- /dev/null +++ b/layer-common/src/main/java/org/layer/common/dto/Meta.java @@ -0,0 +1,17 @@ +package org.layer.common.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Builder; +import lombok.Data; + +@Builder +@Data +public class Meta { + @Schema() + @NotNull + private boolean hasNextPage; + + @Schema(nullable = true) + private Long cursor; +} diff --git a/layer-api/src/main/java/org/layer/common/exception/ExceptionResponse.java b/layer-common/src/main/java/org/layer/common/exception/ExceptionResponse.java similarity index 100% rename from layer-api/src/main/java/org/layer/common/exception/ExceptionResponse.java rename to layer-common/src/main/java/org/layer/common/exception/ExceptionResponse.java diff --git a/layer-api/src/main/java/org/layer/common/exception/GlobalExceptionHandler.java b/layer-common/src/main/java/org/layer/common/exception/GlobalExceptionHandler.java similarity index 100% rename from layer-api/src/main/java/org/layer/common/exception/GlobalExceptionHandler.java rename to layer-common/src/main/java/org/layer/common/exception/GlobalExceptionHandler.java diff --git a/layer-domain/src/main/java/org/layer/domain/BaseEntity.java b/layer-domain/src/main/java/org/layer/domain/BaseEntity.java index 8eb74dba..6f4ca29a 100644 --- a/layer-domain/src/main/java/org/layer/domain/BaseEntity.java +++ b/layer-domain/src/main/java/org/layer/domain/BaseEntity.java @@ -5,11 +5,14 @@ import jakarta.persistence.Id; import jakarta.persistence.MappedSuperclass; import lombok.AccessLevel; +import lombok.Getter; import lombok.NoArgsConstructor; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.LastModifiedDate; + import java.time.LocalDateTime; +@Getter @MappedSuperclass @NoArgsConstructor(access = AccessLevel.PROTECTED) public class BaseEntity { diff --git a/layer-domain/src/main/java/org/layer/domain/block/converter/BlockStyleConverter.java b/layer-domain/src/main/java/org/layer/domain/block/converter/BlockStyleConverter.java deleted file mode 100644 index 0607ba2a..00000000 --- a/layer-domain/src/main/java/org/layer/domain/block/converter/BlockStyleConverter.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.layer.domain.block.converter; - -import jakarta.persistence.AttributeConverter; -import jakarta.persistence.Converter; -import org.layer.domain.block.enums.BlockType; - - -import java.util.stream.Stream; - -@Converter -public class BlockStyleConverter implements AttributeConverter { - - @Override - public String convertToDatabaseColumn(BlockType blockType) { - return blockType.getStyle(); - } - - @Override - public BlockType convertToEntityAttribute(String blockStyle) { - if(blockStyle == null){ - return null; - } - return Stream.of(BlockType.values()).filter(t -> t.getStyle().equals(blockStyle)).findFirst() - .orElseThrow(IllegalArgumentException::new); - } -} diff --git a/layer-domain/src/main/java/org/layer/domain/block/entity/Block.java b/layer-domain/src/main/java/org/layer/domain/block/entity/Block.java deleted file mode 100644 index cfaf4317..00000000 --- a/layer-domain/src/main/java/org/layer/domain/block/entity/Block.java +++ /dev/null @@ -1,36 +0,0 @@ -package org.layer.domain.block.entity; - -import jakarta.persistence.*; -import jakarta.validation.constraints.NotNull; -import lombok.*; -import org.layer.domain.BaseEntity; -import org.layer.domain.block.converter.BlockStyleConverter; -import org.layer.domain.block.enums.BlockType; -import org.layer.domain.blockOption.entity.BlockOption; - -import java.util.HashSet; -import java.util.Set; - -@Getter -@Entity -@EqualsAndHashCode(callSuper = true) -@AllArgsConstructor -@NoArgsConstructor(access = AccessLevel.PROTECTED) -public class Block extends BaseEntity { - - /** - * Form RelationId - */ - @NotNull - private Long formId; - - private String label; - - @Column(length = 20) - @NotNull - @Convert(converter = BlockStyleConverter.class) - private BlockType style; - - @OneToMany(fetch = FetchType.LAZY, mappedBy = "block", cascade = CascadeType.ALL, orphanRemoval = true) - private Set options = new HashSet<>(); -} diff --git a/layer-domain/src/main/java/org/layer/domain/block/enums/BlockType.java b/layer-domain/src/main/java/org/layer/domain/block/enums/BlockType.java deleted file mode 100644 index 5b8e0c74..00000000 --- a/layer-domain/src/main/java/org/layer/domain/block/enums/BlockType.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.layer.domain.block.enums; - -import lombok.AllArgsConstructor; -import lombok.Getter; - -@AllArgsConstructor -@Getter -public enum BlockType { - /** - * 질문(입력) 블록 종류 - * 1. 짧은 인풋 - * 2. 마크다운 - * 3. 레인저 - * 4. 콤보 박스 - * 5. 카드 - * 6. 숫자 인풋 - * - * 질문 블록 중 이산적인 블록 종류 - * 1. 레인저( 숫자 ) - * 2. 콤보 박스 - * 3. 카드 - * - */ - - PLAIN_TEXT("짧은 입력","plain_text","single"), - MARKDOWN("마크다운 입력","markdown","single"), - RANGER("범위 지정","range","multi"), - COMBOBOX("콤보 박스","combobox","multi"), - CARD("카드 선택 입력","card","multi"), - NUMBER("숫자 입력","number","single"); - - private String description; - private String style; - private String type; -} diff --git a/layer-domain/src/main/java/org/layer/domain/question/converter/QuestionTypeConverter.java b/layer-domain/src/main/java/org/layer/domain/question/converter/QuestionTypeConverter.java new file mode 100644 index 00000000..4a02e86b --- /dev/null +++ b/layer-domain/src/main/java/org/layer/domain/question/converter/QuestionTypeConverter.java @@ -0,0 +1,25 @@ +package org.layer.domain.question.converter; + +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Converter; +import org.layer.domain.question.entity.QuestionType; + +import java.util.stream.Stream; + +@Converter +public class QuestionTypeConverter implements AttributeConverter { + + @Override + public String convertToDatabaseColumn(QuestionType blockType) { + return blockType.getStyle(); + } + + @Override + public QuestionType convertToEntityAttribute(String questionType) { + if (questionType == null) { + return null; + } + return Stream.of(QuestionType.values()).filter(t -> t.getStyle().equals(questionType)).findFirst() + .orElseThrow(IllegalArgumentException::new); + } +} diff --git a/layer-domain/src/main/java/org/layer/domain/question/entity/Question.java b/layer-domain/src/main/java/org/layer/domain/question/entity/Question.java index 5522210b..0659a728 100644 --- a/layer-domain/src/main/java/org/layer/domain/question/entity/Question.java +++ b/layer-domain/src/main/java/org/layer/domain/question/entity/Question.java @@ -1,21 +1,21 @@ package org.layer.domain.question.entity; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; +import jakarta.persistence.*; import jakarta.validation.constraints.NotNull; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; +import org.layer.domain.BaseEntity; +import org.layer.domain.question.converter.QuestionTypeConverter; +import org.layer.domain.questionOption.entity.QuestionOption; + +import java.util.HashSet; +import java.util.Set; @Getter @Entity @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class Question { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; +public class Question extends BaseEntity { @NotNull private Long formId; @@ -23,6 +23,12 @@ public class Question { @NotNull private String content; + @Column(length = 20) @NotNull + @Convert(converter = QuestionTypeConverter.class) private QuestionType questionType; + + @OneToMany(fetch = FetchType.LAZY, mappedBy = "question", cascade = CascadeType.ALL, orphanRemoval = true) + private Set options = new HashSet<>(); + } diff --git a/layer-domain/src/main/java/org/layer/domain/question/entity/QuestionType.java b/layer-domain/src/main/java/org/layer/domain/question/entity/QuestionType.java index 8e468827..811b9404 100644 --- a/layer-domain/src/main/java/org/layer/domain/question/entity/QuestionType.java +++ b/layer-domain/src/main/java/org/layer/domain/question/entity/QuestionType.java @@ -1,5 +1,33 @@ package org.layer.domain.question.entity; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter public enum QuestionType { - PLAIN_TEXT, NUMBER + /** + * 질문(입력) 블록 종류 + * 1. 짧은 인풋 + * 2. 마크다운 + * 3. 레인저 + * 4. 콤보 박스 + * 5. 카드 + * 6. 숫자 인풋 + *

+ * 질문 블록 중 이산적인 블록 종류 + * 1. 레인저( 숫자 ) + * 2. 콤보 박스 + * 3. 카드 + */ + PLAIN_TEXT("짧은 입력", "plain_text", "single"), + MARKDOWN("마크다운 입력", "markdown", "single"), + RANGER("범위 지정", "range", "multi"), + COMBOBOX("콤보 박스", "combobox", "multi"), + CARD("카드 선택 입력", "card", "multi"), + NUMBER("숫자 입력", "number", "single"); + + private String description; + private String style; + private String type; } diff --git a/layer-domain/src/main/java/org/layer/domain/blockOption/entity/BlockOption.java b/layer-domain/src/main/java/org/layer/domain/questionOption/entity/QuestionOption.java similarity index 56% rename from layer-domain/src/main/java/org/layer/domain/blockOption/entity/BlockOption.java rename to layer-domain/src/main/java/org/layer/domain/questionOption/entity/QuestionOption.java index d8291454..428dc26e 100644 --- a/layer-domain/src/main/java/org/layer/domain/blockOption/entity/BlockOption.java +++ b/layer-domain/src/main/java/org/layer/domain/questionOption/entity/QuestionOption.java @@ -1,4 +1,4 @@ -package org.layer.domain.blockOption.entity; +package org.layer.domain.questionOption.entity; import jakarta.persistence.*; import jakarta.validation.constraints.NotNull; @@ -7,16 +7,16 @@ import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import org.layer.domain.BaseEntity; -import org.layer.domain.block.entity.Block; +import org.layer.domain.question.entity.Question; @Entity -@Table(name= "block_option", uniqueConstraints = { - @UniqueConstraint(columnNames = {"block_id", "value"}) +@Table(name = "question_option", uniqueConstraints = { + @UniqueConstraint(columnNames = {"question_id", "value"}) }) @EqualsAndHashCode(callSuper = true) @AllArgsConstructor @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class BlockOption extends BaseEntity { +public class QuestionOption extends BaseEntity { private String label; @@ -24,6 +24,6 @@ public class BlockOption extends BaseEntity { private String value; @ManyToOne - @JoinColumn( name = "block_id") - private Block block; + @JoinColumn(name = "question_id") + private Question question; } \ No newline at end of file diff --git a/layer-domain/src/main/java/org/layer/domain/space/dto/SpaceWithMemberCount.java b/layer-domain/src/main/java/org/layer/domain/space/dto/SpaceWithMemberCount.java new file mode 100644 index 00000000..2b7c497d --- /dev/null +++ b/layer-domain/src/main/java/org/layer/domain/space/dto/SpaceWithMemberCount.java @@ -0,0 +1,45 @@ +package org.layer.domain.space.dto; + +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; +import org.layer.domain.space.entity.SpaceCategory; +import org.layer.domain.space.entity.SpaceField; + +import java.time.LocalDateTime; + +@Getter +@Setter +@AllArgsConstructor +public class SpaceWithMemberCount { + + private Long id; + + + private LocalDateTime createdAt; + + private LocalDateTime updatedAt; + + @NotNull + private SpaceCategory category; + + @NotNull + private SpaceField field; + + @NotNull + private String name; + + private String introduction; + + @NotNull + private Long leaderId; + + /** + * Form Relationid + */ + private Long formId; + + private Long userCount; + +} diff --git a/layer-domain/src/main/java/org/layer/domain/space/entity/MemberSpaceRelation.java b/layer-domain/src/main/java/org/layer/domain/space/entity/MemberSpaceRelation.java index 7e7dbc35..fa2a351a 100644 --- a/layer-domain/src/main/java/org/layer/domain/space/entity/MemberSpaceRelation.java +++ b/layer-domain/src/main/java/org/layer/domain/space/entity/MemberSpaceRelation.java @@ -1,22 +1,16 @@ package org.layer.domain.space.entity; import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; import jakarta.validation.constraints.NotNull; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; +import lombok.*; +import org.layer.domain.BaseEntity; @Getter @Entity +@Builder +@AllArgsConstructor @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class MemberSpaceRelation { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - +public class MemberSpaceRelation extends BaseEntity { @NotNull private Long memberId; diff --git a/layer-domain/src/main/java/org/layer/domain/space/entity/Space.java b/layer-domain/src/main/java/org/layer/domain/space/entity/Space.java index 3c10bd58..e7ae0c01 100644 --- a/layer-domain/src/main/java/org/layer/domain/space/entity/Space.java +++ b/layer-domain/src/main/java/org/layer/domain/space/entity/Space.java @@ -1,27 +1,23 @@ package org.layer.domain.space.entity; import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; import jakarta.validation.constraints.NotNull; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; +import lombok.*; +import org.layer.domain.BaseEntity; @Getter @Entity +@AllArgsConstructor +@Builder +@EqualsAndHashCode(callSuper = true) @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class Space { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; +public class Space extends BaseEntity { @NotNull private SpaceCategory category; @NotNull - private Field field; + private SpaceField field; @NotNull private String name; diff --git a/layer-domain/src/main/java/org/layer/domain/space/entity/Field.java b/layer-domain/src/main/java/org/layer/domain/space/entity/SpaceField.java similarity index 82% rename from layer-domain/src/main/java/org/layer/domain/space/entity/Field.java rename to layer-domain/src/main/java/org/layer/domain/space/entity/SpaceField.java index e1ed72ba..0c8233e9 100644 --- a/layer-domain/src/main/java/org/layer/domain/space/entity/Field.java +++ b/layer-domain/src/main/java/org/layer/domain/space/entity/SpaceField.java @@ -1,6 +1,6 @@ package org.layer.domain.space.entity; -public enum Field { +public enum SpaceField { DESIGN, DEVELOPMENT, MARKETING, diff --git a/layer-domain/src/main/java/org/layer/domain/space/repository/MemberSpaceRelationRepository.java b/layer-domain/src/main/java/org/layer/domain/space/repository/MemberSpaceRelationRepository.java new file mode 100644 index 00000000..912f7307 --- /dev/null +++ b/layer-domain/src/main/java/org/layer/domain/space/repository/MemberSpaceRelationRepository.java @@ -0,0 +1,7 @@ +package org.layer.domain.space.repository; + +import org.layer.domain.space.entity.MemberSpaceRelation; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface MemberSpaceRelationRepository extends JpaRepository { +} diff --git a/layer-domain/src/main/java/org/layer/domain/space/repository/SpaceRepository.java b/layer-domain/src/main/java/org/layer/domain/space/repository/SpaceRepository.java index 895865fe..e9cc4e29 100644 --- a/layer-domain/src/main/java/org/layer/domain/space/repository/SpaceRepository.java +++ b/layer-domain/src/main/java/org/layer/domain/space/repository/SpaceRepository.java @@ -1,8 +1,18 @@ package org.layer.domain.space.repository; +import org.layer.domain.space.dto.SpaceWithMemberCount; import org.layer.domain.space.entity.Space; +import org.layer.domain.space.entity.SpaceCategory; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + public interface SpaceRepository extends JpaRepository { + @Query("SELECT s,COUNT(msr.id) FROM Space s JOIN MemberSpaceRelation msr ON s.id = msr.spaceId WHERE msr.memberId = :memberId AND (:cursorId IS NULL OR msr.id > :cursorId) AND s.category = :category GROUP BY s.id ORDER BY msr.id ASC") + Page findAllSpacesByMemberIdAndCategoryAndCursor(Long memberId, Long cursorId, SpaceCategory category, Pageable pageable); + @Query("SELECT s,COUNT(msr.id) FROM Space s JOIN MemberSpaceRelation msr ON s.id = msr.spaceId WHERE msr.memberId = :memberId AND (:cursorId IS NULL OR msr.id > :cursorId) GROUP BY s.id ORDER BY msr.id ASC") + Page findAllSpacesByMemberIdAndCursor(Long memberId, Long cursorId, Pageable pageable); }