Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 팝업스토어 및 태그 구현 #13

Merged
merged 14 commits into from
Aug 14, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
10 changes: 5 additions & 5 deletions backend/pcloud-api/docs/asciidoc/auth.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@

=== Request

include::{snippets}/auth-controller-test/oauth_login/request-headers.adoc[]
include::{snippets}/auth-controller-test/oauth_login/path-parameters.adoc[]
include::{snippets}/auth-controller-test/oauth_login/http-request.adoc[]
include::{snippets}/auth-controller-web-mvc-test/oauth_login/request-headers.adoc[]
include::{snippets}/auth-controller-web-mvc-test/oauth_login/path-parameters.adoc[]
include::{snippets}/auth-controller-web-mvc-test/oauth_login/http-request.adoc[]

=== Response

include::{snippets}/auth-controller-test/oauth_login/response-fields.adoc[]
include::{snippets}/auth-controller-test/oauth_login/http-response.adoc[]
include::{snippets}/auth-controller-web-mvc-test/oauth_login/response-fields.adoc[]
include::{snippets}/auth-controller-web-mvc-test/oauth_login/http-response.adoc[]
78 changes: 78 additions & 0 deletions backend/pcloud-api/docs/asciidoc/popups.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
= Auth API 문서
:doctype: book
:icons: font
:source-highlighter: highlightjs
:toc: left
:toclevels: 3

== Public Tag 정보 (24.08.12 업데이트)

++ 서버측에서 조회 가능하나 캐싱이 성능적으로 나아서 정합성과 트레이드 오프했다고 봐주시면 됩니다.
만약 캐싱이 불필요하다 생각하면 회의 요청주세요 :)

- 브랜드
- 패션
- 뷰티
- 음식
- 홈
- 완구류
- 레저
- 서적
- 음악
- 펫
- 운동
- 디지털
- 예술
- 캐릭터
- 굿즈
- 전시
- 기타

== 팝업스토어를 생성한다 (POST /api/popups)

=== Request

include::{snippets}/popups-controller-web-mvc-test/create_popups/request-headers.adoc[]
include::{snippets}/popups-controller-web-mvc-test/create_popups/request-fields.adoc[]
include::{snippets}/popups-controller-web-mvc-test/create_popups/http-request.adoc[]

=== Response

include::{snippets}/popups-controller-web-mvc-test/create_popups/response-headers.adoc[]
include::{snippets}/popups-controller-web-mvc-test/create_popups/http-response.adoc[]

== 팝업스토어를 페이징 조회한다 (GET /api/popups?popupsId=${value}&pageSize=${value})

=== Request

include::{snippets}/popups-controller-web-mvc-test/find_all_popups_with_paging/query-parameters.adoc[]
include::{snippets}/popups-controller-web-mvc-test/find_all_popups_with_paging/http-request.adoc[]

=== Response

include::{snippets}/popups-controller-web-mvc-test/find_all_popups_with_paging/response-fields.adoc[]
include::{snippets}/popups-controller-web-mvc-test/find_all_popups_with_paging/http-response.adoc[]

== 팝업스토어를 상세 조회한다 (GET /api/popups/{popupsId})

=== Request

include::{snippets}/popups-controller-web-mvc-test/find_popups/path-parameters.adoc[]
include::{snippets}/popups-controller-web-mvc-test/find_popups/http-request.adoc[]

=== Response

include::{snippets}/popups-controller-web-mvc-test/find_popups/response-fields.adoc[]
include::{snippets}/popups-controller-web-mvc-test/find_popups/http-response.adoc[]

== 팝업스토어를 업데이트한다 (PATCH /api/popups/{popupsId})

=== Request

include::{snippets}/popups-controller-web-mvc-test/patch_popups/request-headers.adoc[]
include::{snippets}/popups-controller-web-mvc-test/patch_popups/request-fields.adoc[]
include::{snippets}/popups-controller-web-mvc-test/patch_popups/http-request.adoc[]

=== Response

