Merge pull request #55 from Development-team-1/버그픽스_창훈

중요 버그 수정
This commit is contained in:
Sangbum Park
2022-03-18 18:03:22 +09:00
committed by GitHub
23 changed files with 375 additions and 56 deletions

View File

@@ -1,5 +1,6 @@
import axios from "axios";
import jwt from "@/common/jwt";
import router from "@/router/router";
export default {
async requestReissue() {
@@ -22,5 +23,12 @@ export default {
axios.defaults.headers.common['Authorization'] = "Bearer " + jwt.getToken();
return axios.get(process.env.VUE_APP_CUSTOMER_SERVICE_BASEURL+"/user-service/auth/check/access-token");
},
logout(){
axios.post(process.env.VUE_APP_CUSTOMER_SERVICE_BASEURL+"/user-service/auth/logout",'',{headers: {"X-AUTH-TOKEN": jwt.getToken(),}})
.finally(()=>{
router.push('/login')
jwt.destroyAll()
})
}
}

View File

@@ -41,5 +41,12 @@ export default {
},
requestStore(storeId) {
return axios.get(process.env.VUE_APP_CUSTOMER_SERVICE_BASEURL + "/store-service/store/" + storeId);
},
getFavoriteStoreByStoreId(storeId) {
return axios.get(process.env.VUE_APP_CUSTOMER_SERVICE_BASEURL + "/store-service/api/customer/favoriteStore/" + storeId);
},
markStar(storeId) {
return axios.patch(process.env.VUE_APP_CUSTOMER_SERVICE_BASEURL + "/store-service/api/customer/favoriteStore/" + storeId);
}
}

View File

