Skip to content

Commit

Permalink
Merge pull request #221 from Team-TenTen/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
young970 authored Feb 2, 2024
2 parents 66a54dc + 2cf1c89 commit 7cb1816
Show file tree
Hide file tree
Showing 23 changed files with 466 additions and 135 deletions.
71 changes: 41 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,37 @@
- 슬랙 혹은 단톡방에서 팀원들이 해당 링크를 봤는지 확인하고 싶다.
- 슬랙 혹은 카카오 단톡방에 링크를 공유하면 해당 링크를 본 인원들을 파악하고 싶다.
- 지역 맛집 혹은 쇼핑몰 등을 검색하면 양질의 정보가 아닌 많은 광고들로 인해 정보를 필터링하는 추가적인 비용이 소모된다.
## 🔗 핵심 기능 🔗
- **링크 아카이빙**:
<br>링크의 메타태그를 통해 자동으로 제목을 추천해주며 태그를 통해 추후 필터링의 용이성을 올림
<img width="948" alt="스크린샷 2024-01-23 오전 1 38 16" src="https://github.com/Team-TenTen/LinkHub-BE/assets/108216455/370b6e66-141d-415c-b685-5da911142f87">


- **공유 아카이빙 공간**:
<br>스페이스 초대 기능을 통해 언제든지 팀원들을 초대할 수 있으며 viewer 및 editor 등의 세부 권한 지정이 가능하다. 또한 설정 시 팀원들의 링크 조회 이력을 표시할 수 있다.
<img width="957" alt="스크린샷 2024-01-23 오전 1 38 33" src="https://github.com/Team-TenTen/LinkHub-BE/assets/108216455/53bf0690-1e53-4e83-8db2-e140971eb1fd">


- **즐겨찾기**:
<br>공개된 스페이스(링크 저장소)는 즐겨찾기 기능을 통해 구독을 할 수 있다. 또한 많은 유저들에게 즐겨찾기된 스페이스는 메인화면 즐겨찾기 순 옵션에서 상위에 노출되게 된다.
<img width="948" alt="스크린샷 2024-01-23 오전 1 38 47" src="https://github.com/Team-TenTen/LinkHub-BE/assets/108216455/5a6d8d7a-6dfe-4433-9b0f-878b2ef89741">


- **가져오기**:
<br>공개된 스페이스를(링크 저장소) 복사하여 편집 가능한 나의 스페이스로 만들 수 있다. 즐겨찾기와 다르게 원본의 이후 변경사항은 반영되지 않는다.
<img width="955" alt="스크린샷 2024-01-23 오전 1 39 06" src="https://github.com/Team-TenTen/LinkHub-BE/assets/108216455/b4539f72-a9ff-4120-ad98-c431a2e26f9d">

