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

v1.3.0 #828

Merged
merged 55 commits into from
Oct 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
a1a27dc
[FE] feat: 토스트 구현 (#608)
hae-on Oct 6, 2023
1800ceb
[FE] 로그인 안했을 때도 리뷰를 1개 확인할 수 있는 기능 구현 (#725)
xodms0309 Oct 9, 2023
2660968
[BE] refactor: 레시피 좋아요 api에서 불필요하게 나가는 쿼리 제거 (#680)
hanueleee Oct 10, 2023
d4a820e
[BE] 특정 상품에서 좋아요를 가장 많이 받은 리뷰를 응답하는 API 연결 (#724)
70825 Oct 10, 2023
a3d1be5
[FE] feat: 홈에서 카테고리 눌렀을 때 context에 저장하는 방식으로 변경 (#732)
xodms0309 Oct 11, 2023
cc664d4
refactor: 캐러셀 Index index 수정 (#736)
hae-on Oct 11, 2023
22e792d
[BE] feat: 어드민 관리자 api 구현 (#731)
wugawuga Oct 11, 2023
9f3232c
[FE] fix: 베스트리뷰 요청 204 처리 (#742)
Leejin-Yang Oct 12, 2023
1b770e3
[BE] refactor: 로그인 세션 정보를 DB에 저장하는 기능 추가 (#730)
70825 Oct 12, 2023
1c3fa0e
[BE] fix: 로그아웃시 쿠키가 삭제되도록 수정 (#749)
70825 Oct 12, 2023
dc20956
[BE] hotfix: id, key Session 에 따로 저장 (#751)
wugawuga Oct 12, 2023
3a950c2
[FE] feat: 꿀조합 댓글 기능 구현 (#744)
xodms0309 Oct 13, 2023
679ee07
[FE] feat: 라우팅 관련 처리 (#752)
Leejin-Yang Oct 13, 2023
43cf12a
[FE] fix: 로컬스토리지 유틸 import 대소문자 수정 (#760)
Leejin-Yang Oct 13, 2023
215003f
[FE] feat: 리뷰 상세 페이지 구현 (#762)
hae-on Oct 16, 2023
62124a6
[FE] feat: 랭킹 기준 텍스트 추가 (#765)
Leejin-Yang Oct 16, 2023
5d2e717
[BE] feat: 리뷰 삭제 기능 구현 (#735)
hanueleee Oct 16, 2023
dab066e
[BE] feat: 꿀조합 댓글 작성 기능 구현 (#747)
wugawuga Oct 16, 2023
f614eda
[FE] feat: 꿀조합 댓글 조회에 credentials 추가 (#768)
xodms0309 Oct 16, 2023
ff62d0e
[FE] fix: totalElements가 없는 경우 query string에서 제외 (#770)
xodms0309 Oct 16, 2023
be83c6b
[FE] feat: 꿀조합 댓글이 없는 경우 예외처리 (#772)
xodms0309 Oct 16, 2023
4cd0eaf
[FE] feat: totalElements가 null일 때 아예 쿼리를 보내지 않도록 수정 (#774)
xodms0309 Oct 16, 2023
41a1d75
[BE] feat: 상품에 대한 리뷰 목록 조회 동적 쿼리 구현 및 API 성능 개선 (#607)
70825 Oct 16, 2023
0c55b8a
[FE] feat: 꿀조합 댓글 버그 수정 및 개선 (#779)
xodms0309 Oct 17, 2023
0cddbdf
[BE] refactor: 상품 목록 조회 api 성능 개선 (#685)
hanueleee Oct 17, 2023
16de45c
[BE] fix: ReviewDeleteEventListenerTest 통과하도록 수정 (#778)
hanueleee Oct 18, 2023
9467d94
[BE] refactor: 리뷰 랭킹 알고리즘 개선 (#737)
Go-Jaecheol Oct 18, 2023
0532bbe
[BE] refactor: 꿀조합 랭킹 알고리즘 개선 (#755)
Go-Jaecheol Oct 18, 2023
111d7ea
[FE] 리뷰 삭제 기능 구현 (#780)
xodms0309 Oct 18, 2023
e94c576
[FE] fix: storybook에서 바뀐 변수명 수정 (#783)
xodms0309 Oct 18, 2023
9342c22
[FE] refactor: 무한스크롤 페이징 관련 api 변경 적용 (#781)
xodms0309 Oct 18, 2023
87c5ac8
[BE] refactor: 상품 랭킹 알고리즘 개선 (#756)
Go-Jaecheol Oct 18, 2023
6fc930b
[FE] refactor: 이미지 압축 라이브러리 도입 (#786)
hae-on Oct 18, 2023
ef2ee73
refactor: alert를 toast로 변경 (#789)
hae-on Oct 18, 2023
3f1dd1b
[BE ]test: 비동기 로직 검증 테스트 제거 (#788)
hanueleee Oct 18, 2023
4d299d7
[BE] feat: 리뷰 상세 조회 API 구현 (#758)
Go-Jaecheol Oct 18, 2023
826e713
[BE] feat: Banner entity 구현, Banner 전체 조회 구현 (#791)
wugawuga Oct 18, 2023
3f77bf1
fix: 리뷰 상세 api 수정 (#796)
hae-on Oct 19, 2023
b91e775
[BE] feat: HikariCP 옵션 설정 (#795)
70825 Oct 19, 2023
52cf2ee
[FE] feat: 배너 조회 API 연결 및 배너 컴포넌트 작성 (#790)
Leejin-Yang Oct 19, 2023
c832bb4
fix: 꿀조합 등록 토스트 위치 이동 (#800)
hae-on Oct 19, 2023
08aeb2f
[FE] fix: totalElements와 comments 변수를 destructing 하는 방법 수정 (#801)
xodms0309 Oct 19, 2023
69c744c
[FE] feat: 꿀조합 상세 이미지 크기 조정 (#805)
Leejin-Yang Oct 19, 2023
9e928b3
[FE] router 리팩터링 (#806)
xodms0309 Oct 19, 2023
c4bb04f
[BE] feat: 리뷰 삭제시 상품 정보 업데이트 로직 추가 (#803)
hanueleee Oct 19, 2023
800f563
[BE] fix: 좋아요 취소도 좋아요를 누르지 않은 것으로 설정 (#808)
70825 Oct 19, 2023
48a20c9
fix: 리뷰 삭제시 썸네일 업데이트 로직 순서 변경 (#815)
hanueleee Oct 19, 2023
a51d2b7
[FE] feat: 꿀조합 페이지 접속 시 이전 페이지 로컬스토리지에 경로 저장 (#814)
Leejin-Yang Oct 19, 2023
7575a5c
[BE] fix: 상품에 이미지가 있는 리뷰가 없을 경우 기본 이미지로 설정 (#822)
hanueleee Oct 19, 2023
e214528
[BE] refactor: 리뷰 및 꿀조합 랭킹 Response 수정 (#821)
Go-Jaecheol Oct 19, 2023
114abf1
[BE] refactor: 프로덕션 로그인 쿠키 이름과 테스트 로그인 쿠키 이름 통일 (#819)
70825 Oct 19, 2023
a7d28e0
[ALL] docs: README 작성 (#823)
Leejin-Yang Oct 19, 2023
9b00964
[FE] feat: PB 상품 목록에서 상품 썸네일 삼김이로 수정 (#818)
Leejin-Yang Oct 19, 2023
a5b52e7
[FE] feat: 랭킹 상대 시간 추가 (#826)
Leejin-Yang Oct 19, 2023
25efbfe
[ALL] docs: 로고 이미지 수정 (#827)
hae-on Oct 19, 2023
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
114 changes: 103 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,107 @@
# 2023-fun-eat
<div align="center">

## 팀원 👨‍👨‍👧‍👧👩‍👦‍👦
<br>

| Frontend | Frontend | Frontend |
|:-------------------------------------------------------------------------------------:|:-------------------------------------------------------------------------------------:|:-------------------------------------------------------------------------------------:|
| <img src="https://avatars.githubusercontent.com/u/55427367?v=4" width=130px alt="타미"> | <img src="https://avatars.githubusercontent.com/u/80464961?v=4" width=130px alt="해온"> | <img src="https://avatars.githubusercontent.com/u/78616893?v=4" width=130px alt="황펭"> |
| [타미](https://github.com/xodms0309) | [해온](https://github.com/hae-on) | [황펭](https://github.com/Leejin-Yang) |
<img src="https://github.com/woowacourse-teams/2023-fun-eat/assets/80464961/85396306-1d3e-4d8e-8763-0e28e2a8be04" width="520px" />

| Backend | Backend | Backend | Backend |
|:--------------------------------------------------------------------------------------:|:--------------------------------------------------------------------------------------:|:--------------------------------------------------------------------------------------:|:-------------------------------------------------------------------------------------:|
| <img src="https://avatars.githubusercontent.com/u/79046106?v=4" width=130px alt="로건"/> | <img src="https://avatars.githubusercontent.com/u/33208246?v=4" width=130px alt="망고"/> | <img src="https://avatars.githubusercontent.com/u/91522259?v=4" width=130px alt="오잉"/> | <img src="https://avatars.githubusercontent.com/u/91244090?v=4" width=130px alt="우가"> |
| [로건](https://github.com/70825) | [망고](https://github.com/Go-Jaecheol) | [오잉](https://github.com/hanueleee) | [우가](https://github.com/wugawuga) |
<br>
<br>

<br><br><br>
<b>궁금해? 맛있을걸? 먹어봐! <br>
🍙 편의점 음식 리뷰 & 꿀조합 공유 서비스 🍙</b>

<br>

[![Application](http://img.shields.io/badge/funeat.site-D8EAFF?style=for-the-badge&logo=aHR0cHM6Ly9naXRodWIuY29tL3dvb3dhY291cnNlLXRlYW1zLzIwMjMtZnVuLWVhdC9hc3NldHMvODA0NjQ5NjEvOWI1OWY3NzktY2M5MS00MTJhLWE3NDUtZGQ3M2IzY2UxZGNk&logoColor=black&link=https://funeat.site/)](https://funeat.site/)
[![WIKI](http://img.shields.io/badge/-GitHub%20WiKi-FFEC99?style=for-the-badge&logoColor=black&link=https://github.com/woowacourse-teams/2023-fun-eat/wiki)](https://github.com/woowacourse-teams/2023-fun-eat/wiki)
[![Release](https://img.shields.io/github/v/release/woowacourse-teams/2023-fun-eat?style=for-the-badge&color=FFCFCF)](https://github.com/woowacourse-teams/2023-fun-eat/releases/tag/v1.3.0)

</div>

<br>

# 🥄 서비스 소개

![1_메인페이지](https://github.com/woowacourse-teams/2023-fun-eat/assets/55427367/9663f7b5-cd38-4f06-86fb-c6636fc364c6)

<br>

## 1. 편의점마다 특색있는 음식 궁금해?

![5_상품목록](https://github.com/woowacourse-teams/2023-fun-eat/assets/55427367/03fb9955-61fa-4228-a270-ce9dffc710c6)
![6_상품상세](https://github.com/woowacourse-teams/2023-fun-eat/assets/55427367/694bc8db-74bd-4fa1-b499-900cd27f5028)
![4_검색](https://github.com/woowacourse-teams/2023-fun-eat/assets/55427367/6a157e08-79d8-450b-9511-ffa461000a22)

<br>
<br>

## 2. 솔직한 리뷰를 보면 더 맛있을걸?

![2_리뷰](https://github.com/woowacourse-teams/2023-fun-eat/assets/55427367/4bf5ecd7-df08-45d0-b592-8629f3a4e3e6)

<br>
<br>

## 3. 생각지 못했던 꿀조합, 먹어봐!

![3_꿀조합](https://github.com/woowacourse-teams/2023-fun-eat/assets/55427367/8e560b40-d039-47ce-ad29-5e244cba4bf2)

<br>
<br>

# 🛠️ 기술 스택

### 백엔드

<div align="center">
<img src='https://github.com/woowacourse-teams/2023-fun-eat/assets/55427367/5b60393a-ffbf-4595-bb4c-166d091a7998' width="400px" alt="BE_기술스택"/>
</div>

<br/>

### 프론트엔드

<div align="center">
<img src='https://github.com/woowacourse-teams/2023-fun-eat/assets/55427367/e3d76698-aaa4-4eea-a878-8c03f3faf395' width="400px" alt="FE_기술스택"/>
</div>

<br/>

### 인프라

<div align="center">
<img src='https://github.com/woowacourse-teams/2023-fun-eat/assets/55427367/79399085-1245-4af4-be20-2d5402d53da7' width="400px" alt="인프라_기술스택"/>
</div>

<br>
<br>

# 인프라 구조

### CI/CD

<div align="center">
<img src="https://github.com/woowacourse-teams/2023-fun-eat/assets/55427367/3fbef028-d216-4abe-ab4f-c531b099dd33" alt="cicd">
</div>

### 구조

<div align="center">
<img src="https://github.com/woowacourse-teams/2023-fun-eat/assets/55427367/3bbb9d40-f525-43ab-8ec2-ade6e6a07139" alt="인프라 구조" />
</div>

<br>
<br>

# 👨‍👨‍👧‍👧👩‍👦‍👦 팀원

| Frontend | Frontend | Frontend | Backend | Backend | Backend | Backend |
| :-------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------: |
| <img src="https://avatars.githubusercontent.com/u/55427367?v=4" width=200px alt="타미"> | <img src="https://avatars.githubusercontent.com/u/80464961?v=4" width=200px alt="해온"> | <img src="https://avatars.githubusercontent.com/u/78616893?v=4" width=200px alt="황펭"> | <img src="https://avatars.githubusercontent.com/u/79046106?v=4" width=200px alt="로건"/> | <img src="https://avatars.githubusercontent.com/u/33208246?v=4" width=200px alt="망고"/> | <img src="https://avatars.githubusercontent.com/u/91522259?v=4" width=200px alt="오잉"/> | <img src="https://avatars.githubusercontent.com/u/91244090?v=4" width=200px alt="우가"/> |
| [🐰 타미](https://github.com/xodms0309) | [🌞 해온](https://github.com/hae-on) | [🐧 황펭](https://github.com/Leejin-Yang) | [😺 로건](https://github.com/70825) | [🥭 망고](https://github.com/Go-Jaecheol) | [👻 오잉](https://github.com/hanueleee) | [🍖 우가](https://github.com/wugawuga) |

<br>

<div align="center">
<img src="https://github.com/woowacourse-teams/2023-fun-eat/assets/55427367/27ba38de-34b4-4925-a554-9bed89089984" alt="팀소개"/>
</div>
2 changes: 2 additions & 0 deletions backend/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ dependencies {
runtimeOnly 'io.micrometer:micrometer-registry-prometheus'

implementation 'com.amazonaws:aws-java-sdk-s3:1.12.547'

implementation 'org.springframework.session:spring-session-jdbc'
}

tasks.named('test') {
Expand Down
6 changes: 5 additions & 1 deletion backend/src/main/java/com/funeat/FuneatApplication.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
package com.funeat;

import com.funeat.common.repository.BaseRepositoryImpl;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

@EnableAsync
@SpringBootApplication
@EnableJpaRepositories(repositoryBaseClass = BaseRepositoryImpl.class)
public class FuneatApplication {

public static void main(String[] args) {
SpringApplication.run(FuneatApplication.class, args);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.funeat.admin.application;

import com.funeat.admin.domain.AdminAuthInfo;
import com.funeat.auth.exception.AuthErrorCode;
import com.funeat.auth.exception.AuthException.NotLoggedInException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class AdminChecker {

@Value("${back-office.id}")
private String id;

@Value("${back-office.key}")
private String key;

public boolean check(final AdminAuthInfo adminAuthInfo) {
if (!id.equals(adminAuthInfo.getId())) {
throw new NotLoggedInException(AuthErrorCode.LOGIN_ADMIN_NOT_FOUND);
}

if (!key.equals(adminAuthInfo.getKey())) {
throw new NotLoggedInException(AuthErrorCode.LOGIN_ADMIN_NOT_FOUND);
}

return true;
}
}
117 changes: 117 additions & 0 deletions backend/src/main/java/com/funeat/admin/application/AdminService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package com.funeat.admin.application;

import static com.funeat.product.exception.CategoryErrorCode.CATEGORY_NOT_FOUND;
import static com.funeat.product.exception.ProductErrorCode.PRODUCT_NOT_FOUND;

import com.funeat.admin.dto.AdminProductResponse;
import com.funeat.admin.dto.AdminProductSearchResponse;
import com.funeat.admin.dto.AdminReviewResponse;
import com.funeat.admin.dto.AdminReviewSearchResponse;
import com.funeat.admin.dto.ProductCreateRequest;
import com.funeat.admin.dto.ProductSearchCondition;
import com.funeat.admin.dto.ProductUpdateRequest;
import com.funeat.admin.dto.ReviewSearchCondition;
import com.funeat.admin.repository.AdminProductRepository;
import com.funeat.admin.repository.AdminReviewRepository;
import com.funeat.admin.specification.AdminProductSpecification;
import com.funeat.admin.specification.AdminReviewSpecification;
import com.funeat.product.domain.Category;
import com.funeat.product.domain.Product;
import com.funeat.product.dto.CategoryResponse;
import com.funeat.product.exception.CategoryException.CategoryNotFoundException;
import com.funeat.product.exception.ProductException.ProductNotFoundException;
import com.funeat.product.persistence.CategoryRepository;
import com.funeat.product.persistence.ProductRepository;
import com.funeat.review.domain.Review;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional(readOnly = true)
public class AdminService {

private static final int DEFAULT_PAGE_SIZE = 10;

private final ProductRepository productRepository;
private final AdminProductRepository adminProductRepository;
private final CategoryRepository categoryRepository;
private final AdminReviewRepository adminReviewRepository;

public AdminService(final ProductRepository productRepository, final AdminProductRepository adminProductRepository,
final CategoryRepository categoryRepository, final AdminReviewRepository adminReviewRepository) {
this.productRepository = productRepository;
this.adminProductRepository = adminProductRepository;
this.categoryRepository = categoryRepository;
this.adminReviewRepository = adminReviewRepository;
}

@Transactional
public Long addProduct(final ProductCreateRequest request) {
final Category findCategory = categoryRepository.findById(request.getCategoryId())
.orElseThrow(() -> new CategoryNotFoundException(CATEGORY_NOT_FOUND, request.getCategoryId()));

final Product product = Product.create(request.getName(), request.getPrice(), request.getContent(),
findCategory);

return productRepository.save(product).getId();
}

public List<CategoryResponse> getAllCategories() {
final List<Category> findCategories = categoryRepository.findAll();

return findCategories.stream()
.map(CategoryResponse::toResponse)
.collect(Collectors.toList());
}

public AdminProductSearchResponse getSearchProducts(final ProductSearchCondition condition,
final Pageable pageable) {
final Specification<Product> specification = AdminProductSpecification.searchBy(condition);

final Page<Product> findProducts = adminProductRepository.findAllForPagination(specification, pageable,
condition.getTotalElements());

final List<AdminProductResponse> productResponses = findProducts.stream()
.map(AdminProductResponse::toResponse)
.collect(Collectors.toList());

final Boolean isLastPage = isLastPage(findProducts, condition.getPrePage());

return new AdminProductSearchResponse(productResponses, findProducts.getTotalElements(), isLastPage);
}

private <T> boolean isLastPage(final Page<T> findProducts, Long prePage) {
return prePage * DEFAULT_PAGE_SIZE + findProducts.getContent().size() == findProducts.getTotalElements();
}

@Transactional
public void updateProduct(final Long productId, final ProductUpdateRequest request) {
final Product findProduct = productRepository.findById(productId)
.orElseThrow(() -> new ProductNotFoundException(PRODUCT_NOT_FOUND, productId));

final Category findCategory = categoryRepository.findById(request.getCategoryId())
.orElseThrow(() -> new CategoryNotFoundException(CATEGORY_NOT_FOUND, request.getCategoryId()));

findProduct.update(request.getName(), request.getContent(), request.getPrice(), findCategory);
}

public AdminReviewSearchResponse getSearchReviews(final ReviewSearchCondition condition, final Pageable pageable) {
final Specification<Review> specification = AdminReviewSpecification.searchBy(condition);

final Page<Review> findReviews = adminReviewRepository.findAllForPagination(specification, pageable,
condition.getTotalElements());

final List<AdminReviewResponse> reviewResponses = findReviews.stream()
.map(AdminReviewResponse::toResponse)
.collect(Collectors.toList());

final Boolean isLastPage = isLastPage(findReviews, condition.getPrePage());

return new AdminReviewSearchResponse(reviewResponses, findReviews.getTotalElements(), isLastPage);
}
}
20 changes: 20 additions & 0 deletions backend/src/main/java/com/funeat/admin/domain/AdminAuthInfo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.funeat.admin.domain;

public class AdminAuthInfo {

private final String id;
private final String key;

public AdminAuthInfo(final String id, final String key) {
this.id = id;
this.key = key;
}

public String getId() {
return id;
}

public String getKey() {
return key;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.funeat.admin.dto;

import com.funeat.product.domain.Category;

public class AdminCategoryResponse {

private final Long id;
private final String name;
private final String image;

public AdminCategoryResponse(final Long id, final String name, final String image) {
this.id = id;
this.name = name;
this.image = image;
}

public static AdminCategoryResponse toResponse(final Category category) {
return new AdminCategoryResponse(category.getId(), category.getName(), category.getImage());
}

public Long getId() {
return id;
}

public String getName() {
return name;
}

public String getImage() {
return image;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.funeat.admin.dto;

import com.funeat.product.domain.Product;

public class AdminProductResponse {

private final Long id;
private final String name;
private final String content;
private final Long price;
private final AdminCategoryResponse categoryResponse;

private AdminProductResponse(final Long id, final String name, final String content,
final Long price, final AdminCategoryResponse categoryResponse) {
this.id = id;
this.name = name;
this.content = content;
this.price = price;
this.categoryResponse = categoryResponse;
}

public static AdminProductResponse toResponse(final Product product) {
final AdminCategoryResponse categoryResponse = AdminCategoryResponse.toResponse(product.getCategory());

return new AdminProductResponse(product.getId(), product.getName(), product.getContent(), product.getPrice(),
categoryResponse);
}

public Long getId() {
return id;
}

public String getName() {
return name;
}

public String getContent() {
return content;
}

public Long getPrice() {
return price;
}

public AdminCategoryResponse getCategoryResponse() {
return categoryResponse;
}
}
Loading
Loading