include::{snippets}/popups-controller-web-mvc-test/patch_popups/http-response.adoc[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.api.customtag.infrastructure;

import com.domain.domains.common.CustomTagType;
import com.domain.domains.customtag.domain.CustomTag;
import com.domain.domains.customtag.domain.CustomTagRepository;
import com.domain.domains.customtag.infrastructure.CustomTagJpaRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Optional;

@RequiredArgsConstructor
@Repository
public class CustomTagRepositoryImpl implements CustomTagRepository {

private final CustomTagJpaRepository customTagJpaRepository;

@Override
public Optional<CustomTag> findByTypeAndTargetId(final CustomTagType type, final Long targetId) {
return customTagJpaRepository.findByTypeAndTargetId(type, targetId);
}

@Override
public List<CustomTag> saveAll(final List<CustomTag> customTags) {
return customTagJpaRepository.saveAll(customTags);
}

@Override
public void deleteAllByTypeAndTargetId(final CustomTagType type, final Long targetId) {
customTagJpaRepository.deleteAllByTypeAndTargetId(type, targetId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.api.customtag.service;

import com.domain.domains.common.CustomTagType;
import com.domain.domains.customtag.domain.CustomTag;
import com.domain.domains.customtag.domain.CustomTagRepository;
import com.domain.domains.popups.event.PopupsTagsCreatedEvents;
import com.domain.domains.popups.event.PopupsTagsUpdatedEvents;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;

import java.util.List;

@RequiredArgsConstructor
@Transactional
@Service
public class TagEventHandler {

private final CustomTagRepository customTagRepository;

@EventListener(PopupsTagsCreatedEvents.class)
public void savePopupsTags(final PopupsTagsCreatedEvents event) {
List<CustomTag> customTags = getPopupsCustomTag(event.tags(), event.type(), event.popupsId());
customTagRepository.saveAll(customTags);
}

private List<CustomTag> getPopupsCustomTag(
final List<String> tagNames,
final CustomTagType type,
final Long targetId
) {
return tagNames.stream()
.map(tagName -> CustomTag.of(tagName, type, targetId))
.toList();
}

@EventListener(PopupsTagsUpdatedEvents.class)
public void updatePopupsTags(final PopupsTagsUpdatedEvents event) {
List<CustomTag> customTags = getPopupsCustomTag(event.tags(), event.type(), event.popupsId());
customTagRepository.deleteAllByTypeAndTargetId(event.type(), event.popupsId());
customTagRepository.saveAll(customTags);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
import com.api.global.config.interceptor.auth.LoginValidCheckerInterceptor;
import com.api.global.config.interceptor.auth.ParseMemberIdFromTokenInterceptor;
import com.api.global.config.interceptor.auth.PathMatcherInterceptor;
import com.api.global.config.resolver.AuthArgumentResolver;
import com.api.global.config.resolver.AuthMemberArgumentResolver;
import com.api.global.config.resolver.AuthMembersArgumentResolver;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
Expand All @@ -14,7 +15,6 @@
import java.util.List;

import static com.api.global.config.interceptor.auth.support.HttpMethod.DELETE;
import static com.api.global.config.interceptor.auth.support.HttpMethod.GET;
import static com.api.global.config.interceptor.auth.support.HttpMethod.OPTIONS;
import static com.api.global.config.interceptor.auth.support.HttpMethod.PATCH;
import static com.api.global.config.interceptor.auth.support.HttpMethod.POST;
Expand All @@ -23,7 +23,8 @@
@Configuration
public class AuthConfig implements WebMvcConfigurer {

private final AuthArgumentResolver authArgumentResolver;
private final AuthMemberArgumentResolver authMemberArgumentResolver;
private final AuthMembersArgumentResolver authMembersArgumentResolver;
private final ParseMemberIdFromTokenInterceptor parseMemberIdFromTokenInterceptor;
private final LoginValidCheckerInterceptor loginValidCheckerInterceptor;

Expand All @@ -41,11 +42,12 @@ private HandlerInterceptor parseMemberIdFromTokenInterceptor() {
private HandlerInterceptor loginValidCheckerInterceptor() {
return new PathMatcherInterceptor(loginValidCheckerInterceptor)
.excludePathPattern("/**", OPTIONS)
.addPathPatterns("/members/test", GET, POST, PATCH, DELETE);
.addPathPatterns("/popups/**", POST, PATCH, DELETE);
}

@Override
public void addArgumentResolvers(final List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(authArgumentResolver);
resolvers.add(authMemberArgumentResolver);
resolvers.add(authMembersArgumentResolver);
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package com.api.global.config.resolver;

import com.api.global.config.interceptor.auth.support.AuthenticationContext;
import com.common.annotation.AuthMember;
import com.common.exception.AuthException;
import com.common.exception.AuthExceptionType;
import com.domain.annotation.AuthMember;
import lombok.RequiredArgsConstructor;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
Expand All @@ -14,7 +14,7 @@

@RequiredArgsConstructor
@Component
public class AuthArgumentResolver implements HandlerMethodArgumentResolver {
public class AuthMemberArgumentResolver implements HandlerMethodArgumentResolver {

private static final int ANONYMOUS = -1;

Expand All @@ -27,10 +27,12 @@ public boolean supportsParameter(final MethodParameter parameter) {
}

@Override
public Object resolveArgument(final MethodParameter parameter,
final ModelAndViewContainer mavContainer,
final NativeWebRequest webRequest,
final WebDataBinderFactory binderFactory) throws Exception {
public Object resolveArgument(
final MethodParameter parameter,
final ModelAndViewContainer mavContainer,
final NativeWebRequest webRequest,
final WebDataBinderFactory binderFactory
) {
Long memberId = authenticationContext.getPrincipal();

if (memberId == ANONYMOUS) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.api.global.config.resolver;

import com.api.global.config.interceptor.auth.support.AuthenticationContext;
import com.common.exception.AuthException;
import com.common.exception.AuthExceptionType;
import com.domain.annotation.AuthMembers;
import com.domain.domains.member.domain.Member;
import com.domain.domains.member.domain.MemberRepository;
import com.domain.domains.member.domain.vo.MemberRole;
import com.domain.domains.member.exception.MemberException;
import com.domain.domains.member.exception.MemberExceptionType;
import lombok.RequiredArgsConstructor;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

import java.util.Arrays;
import java.util.List;

import static java.util.Objects.requireNonNull;

@RequiredArgsConstructor
@Component
public class AuthMembersArgumentResolver implements HandlerMethodArgumentResolver {

private static final int ANONYMOUS = -1;

private final AuthenticationContext authenticationContext;
private final MemberRepository memberRepository;

@Override
public boolean supportsParameter(final MethodParameter parameter) {
return parameter.hasParameterAnnotation(AuthMembers.class) &&
parameter.getParameterType().equals(Long.class);
}

@Override
public Object resolveArgument(
final MethodParameter parameter,
final ModelAndViewContainer mavContainer,
final NativeWebRequest webRequest,
final WebDataBinderFactory binderFactory
) {
Long memberId = authenticationContext.getPrincipal();

if (memberId == ANONYMOUS) {
throw new AuthException(AuthExceptionType.LOGIN_INVALID_EXCEPTION);
}

Member member = findMember(memberId);
List<MemberRole> permittedRoles = getPermittedRoles(parameter);
if (!permittedRoles.contains(member.getMemberRole())) {
throw new AuthException(AuthExceptionType.FORBIDDEN_AUTH_LEVEL);
}

return memberId;
}

private Member findMember(final Long memberId) {
return memberRepository.findById(memberId)
.orElseThrow(() -> new MemberException(MemberExceptionType.MEMBER_NOT_FOUND_EXCEPTION));
}

private List<MemberRole> getPermittedRoles(final MethodParameter parameter) {
AuthMembers auths = parameter.getParameterAnnotation(AuthMembers.class);
requireNonNull(auths);
return Arrays.asList(auths.permit());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.api.popups.application;

import com.domain.domains.popups.domain.PopupsRepository;
import com.domain.domains.popups.domain.response.PopupsSimpleResponse;
import com.domain.domains.popups.domain.response.PopupsSpecificResponse;
import com.domain.domains.popups.exception.PopupsException;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

import static com.domain.domains.popups.exception.PopupsExceptionType.POPUPS_NOT_FOUND_EXCEPTION;

@RequiredArgsConstructor
@Transactional(readOnly = true)
@Service
public class PopupsQueryService {

private final PopupsRepository popupsRepository;

public PopupsSpecificResponse findById(final Long popupsId) {
return popupsRepository.findSpecificById(popupsId)
.orElseThrow(() -> new PopupsException(POPUPS_NOT_FOUND_EXCEPTION));
}

public List<PopupsSimpleResponse> findAll(final Long popupsId, final Integer pageSize) {
return popupsRepository.findAllWithPaging(popupsId, pageSize);
}
}
Loading
Loading