Skip to content

Commit

Permalink
Merge branch 'readme' of https://github.com/Kernel360/F2-BAGUNI into …
Browse files Browse the repository at this point in the history
…fe-develop
  • Loading branch information
dmdgpdi committed Jan 9, 2025
2 parents ee4965c + f3e2317 commit 125e03e
Show file tree
Hide file tree
Showing 150 changed files with 4,849 additions and 541 deletions.
1 change: 1 addition & 0 deletions backend/.env.sample
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
none

7 changes: 6 additions & 1 deletion backend/baguni-api/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,16 @@ repositories {

dependencies {
implementation project(":baguni-common")
implementation project(":baguni-entity")
implementation project(":baguni-domain")

// package for [@Transactional] + [@Aspect] + [Slice<T> from PickSliceResponse.java]
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'


// rabbitMQ
implementation 'org.springframework.boot:spring-boot-starter-amqp'
implementation 'org.springframework.amqp:spring-amqp:3.2.0'

// Querydsl for [pickQuery.java]
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;


import baguni.common.event.events.LinkEvent;
import baguni.common.event.messenger.CrawlingEventMessenger;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
Expand All @@ -29,17 +32,17 @@
import lombok.RequiredArgsConstructor;
import baguni.api.service.chromebookmark.dto.ChromeImportResult;
import baguni.api.service.chromebookmark.service.ChromeBookmarkService;
import baguni.api.service.folder.dto.FolderCommand;
import baguni.api.service.link.service.LinkService;
import baguni.domain.infrastructure.folder.dto.FolderCommand;
import baguni.security.annotation.LoginUserId;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/chrome")
@Tag(name = "Chrome API", description = "Chrome Bookmark Import / Export API")
public class ChromeBookmarkController {

private final ChromeBookmarkService chromeBookmarkService;
private final LinkService linkService;
private final CrawlingEventMessenger crawlingEventMessenger;

@GetMapping("/{folderId}/export")
@Operation(summary = "특정 폴더 다운로드", description = "사용자의 특정 폴더를 크롬 브라우저 북마크에 import 가능한 형태로 다운로드 받습니다.")
Expand Down Expand Up @@ -101,7 +104,7 @@ public ResponseEntity<List<String>> importBookmark(@LoginUserId Long userId, @Re
int maxThreadPoolSize = 5;
ExecutorService executor = Executors.newFixedThreadPool(maxThreadPoolSize);
for (String url : result.ogTagUpdateUrls()) {
CompletableFuture.runAsync(() -> linkService.updateOgTag(url), executor)
CompletableFuture.runAsync(() -> crawlingEventMessenger.send(new LinkEvent(url)), executor)
.orTimeout(60, TimeUnit.SECONDS);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import lombok.RequiredArgsConstructor;
import baguni.api.application.event.dto.EventApiRequest;
import baguni.api.service.link.service.LinkService;
import baguni.common.event.EventMessenger;
import baguni.common.event.messenger.EventMessenger;
import baguni.common.event.events.PickViewEvent;
import baguni.common.event.events.SharedFolderLinkViewEvent;
import baguni.common.event.events.SuggestionViewEvent;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
import baguni.api.application.folder.dto.FolderApiMapper;
import baguni.api.application.folder.dto.FolderApiRequest;
import baguni.api.application.folder.dto.FolderApiResponse;
import baguni.api.service.folder.dto.FolderResult;
import baguni.domain.infrastructure.folder.dto.FolderResult;
import baguni.api.service.folder.service.FolderService;
import baguni.api.service.sharedFolder.service.SharedFolderService;
import baguni.security.annotation.LoginUserId;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
import org.mapstruct.Mapping;
import org.mapstruct.ReportingPolicy;

import baguni.api.service.folder.dto.FolderCommand;
import baguni.api.service.folder.dto.FolderResult;
import baguni.domain.infrastructure.folder.dto.FolderCommand;
import baguni.domain.infrastructure.folder.dto.FolderResult;


@Mapper(
componentModel = "spring",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
import java.util.List;

import io.swagger.v3.oas.annotations.media.Schema;
import baguni.entity.model.folder.FolderType;

import baguni.domain.model.folder.FolderType;


public record FolderApiResponse(
Long id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.springframework.web.bind.annotation.RestController;

import baguni.common.annotation.MeasureTime;
import baguni.domain.infrastructure.link.dto.LinkInfo;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
Expand All @@ -28,17 +29,17 @@ public class LinkApiController {

@MeasureTime
@GetMapping
@Operation(summary = "해당 링크 og 데이터 조회", description = "해당 링크의 og 태그 데이터를 스크래핑을 통해 가져옵니다.")
@Operation(summary = "링크 정보 조회", description = "해당 링크의 데이터를 DB에서 가져옵니다. 해당 메서드에서 더 이상 스크래핑하지 않습니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "조회 성공")
})

public ResponseEntity<LinkApiResponse> getLinkData(
@Parameter(description = "og 태그 데이터 가져올 url") @RequestParam String url
) {
var result = linkService.saveLinkAndUpdateOgTag(url);
var response = linkApiMapper.toLinkResponse(result);

// Selenium 사용하도록 변경
LinkInfo linkInfo = linkService.getLinkInfo(url);
var response = linkApiMapper.toLinkResponse(linkInfo);
return ResponseEntity.ok(response);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import org.mapstruct.Mapper;
import org.mapstruct.ReportingPolicy;

import baguni.api.service.link.dto.LinkInfo;
import baguni.domain.infrastructure.link.dto.LinkInfo;

@Mapper(
componentModel = "spring",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import baguni.api.service.link.dto.LinkInfo;
import baguni.api.service.link.service.LinkService;
import baguni.common.annotation.MeasureTime;
import baguni.common.event.events.CrawlingEvent;
import baguni.common.event.messenger.CrawlingEventMessenger;
import baguni.common.event.messenger.RankingEventMessenger;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
Expand All @@ -30,12 +31,10 @@
import baguni.api.application.pick.dto.PickApiRequest;
import baguni.api.application.pick.dto.PickApiResponse;
import baguni.api.application.pick.dto.PickSliceResponse;
import baguni.api.service.pick.dto.PickResult;
import baguni.api.service.pick.exception.ApiPickException;
import baguni.api.service.pick.service.PickBulkService;
import baguni.domain.infrastructure.pick.dto.PickResult;
import baguni.domain.exception.pick.ApiPickException;
import baguni.api.service.pick.service.PickSearchService;
import baguni.api.service.pick.service.PickService;
import baguni.common.event.EventMessenger;
import baguni.common.event.events.PickCreateEvent;
import baguni.security.annotation.LoginUserId;

Expand All @@ -49,9 +48,8 @@ public class PickApiController {
private final PickService pickService;
private final PickApiMapper pickApiMapper;
private final PickSearchService pickSearchService;
private final PickBulkService pickBulkService;
private final LinkService linkService;
private final EventMessenger eventMessenger;
private final RankingEventMessenger rankingEventMessenger;
private final CrawlingEventMessenger crawlingEventMessenger;

@GetMapping
@Operation(summary = "폴더 리스트 내 픽 리스트 조회", description = "해당 폴더 리스트 각각의 픽 리스트를 조회합니다.")
Expand All @@ -60,8 +58,10 @@ public class PickApiController {
})
public ResponseEntity<List<PickApiResponse.FolderPickListWithViewCount>> getFolderChildPickList(
@LoginUserId Long userId,
@Parameter(description = "조회할 폴더 ID 목록", example = "1, 2, 3") @RequestParam(required = false, defaultValue =
"") List<Long> folderIdList) {
@Parameter(description = "조회할 폴더 ID 목록", example = "1, 2, 3")
@RequestParam(required = false, defaultValue = "")
List<Long> folderIdList
) {
var folderPickList = pickService.getFolderListChildPickList(
pickApiMapper.toReadListCommand(userId, folderIdList)
);
Expand Down Expand Up @@ -170,29 +170,7 @@ public ResponseEntity<PickApiResponse.Pick> savePick(@LoginUserId Long userId,
var command = pickApiMapper.toCreateCommand(userId, request);
var result = pickService.saveNewPick(command);
var event = new PickCreateEvent(userId, result.id(), result.linkInfo().url());
eventMessenger.send(event);
var response = pickApiMapper.toApiResponse(result);
return ResponseEntity.ok(response);
}

@MeasureTime
@PostMapping("/unclassified")
@Operation(
summary = "미분류 폴더로 픽 생성",
description = "익스텐션에서 미분류로 바로 픽 생성합니다. 또한, 픽 생성 이벤트가 랭킹 서버에 집계됩니다."
)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "픽 생성 성공"),
@ApiResponse(responseCode = "404", description = "OG 태그 업데이트를 위한 크롤링 요청 실패")
})
public ResponseEntity<PickApiResponse.Pick> savePickAsUnclassified(@LoginUserId Long userId,
@Valid @RequestBody PickApiRequest.Extension request) {
// Jsoup으로 og 데이터를 가져옵니다.
LinkInfo linkInfo = linkService.getOgTag(request.url(), request.title());
var command = pickApiMapper.toExtensionCommand(userId, request.title(), linkInfo);
var result = pickService.saveExtensionPick(command);
var event = new PickCreateEvent(userId, result.id(), result.linkInfo().url());
eventMessenger.send(event);
rankingEventMessenger.send(event);
var response = pickApiMapper.toApiResponse(result);
return ResponseEntity.ok(response);
}
Expand All @@ -206,8 +184,10 @@ public ResponseEntity<PickApiResponse.Pick> savePickAsUnclassified(@LoginUserId
@ApiResponse(responseCode = "200", description = "픽 생성 성공"),
@ApiResponse(responseCode = "403", description = "접근할 수 없는 폴더")
})
public ResponseEntity<PickApiResponse.CreateFromRecommend> savePickFromRecommend(@LoginUserId Long userId,
@Valid @RequestBody PickApiRequest.Create request) {
public ResponseEntity<PickApiResponse.CreateFromRecommend> savePickFromRecommend(
@LoginUserId Long userId,
@Valid @RequestBody PickApiRequest.Create request
) {
boolean existPick;
PickResult.Pick result;
if (pickService.existPickByUrl(userId, request.linkInfo().url())) {
Expand All @@ -219,23 +199,69 @@ public ResponseEntity<PickApiResponse.CreateFromRecommend> savePickFromRecommend
result = pickService.saveNewPick(command);
}
var event = new PickCreateEvent(userId, result.id(), result.linkInfo().url());
eventMessenger.send(event);
rankingEventMessenger.send(event);
return ResponseEntity.ok(new PickApiResponse.CreateFromRecommend(existPick, result));
}

@PatchMapping
@Operation(summary = "픽 내용 수정", description = "픽 내용 수정 및 폴더 이동까지 지원합니다.")
@MeasureTime
@PostMapping("/extension")
@Operation(
summary = "[익스텐션 전용] 미분류 폴더로 픽 생성",
description = "익스텐션에서 미분류로 바로 픽 생성합니다. 또한, 픽 생성 이벤트가 랭킹 서버에 집계됩니다."
)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "픽 생성 성공"),
@ApiResponse(responseCode = "404", description = "OG 태그 업데이트를 위한 크롤링 요청 실패")
})
public ResponseEntity<PickApiResponse.Extension> savePickAsUnclassified(
@LoginUserId Long userId,
@Valid @RequestBody PickApiRequest.CreateFromExtension request
) {
var command = pickApiMapper.toExtensionCommand(userId, request.title(), request.url());
var result = pickService.savePickToUnclassified(command);
var rankingEvent = new PickCreateEvent(userId, result.id(), request.url());
var crawlingEvent = new CrawlingEvent(result.linkId(), request.url(), request.title());
rankingEventMessenger.send(rankingEvent);
crawlingEventMessenger.send(crawlingEvent);
var response = pickApiMapper.toApiExtensionResponse(result);
return ResponseEntity.ok(response);
}

// TODO: 다루는 도메인이 pick 외에 생길 경우 extension 컨트롤러로 빼기
@PatchMapping("/extension")
@Operation(summary = "[익스텐션 전용] 픽 수정", description = "픽 내용 수정 및 폴더 이동까지 지원합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "픽 내용 수정 성공")
})
public ResponseEntity<PickApiResponse.Pick> updatePick(@LoginUserId Long userId,
@Valid @RequestBody PickApiRequest.Update request) {
if (!Objects.isNull(request.title()) && 200 < request.title().length()) {
public ResponseEntity<PickApiResponse.Pick> updatePickFromChromeExtension(
@LoginUserId Long userId,
@Valid @RequestBody PickApiRequest.UpdateFromExtension request
) {
if (Objects.nonNull(request.title()) && (200 < request.title().length())) {
throw ApiPickException.PICK_TITLE_TOO_LONG();
}
var command = pickApiMapper.toUpdateCommand(userId, request);
var result = pickService.updatePick(command);
var response = pickApiMapper.toApiResponse(result);
return ResponseEntity.ok(response);
}

return ResponseEntity.ok(
pickApiMapper.toApiResponse(pickService.updatePick(pickApiMapper.toUpdateCommand(userId, request))));
@PatchMapping
@Operation(summary = "웹사이트에서 픽 내용만 수정 (폴더 이동 X)", description = "픽 내용 수정 및 폴더 이동까지 지원합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "픽 내용 수정 성공")
})
public ResponseEntity<PickApiResponse.Pick> updatePick(
@LoginUserId Long userId,
@Valid @RequestBody PickApiRequest.Update request
) {
if (Objects.nonNull(request.title()) && (200 < request.title().length())) {
throw ApiPickException.PICK_TITLE_TOO_LONG();
}
var command = pickApiMapper.toUpdateCommand(userId, request);
var result = pickService.updatePick(command);
var response = pickApiMapper.toApiResponse(result);
return ResponseEntity.ok(response);
}

@PatchMapping("/location")
Expand All @@ -244,9 +270,12 @@ public ResponseEntity<PickApiResponse.Pick> updatePick(@LoginUserId Long userId,
@ApiResponse(responseCode = "204", description = "픽 이동 성공"),
@ApiResponse(responseCode = "400", description = "폴더가 존재하지 않음.")
})
public ResponseEntity<Void> movePick(@LoginUserId Long userId,
@Valid @RequestBody PickApiRequest.Move request) {
pickService.movePick(pickApiMapper.toMoveCommand(userId, request));
public ResponseEntity<Void> movePick(
@LoginUserId Long userId,
@Valid @RequestBody PickApiRequest.Move request
) {
var command = pickApiMapper.toMoveCommand(userId, request);
pickService.movePick(command);
return ResponseEntity.noContent().build();
}

Expand All @@ -257,16 +286,12 @@ public ResponseEntity<Void> movePick(@LoginUserId Long userId,
@ApiResponse(responseCode = "406", description = "휴지통이 아닌 폴더에서 픽 삭제 불가"),
@ApiResponse(responseCode = "500", description = "미확인 서버 에러 혹은 존재하지 않는 픽 삭제")
})
public ResponseEntity<Void> deletePick(@LoginUserId Long userId,
@Valid @RequestBody PickApiRequest.Delete request) {
pickService.deletePick(pickApiMapper.toDeleteCommand(userId, request));
public ResponseEntity<Void> deletePick(
@LoginUserId Long userId,
@Valid @RequestBody PickApiRequest.Delete request
) {
var command = pickApiMapper.toDeleteCommand(userId, request);
pickService.deletePick(command);
return ResponseEntity.noContent().build();
}

@PostMapping("/bulk")
@Operation(summary = "픽 10000개 insert", description = "픽 10000개 insert - 1회만 가능합니다.")
public ResponseEntity<Void> bulkInsertPick(@LoginUserId Long userId, @RequestParam Long folderId) {
pickBulkService.saveBulkPick(userId, folderId);
return ResponseEntity.ok().build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.SliceImpl;

import baguni.api.service.link.dto.LinkInfo;
import baguni.api.service.pick.dto.PickCommand;
import baguni.api.service.pick.dto.PickResult;
import baguni.domain.infrastructure.pick.dto.PickCommand;
import baguni.domain.infrastructure.pick.dto.PickResult;

@Mapper(
componentModel = "spring",
Expand All @@ -30,9 +29,11 @@ PickCommand.SearchPagination toSearchPaginationCommand(Long userId, List<Long> f

PickCommand.Create toCreateCommand(Long userId, PickApiRequest.Create request);

@Mapping(source = "linkInfo", target = "linkInfo")
PickCommand.Extension toExtensionCommand(Long userId, String title, LinkInfo linkInfo);
PickCommand.Unclassified toExtensionCommand(Long userId, String title, String url);

PickCommand.Update toUpdateCommand(Long userId, PickApiRequest.UpdateFromExtension request);

@Mapping(target = "parentFolderId", ignore = true)
PickCommand.Update toUpdateCommand(Long userId, PickApiRequest.Update request);

PickCommand.Move toMoveCommand(Long userId, PickApiRequest.Move request);
Expand All @@ -43,10 +44,9 @@ PickCommand.SearchPagination toSearchPaginationCommand(Long userId, List<Long> f

PickApiResponse.Pick toApiResponse(PickResult.Pick pickResult);

PickApiResponse.Exist toApiExistResponse(Boolean exist);
PickApiResponse.Extension toApiExtensionResponse(PickResult.Extension pickResult);

@Mapping(target = "pickList", source = "pickList", qualifiedByName = "mapPickList")
PickApiResponse.FolderPickList toApiFolderPickList(PickResult.FolderPickList folderPickList);
PickApiResponse.Exist toApiExistResponse(Boolean exist);

@Named("mapPickList")
default List<PickApiResponse.Pick> mapPickList(List<PickResult.Pick> pickList) {
Expand Down
Loading

0 comments on commit 125e03e

Please sign in to comment.