Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ public enum ErrorCode {
// ๋งˆ์ธ๋“œ๋งต ๊ด€๋ จ
MINDMAP_NOT_FOUND(HttpStatus.NOT_FOUND, "MINDMAP-001", "์š”์ฒญํ•œ ๋งˆ์ธ๋“œ๋งต์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."),

// ๋ฐฉ๋ฌธ๊ธฐ๋ก ๊ด€๋ จ
HISTORY_NOT_FOUND(HttpStatus.NOT_FOUND, "VISITHISTORY-001", "๋ฐฉ๋ฌธ ๊ธฐ๋ก์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."),

// ๋ฐฉ๋ฌธ ๊ธฐ๋ก ํ•€ ๊ณ ์ • ๊ด€๋ จ
USER_NOT_FOUND_FIX_PIN(HttpStatus.NOT_FOUND, "PINNEDHISTORY-001", "ํ•€ ๊ณ ์ •ํ•œ ์œ ์ €๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."),
PINNEDHISTORY_ALREADY_EXISTS(HttpStatus.CONFLICT, "PINNEDHISTORY-002", "์ด๋ฏธ ํ•€ ๊ณ ์ •ํ•œ ๊ธฐ๋ก์ž…๋‹ˆ๋‹ค."),
PINNEDHISTORY_NOT_FOUND(HttpStatus.NOT_FOUND, "PINNEDHISTORY-003", "ํ•€ ๊ณ ์ • ๊ธฐ๋ก์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."),

// S3 ํŒŒ์ผ ๊ด€๋ จ
// Client Errors (4xx)
FILE_NOT_FOUND(HttpStatus.NOT_FOUND, "FILE-001", "์š”์ฒญํ•œ ํŒŒ์ผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@
import com.teamEWSN.gitdeun.mindmap.repository.MindmapRepository;
import com.teamEWSN.gitdeun.repo.entity.Repo;
import com.teamEWSN.gitdeun.repo.repository.RepoRepository;
import com.teamEWSN.gitdeun.repo.service.RepoService;
import com.teamEWSN.gitdeun.user.entity.User;
import com.teamEWSN.gitdeun.user.repository.UserRepository;
import com.teamEWSN.gitdeun.visithistory.service.VisitHistoryService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
Expand All @@ -29,7 +29,7 @@
@Service
@RequiredArgsConstructor
public class MindmapService {
private final RepoService repoService;
private final VisitHistoryService visitHistoryService;
private final MindmapMapper mindmapMapper;
private final MindmapRepository mindmapRepository;
private final RepoRepository repoRepository;
Expand Down Expand Up @@ -74,6 +74,9 @@ public MindmapResponseDto createMindmap(MindmapCreateRequest req, Long userId) {

mindmapRepository.save(mindmap);

// ๋ฐฉ๋ฌธ ๊ธฐ๋ก ์ƒ์„ฑ
visitHistoryService.createVisitHistory(user, mindmap);

return mindmapMapper.toResponseDto(mindmap);

}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.teamEWSN.gitdeun.visithistory.controller;

import com.teamEWSN.gitdeun.common.jwt.CustomUserDetails;
import com.teamEWSN.gitdeun.visithistory.service.PinnedHistoryService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;

@Slf4j
@RestController
@RequestMapping("/api/history/{historyId}/mindmaps/pinned")
@RequiredArgsConstructor
public class PinnedHistoryController {

private final PinnedHistoryService pinnedHistoryService;

// ํ•€ ๊ณ ์ •
@PostMapping
public ResponseEntity<Void> fixPinned(
@PathVariable("historyId") Long historyId,
@AuthenticationPrincipal CustomUserDetails customUserDetails
) {
pinnedHistoryService.fixPinned(historyId, customUserDetails.getId());
return ResponseEntity.status(HttpStatus.CREATED).build();
}

// ํ•€ ํ•ด์ œ
@DeleteMapping
public ResponseEntity<Void> removePinned(
@PathVariable("historyId") Long historyId,
@AuthenticationPrincipal CustomUserDetails customUserDetails
) {
pinnedHistoryService.removePinned(historyId, customUserDetails.getId());
return ResponseEntity.noContent().build();
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,50 @@
package com.teamEWSN.gitdeun.visithistory.controller;

import com.teamEWSN.gitdeun.common.jwt.CustomUserDetails;
import com.teamEWSN.gitdeun.visithistory.dto.VisitHistoryResponseDto;
import com.teamEWSN.gitdeun.visithistory.service.VisitHistoryService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@Slf4j
@RestController
@RequestMapping("/api/history")
@RequiredArgsConstructor
public class VisitHistoryController {


private final VisitHistoryService visitHistoryService;

// ํ•€ ๊ณ ์ •๋˜์ง€ ์•Š์€ ๋ฐฉ๋ฌธ ๊ธฐ๋ก ์กฐํšŒ
@GetMapping("/visits")
public ResponseEntity<List<VisitHistoryResponseDto>> getVisitHistories(
@AuthenticationPrincipal CustomUserDetails userDetails
) {
List<VisitHistoryResponseDto> histories = visitHistoryService.getVisitHistories(userDetails.getId());
return ResponseEntity.ok(histories);
}

// ํ•€ ๊ณ ์ •๋œ ๋ฐฉ๋ฌธ ๊ธฐ๋ก ์กฐํšŒ
@GetMapping("/pins")
public ResponseEntity<List<VisitHistoryResponseDto>> getPinnedHistories(
@AuthenticationPrincipal CustomUserDetails userDetails
) {
List<VisitHistoryResponseDto> histories = visitHistoryService.getPinnedHistories(userDetails.getId());
return ResponseEntity.ok(histories);
}

// ๋ฐฉ๋ฌธ ๊ธฐ๋ก ์‚ญ์ œ
@DeleteMapping("/visits/{historyId}")
public ResponseEntity<Void> deleteVisitHistory(
@PathVariable Long historyId,
@AuthenticationPrincipal CustomUserDetails userDetails
) {
visitHistoryService.deleteVisitHistory(historyId, userDetails.getId());
return ResponseEntity.ok().build();
}

}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.teamEWSN.gitdeun.visithistory.dto;

import lombok.Builder;
import lombok.Getter;

import java.time.LocalDateTime;

@Getter
@Builder
public class VisitHistoryResponseDto {
private Long visitHistoryId;
private Long mindmapId;
private String mindmapField; // ๋งˆ์ธ๋“œ๋งต ์ œ๋ชฉ
private String repoUrl;
private LocalDateTime lastVisitedAt;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,21 @@

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.teamEWSN.gitdeun.common.util.CreatedEntity;
import com.teamEWSN.gitdeun.mindmap.entity.Mindmap;
import com.teamEWSN.gitdeun.user.entity.User;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;


@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = "pinned_history")
@Table(name = "pinned_history", indexes = {
@Index(name = "idx_pinnedHistory_user_visit_history", columnList = "user_id, visit_history_id", unique = true), // ์ฃผ์š” ์กฐํšŒ ์กฐ๊ฑด ๋ฐ ์ค‘๋ณต ๋ฐฉ์ง€
@Index(name = "idx_pinnedHistory_visit_history_id", columnList = "visit_history_id") // ๋ฐฉ๋ฌธ ๊ธฐ๋ก ๊ธฐ์ค€ ํ•€ ๊ณ ์ • ๋ชฉ๋ก ์กฐํšŒ
})
public class PinnedHistory extends CreatedEntity {

@Id
Expand All @@ -28,4 +31,10 @@ public class PinnedHistory extends CreatedEntity {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "visit_history_id", nullable = false)
private VisitHistory visitHistory;

@Builder
public PinnedHistory(User user, VisitHistory visitHistory) {
this.user = user;
this.visitHistory = visitHistory;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.teamEWSN.gitdeun.visithistory.mapper;

import com.teamEWSN.gitdeun.visithistory.dto.VisitHistoryResponseDto;
import com.teamEWSN.gitdeun.visithistory.entity.VisitHistory;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.ReportingPolicy;

@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface VisitHistoryMapper {

@Mapping(source = "id", target = "visitHistoryId")
@Mapping(source = "mindmap.id", target = "mindmapId")
@Mapping(source = "mindmap.field", target = "mindmapField")
@Mapping(source = "mindmap.repo.githubRepoUrl", target = "repoUrl")
VisitHistoryResponseDto toResponseDto(VisitHistory visitHistory);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.teamEWSN.gitdeun.visithistory.repository;

import com.teamEWSN.gitdeun.user.entity.User;
import com.teamEWSN.gitdeun.visithistory.entity.PinnedHistory;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

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

@Repository
public interface PinnedHistoryRepository extends JpaRepository<PinnedHistory, Long> {

boolean existsByUserIdAndVisitHistoryId(Long userId, Long historyId);

Optional<PinnedHistory> findByUserIdAndVisitHistoryId(Long userId, Long historyId);

// ์‚ฌ์šฉ์ž์˜ ํ•€ ๊ณ ์ • ๊ธฐ๋ก ์ตœ์‹ ์ˆœ ์กฐํšŒ
List<PinnedHistory> findByUserOrderByCreatedAtDesc(User user);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
package com.teamEWSN.gitdeun.visithistory.repository;

public class VisitHistoryRepository {

import com.teamEWSN.gitdeun.user.entity.User;
import com.teamEWSN.gitdeun.visithistory.entity.VisitHistory;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface VisitHistoryRepository extends JpaRepository<VisitHistory, Long> {

// ์‚ฌ์šฉ์ž์˜ ํ•€ ๊ณ ์ •๋˜์ง€ ์•Š์€ ๋ฐฉ๋ฌธ ๊ธฐ๋ก์„ ์ตœ์‹ ์ˆœ์œผ๋กœ ์กฐํšŒ
@Query("SELECT v FROM VisitHistory v LEFT JOIN v.pinnedHistorys p " +
"WHERE v.user = :user AND p IS NULL " +
"ORDER BY v.lastVisitedAt DESC")
List<VisitHistory> findUnpinnedHistoriesByUser(@Param("user") User user);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.teamEWSN.gitdeun.visithistory.service;

import com.teamEWSN.gitdeun.common.exception.GlobalException;
import com.teamEWSN.gitdeun.user.entity.User;
import com.teamEWSN.gitdeun.user.repository.UserRepository;
import com.teamEWSN.gitdeun.visithistory.entity.PinnedHistory;
import com.teamEWSN.gitdeun.visithistory.entity.VisitHistory;
import com.teamEWSN.gitdeun.visithistory.repository.PinnedHistoryRepository;
import com.teamEWSN.gitdeun.visithistory.repository.VisitHistoryRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import static com.teamEWSN.gitdeun.common.exception.ErrorCode.*;


@Slf4j
@Service
@Transactional
@RequiredArgsConstructor
public class PinnedHistoryService {

private final PinnedHistoryRepository pinnedHistoryRepository;
private final UserRepository userRepository;
private final VisitHistoryRepository visitHistoryRepository;

public void fixPinned(Long historyId, Long userId) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new GlobalException(USER_NOT_FOUND_FIX_PIN));

VisitHistory visitHistory = visitHistoryRepository.findById(historyId)
.orElseThrow(() -> new GlobalException(HISTORY_NOT_FOUND));

// ์ด๋ฏธ ํ•€ ๊ณ ์ •์ด ์žˆ๋Š”์ง€ ํ™•์ธ
if (pinnedHistoryRepository.existsByUserIdAndVisitHistoryId(userId, historyId)) {
throw new GlobalException(PINNEDHISTORY_ALREADY_EXISTS);
}

PinnedHistory pin = PinnedHistory.builder()
.user(user)
.visitHistory(visitHistory)
.build();

pinnedHistoryRepository.save(pin);

}

@Transactional
public void removePinned(Long historyId, Long userId) {
PinnedHistory pin = pinnedHistoryRepository.findByUserIdAndVisitHistoryId(userId, historyId)
.orElseThrow(() -> new GlobalException(PINNEDHISTORY_NOT_FOUND));

pinnedHistoryRepository.delete(pin);

}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,76 @@
package com.teamEWSN.gitdeun.visithistory.service;

import com.teamEWSN.gitdeun.common.exception.ErrorCode;
import com.teamEWSN.gitdeun.common.exception.GlobalException;
import com.teamEWSN.gitdeun.mindmap.entity.Mindmap;
import com.teamEWSN.gitdeun.user.entity.User;
import com.teamEWSN.gitdeun.user.service.UserService;
import com.teamEWSN.gitdeun.visithistory.dto.VisitHistoryResponseDto;
import com.teamEWSN.gitdeun.visithistory.entity.PinnedHistory;
import com.teamEWSN.gitdeun.visithistory.entity.VisitHistory;
import com.teamEWSN.gitdeun.visithistory.mapper.VisitHistoryMapper;
import com.teamEWSN.gitdeun.visithistory.repository.PinnedHistoryRepository;
import com.teamEWSN.gitdeun.visithistory.repository.VisitHistoryRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
public class VisitHistoryService {


private final UserService userService;
private final VisitHistoryRepository visitHistoryRepository;
private final PinnedHistoryRepository pinnedHistoryRepository;
private final VisitHistoryMapper visitHistoryMapper;

// ๋งˆ์ธ๋“œ๋งต ์ƒ์„ฑ ์‹œ ํ˜ธ์ถœ๋˜์–ด ๋ฐฉ๋ฌธ ๊ธฐ๋ก์„ ์ƒ์„ฑ
@Transactional
public void createVisitHistory(User user, Mindmap mindmap) {
VisitHistory visitHistory = VisitHistory.builder()
.user(user)
.mindmap(mindmap)
.lastVisitedAt(LocalDateTime.now())
.build();
visitHistoryRepository.save(visitHistory);
}

// ํ•€ ๊ณ ์ •๋˜์ง€ ์•Š์€ ๋ฐฉ๋ฌธ ๊ธฐ๋ก ์กฐํšŒ
@Transactional(readOnly = true)
public List<VisitHistoryResponseDto> getVisitHistories(Long userId) {
User user = userService.findById(userId);
List<VisitHistory> histories = visitHistoryRepository.findUnpinnedHistoriesByUser(user);
return histories.stream()
.map(visitHistoryMapper::toResponseDto)
.collect(Collectors.toList());
}

// ํ•€ ๊ณ ์ •๋œ ๋ฐฉ๋ฌธ ๊ธฐ๋ก ์กฐํšŒ
@Transactional(readOnly = true)
public List<VisitHistoryResponseDto> getPinnedHistories(Long userId) {
User user = userService.findById(userId);
List<PinnedHistory> pinnedHistories = pinnedHistoryRepository.findByUserOrderByCreatedAtDesc(user);
return pinnedHistories.stream()
.map(pinned -> visitHistoryMapper.toResponseDto(pinned.getVisitHistory()))
.collect(Collectors.toList());
}

// ๋ฐฉ๋ฌธ ๊ธฐ๋ก ์‚ญ์ œ
@Transactional
public void deleteVisitHistory(Long visitHistoryId, Long userId) {
VisitHistory visitHistory = visitHistoryRepository.findById(visitHistoryId)
.orElseThrow(() -> new GlobalException(ErrorCode.HISTORY_NOT_FOUND));

if (!visitHistory.getUser().getId().equals(userId)) {
throw new GlobalException(ErrorCode.FORBIDDEN_ACCESS);
}

visitHistoryRepository.delete(visitHistory);
}


}