@@ -23,6 +23,9 @@ export default {
localStorage.removeItem(EXPIRED_TIME_NAME);
},
isExpired() {
if(this.getExpiredTime() == null || this.getToken() == null)
return true;
const expiredTime = this.getExpiredTime();
const expiredMoment = moment(expiredTime);

View File

@@ -6,7 +6,7 @@
elevation="1"
>
<v-app-bar-nav-icon>
<v-app-bar-nav-icon @click="$router.back()">
<v-icon>mdi-arrow-left</v-icon>
</v-app-bar-nav-icon>
<v-spacer></v-spacer>
@@ -29,16 +29,31 @@
<v-icon>mdi-bell-outline</v-icon>
</v-badge>
</v-btn>
<v-btn
color="white"
elevation="0"
@click="logout"
>
<v-icon>mdi-logout</v-icon>
</v-btn>
</v-app-bar>
</template>
<script>
import authApi from "@/api/auth";
export default {
name: "AppNavigation",
props: ["notificationCounts"],
methods: {
goNotification: function() {
this.$router.push('/notification');
},
logout: function () {
if(confirm("로그아웃하시겠습니까?")){
authApi.logout();
}
}
}
}

View File

@@ -15,16 +15,69 @@
<b>{{store.name}}</b>
</v-toolbar-title>
<v-spacer></v-spacer>
<v-btn icon>
<v-icon>mdi-magnify</v-icon>
<v-btn
icon
@click="markStar()"
>
<v-icon
:color="favoriteStore.color"
>{{ favoriteStore.icon }}</v-icon>
</v-btn>
</v-app-bar>
</template>
<script>
import storeApi from "@/api/store";
export default {
name: "StoreNavigation",
props: ["store"],
data: function() {
return {
storeId:-1,
favoriteStore:{
status:true,
color:null,
icon: 'mdi-heart-outline'
}
}
},
methods:{
markStar:function () {
this.changeStarStatus()
var vm = this
storeApi.markStar(this.storeId)
.then(()=>{
vm.favoriteStore.status?
alert('즐겨찾는 매장에서 제외되었습니다.') : alert('즐겨찾는 매장에 등록되었습니다.')
})
},
getFavoriteStoreByStoreId: function (storeId){
var vm = this
storeApi.getFavoriteStoreByStoreId(storeId)
.then((response)=>{
if(response.data.data.exist) vm.changeStarStatus()
})
},
changeStarStatus(){
if(this.favoriteStore.status){
this.favoriteStore.color = "rgb(255, 152, 0)"
this.favoriteStore.icon = "mdi-heart"
}else{
this.favoriteStore.color = null
this.favoriteStore.icon = "mdi-heart-outline"
}
this.favoriteStore.status= !this.favoriteStore.status
}
},
mounted() {
console.log(this.$route.params)
this.storeId =this.$route.params.storeId
this.getFavoriteStoreByStoreId(this.storeId)
}
}
</script>

View File

@@ -32,44 +32,56 @@ const routes = [
{
path: '/',
redirect: 'home',
beforeEnter: authCheck,
component: HomeLayout,
children: [
{
path: "/home",
beforeEnter: authCheck,
name: 'home',
component: () => import('../views/HomeView')
},
{
path: "/search",
beforeEnter: authCheck,
name: 'search-store',
component: () => import('../views/SearchStore')
},
{
path: "/history",
beforeEnter: authCheck,
name: 'order-history',
component: () => import('../views/OrderHistory')
},
{
path: "/favorite",
beforeEnter: authCheck,
name: 'favorite-store',
component: () => import('../views/FavoriteStore')
},
{
path: "/notification",
beforeEnter: authCheck,
name: 'notification',
component: () => import('../views/NotificationView')
},
{
path: "/item/:itemId",
beforeEnter: authCheck,
name: 'itemDetail',
component: () => import('../views/ItemDetail')
},
{
path: "/order",
beforeEnter: authCheck,
name: 'orderPage',
component: () => import('../views/OrderPage')
},
{
path: "/mypage",
beforeEnter: authCheck,
name: 'mypage',
component: () => import('../views/MyPage')
},
]
},
{
@@ -80,12 +92,12 @@ const routes = [
{
path: '/store',
redirect: 'store',
beforeEnter: authCheck,
component: StoreLayout,
children: [
{
path: "/store/:storeId",
name: "store",
beforeEnter: authCheck,
component: () => import('../views/StoreView'),
props: true
},

View File

@@ -36,6 +36,7 @@
<v-col >
<v-container>
<v-text-field
readonly
v-model="setItem.count"
append-outer-icon="mdi-plus"
prepend-icon="mdi-minus"
@@ -111,7 +112,7 @@ export default {
itemOptions:[],
},
setItem:{
count:0,
count:1,
storeId:-1,
itemId:-1,
price:0,
@@ -145,9 +146,12 @@ export default {
},
addItemCount: function (v){
this.setItem.count =
this.setItem.count+v >=0? this.setItem.count+v: 0;
this.setItem.count+v >=1? this.setItem.count+v: 1;
},
addItem: function(){
if(!this.validItem())
return;
orderApi.addItemToBasket(this.setItem)
.then(response=>{
console.log(response)
@@ -156,7 +160,16 @@ export default {
.catch(error=>{
console.log(error.response)
})
},
validItem(){
if(this.setItem.count <= 0 || isNaN(this.setItem.count)){
alert("수량이 잘못되었습니다.")
this.setItem.count =1
return false
}
}
},
}
</script>

View File

@@ -120,7 +120,6 @@ export default {
},
},
async mounted() {
if (!jwt.isExpired())
await router.push("/");

View File

@@ -56,11 +56,6 @@
@click="saveOrder"
color="primary"
>주문하기</v-btn>
<!-- <v-btn-->
<!-- style="position: absolute; bottom: 0;transform: translateY(-100%); width: 100%"-->
<!-- @click="orderItems"-->
<!-- >주문하기</v-btn>-->
</div>
</template>
@@ -90,10 +85,12 @@ export default {
orderApi.saveOrder()
.then(()=>{
this.$emit('plusCount')
alert('주문되었습니다.')
this.$router.push("/history")
})
.catch(error=>{
alert("문제가 발생하였습니다. 다시 시도해보세요.")
console.log(error)
//this.$router.replace("/")
})
@@ -105,8 +102,9 @@ export default {
this.orderData=response.data.data
})
.catch(error=>{
alert("장바구니에 상품이 없습니다.")
this.$router.back();
console.log(error.response)
history.back();
})
},
}

View File

@@ -28,6 +28,7 @@
class="mx-auto mb-5"
outlined
v-for="item in category.items"
@click="itemDetail(item.id)"
:key="item.id"
>
<v-list-item three-line>
@@ -91,6 +92,9 @@ export default {
this.tags.push(category.name);
})
},
itemDetail: function (itemId) {
this.$router.push("/item/"+itemId)
}
}
}
</script>

View File

@@ -20,7 +20,7 @@ export default {
return axios.get(process.env.VUE_APP_OWNER_SERVICE_BASEURL+'/store-service/api/owner/item/'+itemId)
},
saveItem(method, itemData){
const _url = process.env.VUE_APP_OWNER_SERVICE_BASEURL+'/store-service/api/owner/item'+(method==='put'?+"/"+itemData.itemId:'')
const _url = process.env.VUE_APP_OWNER_SERVICE_BASEURL+'/store-service/api/owner/item'+(method==='put'? "/"+itemData.itemId:'')
console.log(_url)
return axios({
method:method,

View File

@@ -163,6 +163,7 @@ export default {
},
methods:{
save : function () {
this.dialog =false
this.$emit('save')
},
addItemOption : function (itemOptionValue,optionType){

View File

@@ -1,7 +1,7 @@
<template>
<v-app id="inspire">
<side-bar v-bind:drawer="drawer"></side-bar>
<top-bar v-on:drawEvent="drawer = !drawer"></top-bar>
<side-bar :drawer="drawer"></side-bar>
<top-bar @drawerEvent="drawer = !drawer"></top-bar>
<v-main style="background: #f5f5f540">
<v-container class="py-8, px-6" fluid>
<router-view></router-view>

View File

@@ -5,20 +5,9 @@
app>
<v-img
height="140"
class="pa-4"
src="https://preview.pixlr.com/images/800wm/1439/2/1439104804.jpg"
>
<div class="text-center">
<v-avatar class="mb-4" color="grey darken-1" size="64">
<v-img
aspect-ratio="30"
src="https://yt3.ggpht.com/esazPAO03T0f0vKdByJvkDy6MSwjyG5f-c_2S2CJapszQ3KPQyZarpoqvgv0Us0atUbILytj=s88-c-k-c0x00ffffff-no-rj"
/>
</v-avatar>
<h2 class="white--text">Web Burden</h2>
</div>
</v-img>
class="px-7 mx-7"
:src="require('@/assets/just-logo.png')"
contain/>
<v-divider></v-divider>
<v-list>
<v-list-item v-for="(link,index) in links" :key="link.icon" link @click="clickGo(index, link.url)">
@@ -41,10 +30,10 @@ export default {
data() {
return {
links: [
{name: "지난 주문", url: "/prev-order", icon: "mdi-home-outline"},
{name: "카테고리", url: "/category", icon: "mdi-magnify"},
{name: "주문", url: "/order", icon: "mdi-cards-heart-outline"},
{name: "메뉴 관리", url: "/menu", icon: "mdi-cards-heart-outline"},
{name: "지난 주문", url: "/prev-order", icon: "mdi-clipboard-check-outline"},
{name: "카테고리", url: "/category", icon: "mdi-shape-outline"},
{name: "주문", url: "/order", icon: "mdi-order-numeric-ascending"},
{name: "메뉴 관리", url: "/menu", icon: "mdi-cog-outline"},
],
};
},

View File

@@ -75,7 +75,6 @@ operation::items-get[snippets='curl-request,http-request,http-response,path-para
== 상품 (판매자)
=== 상품 조회
=======
=== 상품 리스트 조회(구매자)
operation::customer-itemList-get[snippets='curl-request,http-request,http-response,path-parameters,response-fields']
@@ -109,3 +108,9 @@ operation::store-get[snippets='curl-request,http-request,http-response,path-para
operation::api-get-store-byUserId[snippets='curl-request,http-request,http-response,request-headers,response-fields']
=== 매장 리스트 조회
operation::stores-get[snippets='curl-request,http-request,http-response,path-parameters,response-fields']
== 즐겨찾는 매장
=== 즐겨찾는 매장 조회
operation::get-favoritestore-by-storeid[snippets='curl-request,http-request,http-response,request-headers,path-parameters,response-fields']
=== 즐겨찾는 매장 추가/삭제
operation::patch-FavoriteStore[snippets='curl-request,http-request,http-response,request-headers,path-parameters']

View File

@@ -0,0 +1,14 @@
package com.justpickup.storeservice.domain.favoritestore.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class GetFavoriteStoreByStoreIdDto {
private Long userId;
private Long storeId;
private boolean isExist;
}

View File

@@ -1,7 +1,12 @@
package com.justpickup.storeservice.domain.favoritestore.repository;
import com.justpickup.storeservice.domain.favoritestore.entity.FavoriteStore;
import com.justpickup.storeservice.domain.store.entity.Store;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface FavoriteStoreRepository extends JpaRepository<FavoriteStore,Long> {
Optional<FavoriteStore> findByUserIdAndStore(Long userId, Store store);
}

View File

@@ -0,0 +1,55 @@
package com.justpickup.storeservice.domain.favoritestore.service;
import com.justpickup.storeservice.domain.favoritestore.dto.GetFavoriteStoreByStoreIdDto;
import com.justpickup.storeservice.domain.favoritestore.entity.FavoriteStore;
import com.justpickup.storeservice.domain.favoritestore.repository.FavoriteStoreRepository;
import com.justpickup.storeservice.domain.store.entity.Store;
import com.justpickup.storeservice.domain.store.exception.NotExistStoreException;
import com.justpickup.storeservice.domain.store.repository.StoreRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
@Slf4j
@RequiredArgsConstructor
@Service
public class FavoriteStoreService {
private final FavoriteStoreRepository favoriteStoreRepository;
private final StoreRepository storeRepository;
@Transactional
public void patchFavoriteStore(Long userId, Long storeId){
Store store = storeRepository.findById(storeId)
.orElseThrow(()-> new NotExistStoreException("매장이 존재하지않습니다."));
favoriteStoreRepository
.findByUserIdAndStore(userId, store)
.ifPresentOrElse(
favoriteStoreRepository::delete,
()->favoriteStoreRepository.save(FavoriteStore.of(userId, store))
);
}
@Transactional
public GetFavoriteStoreByStoreIdDto getFavoriteStoreByStoreId(Long userId, Long storeId){
Store store = storeRepository.findById(storeId)
.orElseThrow(()-> new NotExistStoreException("매장이 존재하지않습니다."));
Optional<FavoriteStore> byUserIdAndStore = favoriteStoreRepository
.findByUserIdAndStore(userId, store);
if(byUserIdAndStore.isPresent()){
return new GetFavoriteStoreByStoreIdDto(userId,storeId, true);
}else {
return new GetFavoriteStoreByStoreIdDto(userId,storeId, false);
}
}
}

View File

@@ -0,0 +1,40 @@
package com.justpickup.storeservice.domain.favoritestore.web;
import com.justpickup.storeservice.domain.favoritestore.dto.GetFavoriteStoreByStoreIdDto;
import com.justpickup.storeservice.domain.favoritestore.service.FavoriteStoreService;
import com.justpickup.storeservice.global.dto.Result;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@Slf4j
@RequiredArgsConstructor
@RequestMapping("/api/customer/favoriteStore")
public class FavoriteStoreCustomerApiController {
private final FavoriteStoreService favoriteStoreService;
@GetMapping("/{storeId}")
public ResponseEntity getFavoriteStoreByStoreId(@RequestHeader(value = "user-id") String userId, @PathVariable Long storeId){
GetFavoriteStoreByStoreIdDto favoriteStoreByStoreId = favoriteStoreService.getFavoriteStoreByStoreId(Long.parseLong(userId), storeId);
return ResponseEntity
.status(HttpStatus.OK)
.body(Result.createSuccessResult(favoriteStoreByStoreId));
}
@PatchMapping("/{storeId}")
public ResponseEntity patchFavoriteStore(@RequestHeader(value = "user-id") String userId, @PathVariable Long storeId){
favoriteStoreService.patchFavoriteStore(Long.parseLong(userId),storeId);
return ResponseEntity
.status(HttpStatus.NO_CONTENT)
.body(Result.success());
}
}

View File

@@ -48,24 +48,22 @@ public class ItemRepositoryCustom {
Long count = queryFactory.select(QItem.item.count())
.from(QItem.item)
.join(QItem.item.category)
.leftJoin(QItem.item.store)
.on(QItem.item.store.userId.eq(userId))
.join(QItem.item.store)
.where(
QItem.item.name.contains(word)
.or(QItem.item.category.name.contains(word))
.or(QItem.item.category.name.contains(word)),
QItem.item.store.userId.eq(userId)
)
.limit(pageable.getPageSize())
.offset(pageable.getOffset())
.fetchOne();
//List 가져오기
List<Item> itemList = queryFactory.selectFrom(QItem.item)
.join(QItem.item.category).fetchJoin()
.leftJoin(QItem.item.store)
.on(QItem.item.store.id.eq(userId))
.join(QItem.item.store)
.where(
QItem.item.name.contains(word)
.or(QItem.item.category.name.contains(word))
.or(QItem.item.category.name.contains(word)),
QItem.item.store.userId.eq(userId)
)
.limit(pageable.getPageSize())
.offset(pageable.getOffset())

View File

@@ -91,7 +91,7 @@ public class ItemServiceImpl implements ItemService {
itemOptionDtos
.forEach(itemOptionDto -> {
if(itemOptionDto.getId()==null) return;
if(itemOptionDto.getId()!=null) return;
if (itemOptionRepository.existsById(itemOptionDto.getId()))
itemOptionRepository.save(ItemOptionDto.createItemOption(itemOptionDto, item));
});

View File

@@ -0,0 +1,111 @@
package com.justpickup.storeservice.domain.favoritestore.web;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.justpickup.storeservice.config.TestConfig;
import com.justpickup.storeservice.domain.favoritestore.dto.GetFavoriteStoreByStoreIdDto;
import com.justpickup.storeservice.domain.favoritestore.service.FavoriteStoreService;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.BDDMockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName;
import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.patch;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
import static org.springframework.restdocs.request.RequestDocumentation.pathParameters;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@WebMvcTest(FavoriteStoreCustomerApiController.class)
@Import(TestConfig.class)
@AutoConfigureRestDocs(uriHost = "just-pickup.com", uriPort = 8000)
class FavoriteStoreCustomerApiControllerTest {
@Autowired
ObjectMapper objectMapper;
@Autowired
MockMvc mockMvc;
@MockBean
FavoriteStoreService favoriteStoreService;
@Test
@DisplayName("Get_즐겨찾는 매장")
void getFavoriteStoreByStoreId() throws Exception {
//given
Long userId=1L;
Long storeId=1L;
BDDMockito.given(favoriteStoreService
.getFavoriteStoreByStoreId(userId,storeId))
.willReturn(
new GetFavoriteStoreByStoreIdDto(userId,storeId,true));
//when
ResultActions resultActions = mockMvc.perform(
get("/api/customer/favoriteStore/{storeId}",storeId)
.header("user-id", userId));
//then
resultActions.andExpect(status().isOk())
.andDo(print())
.andDo(document("get-favoritestore-by-storeid",
requestHeaders(
headerWithName("user-id").description("로그인한 유저 id")
),
pathParameters(
parameterWithName("storeId").description("매장 고유 번호")
),
responseFields(
fieldWithPath("code").description("결과 코드 SUCCESS/ERROR"),
fieldWithPath("message").description("메시지"),
fieldWithPath("data.userId").description("유저 고유번호"),
fieldWithPath("data.storeId").description("매장 고유 번호"),
fieldWithPath("data.exist").description("즐겨찾기 매장 존재여부")
))
);
}
@Test
@DisplayName("즐겨찾는 매장 추가 or 제거")
void patchFavoriteStore() throws Exception {
//given
Long userId=1L;
Long storeId=1L;
//when
ResultActions resultActions = mockMvc.perform(
patch("/api/customer/favoriteStore/{storeId}",storeId)
.header("user-id", userId));
//then
resultActions.andExpect(status().isNoContent())
.andDo(print())
.andDo(document("patch-FavoriteStore",
requestHeaders(
headerWithName("user-id").description("로그인한 유저 id")
),
pathParameters(
parameterWithName("storeId").description("매장 고유 번호")
)
)
);
}
}

View File

@@ -1,11 +1,9 @@
package com.justpickup.userservice.domain.jwt.service;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.justpickup.userservice.domain.user.dto.OAuthAttributeDto;
import com.justpickup.userservice.domain.user.entity.Customer;
import com.justpickup.userservice.domain.user.repository.CustomerRepository;
import com.justpickup.userservice.domain.user.service.UserServiceImpl;
import com.justpickup.userservice.global.dto.Result;
import com.justpickup.userservice.global.utils.CookieProvider;
import com.justpickup.userservice.global.utils.JwtTokenProvider;
import lombok.RequiredArgsConstructor;
@@ -30,8 +28,6 @@ import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
@@ -42,8 +38,6 @@ import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
public class OAuthService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
private final CustomerRepository customerRepository;
private final HttpServletResponse response;
private final HttpServletRequest request;
private final JwtTokenProvider jwtTokenProvider;
private final RefreshTokenService refreshTokenService;
private final UserServiceImpl userServiceImpl;
@@ -71,11 +65,6 @@ public class OAuthService implements OAuth2UserService<OAuth2UserRequest, OAuth2
Collection<? extends GrantedAuthority> authorities = userServiceImpl.loadUserByUsername(userEmail).getAuthorities();
List<String> roles = authorities
.stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList());
return new DefaultOAuth2User(
authorities
, attributeDto.getAttributes()