## 🔗 기능 시연 🔗
### 메인 화면 및 검색
좋아요를 많이 받은 상위 링크들과 최신순 혹은 즐겨찾기 순의 스페이스(링크 저장소)를 필터링하여 보여준다. 또한 검색 기능을 제공하여 편의성 확보
| 메인 페이지 | 스페이스(링크 저장소) 검색 | 유저 검색 |
|:---:|:---:|:---:|
| ![메인 페이지](https://github.com/Team-TenTen/LinkHub-BE/assets/108216455/14368d47-d597-4bac-a375-4886b10f3eb4) | ![스페이스(링크 저장소) 검색](https://github.com/Team-TenTen/LinkHub-BE/assets/108216455/4ace19c3-0419-4955-a4de-1b2e355046de) | ![유저 검색](https://github.com/Team-TenTen/LinkHub-BE/assets/108216455/de209ba9-8871-4953-b86e-18360a794269) |

### 링크 아카이빙
링크의 메타태그를 통해 자동으로 제목을 추천해주며 태그를 통해 추후 필터링의 용이성을 올림
| 링크 생성 | 링크 필터링 |
|:---:|:---:|
| <img src="https://github.com/Team-TenTen/LinkHub-BE/assets/108216455/73319202-e0eb-437d-8521-9907fc4bf856" width="300"> | <img src="https://github.com/Team-TenTen/LinkHub-BE/assets/108216455/18b07c5f-e369-4772-88c3-81e28a29c0ef" width="300"> |

### 공유 아카이빙 공간
스페이스 초대 기능을 통해 언제든지 팀원들을 초대할 수 있으며 viewer 및 editor 등의 세부 권한 지정이 가능하다. 또한 설정 시 팀원들의 링크 조회 이력을 표시할 수 있다.
| 초대 하기 | 초대 받기 | 링크 조회 이력 표시 |
|:---:|:---:|:---:|
| ![초대 하기](https://github.com/Team-TenTen/LinkHub-BE/assets/108216455/cae806df-b646-491d-97ba-611179d92cc7) | ![초대 받기](https://github.com/Team-TenTen/LinkHub-BE/assets/108216455/9ed2649d-49ce-4209-9136-84b29e2a938e) | ![링크 조회 이력 표시](https://github.com/Team-TenTen/LinkHub-BE/assets/108216455/e4748848-8b7e-41af-9a40-cb0de6524f28) |

### 즐겨찾기
공개된 스페이스(링크 저장소)는 즐겨찾기 기능을 통해 구독을 할 수 있다. 또한 많은 유저들에게 즐겨찾기된 스페이스는 메인화면 즐겨찾기 순 옵션에서 상위에 노출되게 된다.
| 즐겨찾기 |
|:---:|
| <img src="https://github.com/Team-TenTen/LinkHub-BE/assets/108216455/e7d6fc60-fe21-499c-bb9e-7c6db1d5354a" width="300"> |

### 가져오기
공개된 스페이스를(링크 저장소) 복사하여 편집 가능한 나의 스페이스로 만들 수 있다. 즐겨찾기와 다르게 원본의 이후 변경사항은 반영되지 않는다.
| 가져오기 |
|:---:|
| <img src="https://github.com/Team-TenTen/LinkHub-BE/assets/108216455/e8757df5-c505-4473-a0d9-4412e8cf656e" width="300"> |

## 👨‍👩‍👦 서버 팀원 소개
| Team Leader |Developer | Developer |
Expand All @@ -44,17 +55,17 @@

## 💻 기술스택
### 개발 환경
<img src="https://img.shields.io/badge/java_17 -007396?style=for-the-badge&logo=java&logoColor=white"></a>
<img src="https://img.shields.io/badge/springboot 3.1.0-6DB33F?style=for-the-badge&logo=springboot&logoColor=white"></a>
<img src="https://img.shields.io/badge/-Spring Data JPA-gray?style=for-the-badge&logoColor=white"/></a>
<img src="https://img.shields.io/badge/Query DSL-0078D4?style=for-the-badge&logo=Spring Data JPA&logoColor=white"/></a>
<img src="https://img.shields.io/badge/mysql 8.0-4479A1?style=for-the-badge&logo=mysql&logoColor=white"> </a>
<img src="https://img.shields.io/badge/Redis-DC382D?style=for-the-badge&logo=redis&logoColor=white"/></a>
<img src="https://img.shields.io/badge/java_17 -007396?style=for-the-badge&logo=java&logoColor=white"></a>
<img src="https://img.shields.io/badge/springboot 3.1.0-6DB33F?style=for-the-badge&logo=springboot&logoColor=white"></a>
<img src="https://img.shields.io/badge/-Spring Data JPA-gray?style=for-the-badge&logoColor=white"/></a>
<img src="https://img.shields.io/badge/Query DSL-0078D4?style=for-the-badge&logo=Spring Data JPA&logoColor=white"/></a>
<img src="https://img.shields.io/badge/mysql 8.0-4479A1?style=for-the-badge&logo=mysql&logoColor=white"> </a>
<img src="https://img.shields.io/badge/Redis-DC382D?style=for-the-badge&logo=redis&logoColor=white"/></a>
<img src="https://img.shields.io/badge/gradle-02303A?style=for-the-badge&logo=gradle&logoColor=white"></a>
<img src="https://img.shields.io/badge/Junit-25A162?style=for-the-badge&logo=JUnit5&logoColor=white"/></a>
<img src="https://img.shields.io/badge/Spring Security-6DB33F?style=for-the-badge&logo=spring-security&logoColor=white"/></a>
<img src="https://img.shields.io/badge/Spring Oauth2-000000?style=for-the-badge&logo=oauth2&logoColor=white"></a>
<img src="https://img.shields.io/badge/mapstruct-6DB33F?style=for-the-badge&logo=mapstruct&logoColor=white"></a>
<img src="https://img.shields.io/badge/Junit-25A162?style=for-the-badge&logo=JUnit5&logoColor=white"/></a>
<img src="https://img.shields.io/badge/Spring Security-6DB33F?style=for-the-badge&logo=spring-security&logoColor=white"/></a>
<img src="https://img.shields.io/badge/Spring Oauth2-000000?style=for-the-badge&logo=oauth2&logoColor=white"></a>
<img src="https://img.shields.io/badge/mapstruct-6DB33F?style=for-the-badge&logo=mapstruct&logoColor=white"></a>

### 인프라
<img src="https://img.shields.io/badge/amazon aws-232F3E?style=for-the-badge&logo=amazonaws&logoColor=white"></a>
Expand All @@ -80,7 +91,7 @@
![스크린샷 2023-12-03 오후 4 44 07](https://github.com/Team-TenTen/LinkHub-BE/assets/90172648/15b82f59-5f85-4567-996d-66652ac44aa0)



## 문서

[📁 LinkHub API 명세서 ](https://www.notion.so/prgrms/API-c9e7dd4d09b246999a0022273810e4f7?pvs=4) <br>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.tenten.linkhub.domain.space.common;

public record CursorPageMataData(
Long lastFavoriteCount,
Long lastId,
Integer pageSize,
Boolean hasNext
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.tenten.linkhub.domain.space.common;

import com.tenten.linkhub.domain.space.model.category.Category;
import org.springframework.data.domain.Sort;
import org.springframework.util.StringUtils;

public record SpaceCursorPageRequest(
int pageSize,
Sort sort,
Category filter
) {

public static SpaceCursorPageRequest of(
int pageSize,
String sort,
Category filter
) {
return new SpaceCursorPageRequest(
pageSize,
StringUtils.hasText(sort) ? Sort.by(sort) : Sort.unsorted(),
filter);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.tenten.linkhub.domain.space.common;

import lombok.Getter;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;

@Getter
public class SpaceCursorSlice<T> {
private final Long lastFavoriteCount;
private final Long lastId;
private final Integer pageSize;
private final Boolean hasNext;
private final List<T> content;

public SpaceCursorSlice(Long lastFavoriteCount, Long lastId, Integer pageSize, Boolean hasNext, List<T> content) {
this.lastFavoriteCount = lastFavoriteCount;
this.lastId = lastId;
this.pageSize = pageSize;
this.hasNext = hasNext;
this.content = new ArrayList<>(content);
}

public static <T> SpaceCursorSlice<T> of(Long lastFavoriteCount, Long lastId, Integer pageSize, Boolean hasNext, List<T> content) {
return new SpaceCursorSlice<>(
lastFavoriteCount,
lastId,
pageSize,
hasNext,
content
);
}

public <U> SpaceCursorSlice<U> map(Function<? super T, ? extends U> converter) {
List<U> newContent = content.stream()
.map(converter)
.collect(Collectors.toList());
return new SpaceCursorSlice<>(
lastFavoriteCount,
lastId,
pageSize,
hasNext,
newContent
);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.tenten.linkhub.domain.auth.MemberDetails;

import com.tenten.linkhub.domain.space.common.SpaceCursorPageRequest;
import com.tenten.linkhub.domain.space.controller.dto.comment.CommentUpdateApiRequest;
import com.tenten.linkhub.domain.space.controller.dto.comment.CommentUpdateApiResponse;
import com.tenten.linkhub.domain.space.controller.dto.comment.RepliesFindApiRequest;
Expand Down Expand Up @@ -48,10 +49,11 @@
import com.tenten.linkhub.domain.space.service.dto.comment.RootCommentCreateRequest;
import com.tenten.linkhub.domain.space.service.dto.favorite.FavoriteSpacesFindResponses;
import com.tenten.linkhub.domain.space.service.dto.favorite.SpaceRegisterInFavoriteResponse;
import com.tenten.linkhub.domain.space.service.dto.space.PublicSpacesFindByQueryRequest;
import com.tenten.linkhub.domain.space.service.dto.space.PublicSpacesFindWithFilterRequest;
import com.tenten.linkhub.domain.space.service.dto.space.SpacesFindByQueryResponses;
import com.tenten.linkhub.domain.space.service.dto.space.SpaceTagGetResponses;

import com.tenten.linkhub.domain.space.service.dto.space.SpacesFindWithCursorResponses;
import com.tenten.linkhub.global.response.ErrorResponse;
import com.tenten.linkhub.global.response.ErrorWithDetailCodeResponse;

Expand Down Expand Up @@ -129,7 +131,7 @@ public ResponseEntity<PublicSpacesFindByQueryApiResponses> findPublicSpacesByQue
request.pageSize(),
StringUtils.hasText(request.sort()) ? Sort.by(request.sort()) : Sort.unsorted());

SpacesFindByQueryResponses responses = spaceService.findPublicSpacesByQuery(
SpacesFindByQueryResponses responses = spaceService.searchPublicSpacesByQuery(
spaceMapper.toPublicSpacesFindByQueryRequest(request, pageRequest)
);

Expand Down Expand Up @@ -281,23 +283,24 @@ public ResponseEntity<Void> deleteSpace(
* 스페이스 필터 조회 API
*/
@Operation(
summary = "스페이스 필터 조회 API", description = "메인 페이지용 스페이스 필터 조회이며 pageNumber, pageSize, sort, filter를 받아 검색합니다. (sort, filter조건 없이 사용 가능합니다.)\n\n " +
"sort: {created_at, updated_at, favorite_count, view_count}\n\n " +
summary = "스페이스 필터 조회 API", description = "메인 페이지용 스페이스 필터 조회이며 lastSpaceId(정렬 조건 favorite_count인 경우 lastFavoriteCount 추가로 필요), pageSize, sort, filter를 받아 조회합니다. (sort, filter조건 없이 사용 가능합니다.)\n\n " +
"첫 페이지 조회의 경우 lastSpaceId 없이 요청하면 됩니다. (정렬 조건 favorite_count의 경우 lastSpaceId, lastFavoriteCount 둘다 없이 요청.)\n\n " +
"sort: {created_at, favorite_count}\n\n " +
"filter: {ENTER_ART, LIFE_KNOWHOW_SHOPPING, HOBBY_LEISURE_TRAVEL, KNOWLEDGE_ISSUE_CAREER, ETC}",
responses = {
@ApiResponse(responseCode = "200", description = "검색이 성공적으로 완료 되었습니다."),
@ApiResponse(responseCode = "200", description = "조회가 성공적으로 완료 되었습니다."),
})
@GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<PublicSpaceFindWithFilterApiResponses> findPublicSpacesWithFilter(
@ModelAttribute PublicSpacesFindWithFilterApiRequest request
) {
PageRequest pageRequest = PageRequest.of(
request.pageNumber(),
SpaceCursorPageRequest pageRequest = SpaceCursorPageRequest.of(
request.pageSize(),
StringUtils.hasText(request.sort()) ? Sort.by(request.sort()) : Sort.unsorted());
request.sort(),
request.filter());

PublicSpacesFindByQueryRequest serviceRequest = spaceMapper.toPublicSpacesFindByQueryRequest(request, pageRequest);
SpacesFindByQueryResponses responses = spaceService.findPublicSpacesByQuery(serviceRequest);
PublicSpacesFindWithFilterRequest serviceRequest = spaceMapper.toPublicSpacesFindWithFilterRequest(request, pageRequest);
SpacesFindWithCursorResponses responses = spaceService.findPublicSpacesWithFilter(serviceRequest);

PublicSpaceFindWithFilterApiResponses apiResponses = PublicSpaceFindWithFilterApiResponses.from(responses);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
package com.tenten.linkhub.domain.space.controller.dto.space;

import com.tenten.linkhub.domain.space.service.dto.space.SpacesFindByQueryResponses;
import com.tenten.linkhub.global.util.PageMetaData;
import org.springframework.data.domain.Slice;
import com.tenten.linkhub.domain.space.common.CursorPageMataData;
import com.tenten.linkhub.domain.space.common.SpaceCursorSlice;
import com.tenten.linkhub.domain.space.service.dto.space.SpacesFindWithCursorResponses;

import java.util.List;

public record PublicSpaceFindWithFilterApiResponses(
List<PublicSpaceFindWithFilterApiResponse> responses,
PageMetaData metaData
CursorPageMataData metaData
) {
public static PublicSpaceFindWithFilterApiResponses from(SpacesFindByQueryResponses responses) {
Slice<PublicSpaceFindWithFilterApiResponse> mapResponses = responses.responses()
public static PublicSpaceFindWithFilterApiResponses from(SpacesFindWithCursorResponses responses) {
SpaceCursorSlice<PublicSpaceFindWithFilterApiResponse> mapResponses = responses.responses()
.map(r -> new PublicSpaceFindWithFilterApiResponse(
r.spaceId(),
r.spaceName(),
Expand All @@ -23,13 +23,15 @@ public static PublicSpaceFindWithFilterApiResponses from(SpacesFindByQueryRespon
r.spaceImagePath(),
r.ownerNickName()));

PageMetaData pageMetaData = new PageMetaData(
mapResponses.hasNext(),
mapResponses.getSize(),
mapResponses.getNumber());
CursorPageMataData cursorPageMataData = new CursorPageMataData(
mapResponses.getLastFavoriteCount(),
mapResponses.getLastId(),
mapResponses.getPageSize(),
mapResponses.getHasNext()
);

return new PublicSpaceFindWithFilterApiResponses(
mapResponses.getContent(),
pageMetaData);
cursorPageMataData);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@
import io.swagger.v3.oas.annotations.media.Schema;

public record PublicSpacesFindWithFilterApiRequest(
@Schema(title = "페이지 번호", example = "0")
Integer pageNumber,
@Schema(title = "마지막 favoriteCount", example = "0")
Long lastFavoriteCount,

@Schema(title = "마지막 spaceId", example = "62")
Long lastSpaceId,

@Schema(title = "페이지 크기", example = "10")
Integer pageSize,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.tenten.linkhub.domain.space.controller.mapper;

import com.tenten.linkhub.domain.space.common.SpaceCursorPageRequest;
import com.tenten.linkhub.domain.space.controller.dto.space.NewSpacesScrapApiRequest;
import com.tenten.linkhub.domain.space.controller.dto.space.PublicSpacesFindWithFilterApiRequest;
import com.tenten.linkhub.domain.space.controller.dto.space.SpaceCreateApiRequest;
Expand All @@ -11,6 +12,7 @@
import com.tenten.linkhub.domain.space.facade.dto.SpaceCreateFacadeRequest;
import com.tenten.linkhub.domain.space.facade.dto.SpaceDetailGetByIdFacadeRequest;
import com.tenten.linkhub.domain.space.facade.dto.SpaceUpdateFacadeRequest;
import com.tenten.linkhub.domain.space.service.dto.space.PublicSpacesFindWithFilterRequest;
import com.tenten.linkhub.domain.space.service.dto.space.SpaceTagGetResponses;
import com.tenten.linkhub.domain.space.service.dto.space.PublicSpacesFindByQueryRequest;
import com.tenten.linkhub.domain.space.service.dto.spacemember.SpaceMemberRoleChangeRequest;
Expand All @@ -28,7 +30,7 @@ public interface SpaceApiMapper {

PublicSpacesFindByQueryRequest toPublicSpacesFindByQueryRequest(PublicSpacesFindByQueryApiRequest request, Pageable pageable);

PublicSpacesFindByQueryRequest toPublicSpacesFindByQueryRequest(PublicSpacesFindWithFilterApiRequest request, Pageable pageable);
PublicSpacesFindWithFilterRequest toPublicSpacesFindWithFilterRequest(PublicSpacesFindWithFilterApiRequest request, SpaceCursorPageRequest pageable);

SpaceCreateFacadeRequest toSpaceCreateFacadeRequest(SpaceCreateApiRequest request, MultipartFile file, Long memberId);

Expand Down

This file was deleted.

Loading

0 comments on commit 7cb1816

Please sign in to comment.