Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
12 commits
Select commit Hold shift + click to select a range
c2c6485
refactor/#264: ๋ถ„์œ„๊ธฐ ํŠธ๋ž˜์ปค ์—๋Ÿฌ ๋ฐ ํ”„๋กฌํ”„ํŠธ ๋ฆฌํŒฉํ† ๋ง
2ghrms Aug 19, 2025
b9ef3ef
refactor/#264: ๋ถ„์œ„๊ธฐ ํŠธ๋ž˜์ปค ์—๋Ÿฌ ๋ฐ ํ”„๋กฌํ”„ํŠธ ๋ฆฌํŒฉํ† ๋ง
2ghrms Aug 19, 2025
a952e75
refactor/#264: ๋ถ„์œ„๊ธฐ ํŠธ๋ž˜์ปค ์—๋Ÿฌ ๋ฐ ํ”„๋กฌํ”„ํŠธ ๋ฆฌํŒฉํ† ๋ง
2ghrms Aug 19, 2025
d0beb1b
refactor/#264: ๋ถ„์œ„๊ธฐ ํŠธ๋ž˜์ปค ์—๋Ÿฌ ๋ฐ ํ”„๋กฌํ”„ํŠธ ๋ฆฌํŒฉํ† ๋ง
2ghrms Aug 19, 2025
e415771
feat/#236: ๋ถ„์œ„๊ธฐ ํŠธ๋ž˜์ปค ์ˆ˜์ • & ์‚ญ์ œ์‹œ์— ๋ฆฌํฌํŠธ ํŒŒ์ผ ๋ฐ ์ธ๋„ค์ผ ์ˆ˜์ • & ์‚ญ์ œ
2ghrms Aug 19, 2025
7f4e1b6
Merge pull request #296 from HaRu-Developers/feat/#236-mood-tracker-sync
2ghrms Aug 19, 2025
c9f84ed
feat/#297: ๋ถ„์œ„๊ธฐ ํŠธ๋ž˜์ปค ๋ฒ ์ด์Šค ์ •๋ณด ์กฐํšŒ API
2ghrms Aug 19, 2025
1aa717c
Merge pull request #298 from HaRu-Developers/feat/#297-mood-tracker-pโ€ฆ
2ghrms Aug 19, 2025
6e41236
fix/#292: ํšŒ์›๊ฐ€์ž… ์‹œ user document ์ถ”๊ฐ€ ์•ˆ๋˜๋Š” ์˜ค๋ฅ˜ ์ˆ˜์ •
hknhj Aug 19, 2025
9a544f8
Merge pull request #299 from HaRu-Developers/fix/#292-register-after-โ€ฆ
hknhj Aug 19, 2025
58573e8
feat/#285: SNS ์ด๋ฒคํŠธ ์ œ๋ชฉ ์ˆ˜์ • ์‹œ S3์˜ ๋‹ค์šด๋กœ๋“œ ๋ฌธ์„œ ์ œ๋ชฉ ๋ฐ ๋ฌธ์„œ๋‚ด ์ œ๋ชฉ ์ˆ˜์ •, S3์˜ ์ธ๋„ค์ผ ์ด๋ฏธ์ง€ ์ˆ˜์ •
Jinho622 Aug 19, 2025
f32d701
Merge pull request #300 from HaRu-Developers/feat/#285-sns-event-updaโ€ฆ
Jinho622 Aug 19, 2025
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 @@ -48,7 +48,7 @@ public ApiResponse<MoodTrackerResponseDTO.PreviewList> getMoodTrackerPreviewList
@Parameter(hidden = true) @AuthWorkspace Workspace workspace
) {

MoodTrackerResponseDTO.PreviewList result = moodTrackerQueryService.getMoodTrackerPreviewList(user, workspace);
MoodTrackerResponseDTO.PreviewList result = moodTrackerQueryService.getPreviewList(user, workspace);

return ApiResponse.onSuccess(result);

Expand Down Expand Up @@ -145,7 +145,7 @@ public ApiResponse<Void> sendMoodTrackerSurveyLink(
})
public ApiResponse<Void> submitMoodTrackerSurveyAnswers(
@PathVariable("mood-tracker-hashed-Id") String moodTrackerHashedId,
@RequestBody MoodTrackerRequestDTO.SurveyAnswerList request,
@Valid @RequestBody MoodTrackerRequestDTO.SurveyAnswerList request,
@Parameter(hidden = true) @AuthMoodTracker MoodTracker moodTracker
) {

Expand All @@ -155,6 +155,26 @@ public ApiResponse<Void> submitMoodTrackerSurveyAnswers(

}

@GetMapping("/{mood-tracker-hashed-Id}")
@Operation(
summary = "๋ถ„์œ„๊ธฐ ํŠธ๋ž˜์ปค ์„ค๋ฌธ ํŒ€๋ถ„์œ„๊ธฐ ๋ฒ ์ด์Šค ์ •๋ณด ์กฐํšŒ API",
description = "# [v1.0 (2025-08-19)](https://www.notion.so/2545da7802c580dd9742d971d3a4bc08?source=copy_link) ๋ถ„์œ„๊ธฐ ํŠธ๋ž˜์ปค(moodTrackerId)์— ๋Œ€ํ•œ ๋ฒ ์ด์Šค ์ •๋ณด๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค."
)
@Parameters({
@Parameter(name = "mood-tracker-hashed-Id", description = "๋ถ„์œ„๊ธฐ ํŠธ๋ž˜์ปค ID (Hashed, Path Variable)", required = true)
})
public ApiResponse<MoodTrackerResponseDTO.BaseResult> getMoodTrackerBaseResult(
@PathVariable(name = "mood-tracker-hashed-Id") String moodTrackerHashedId,
@Parameter(hidden = true) @AuthUser User user,
@Parameter(hidden = true) @AuthMoodTracker MoodTracker moodTracker
) {

MoodTrackerResponseDTO.BaseResult result = moodTrackerQueryService.getBaseResult(user, moodTracker);

return ApiResponse.onSuccess(result);

}

@GetMapping("/{mood-tracker-hashed-Id}/questions")
@Operation(
summary = "๋ถ„์œ„๊ธฐ ํŠธ๋ž˜์ปค ์„ค๋ฌธ ๋ฌธํ•ญ ์กฐํšŒ API",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,23 @@ public static List<CheckboxChoiceAnswer> toCheckboxChoiceAnswerList(
return answers;
}

/**
* ๋ถ„์œ„๊ธฐ ํŠธ๋ž˜์ปค ์„ค๋ฌธ Base ์ •๋ณด ๋ณ€ํ™˜
*/
public static MoodTrackerResponseDTO.BaseResult toBaseResultDTO(MoodTracker moodTracker, HashIdUtil hashIdUtil) {
return MoodTrackerResponseDTO.BaseResult.builder()
.workspaceId(moodTracker.getWorkspace().getId())
.moodTrackerHashedId(hashIdUtil.encode(moodTracker.getId()))
.title(moodTracker.getTitle())
.creatorId(moodTracker.getCreator().getId())
.creatorName(moodTracker.getCreator().getName())
.updatedAt(moodTracker.getUpdatedAt())
.dueDate(moodTracker.getDueDate())
.respondentsNum(moodTracker.getRespondentsNum())
.build();
}


/**
* ๋ถ„์œ„๊ธฐ ํŠธ๋ž˜์ปค ๋ฆฌํฌํŠธ DTO ๋ณ€ํ™˜
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@
import com.haru.api.global.util.json.ToLongListDeserializer;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.*;
import lombok.*;
import org.springframework.validation.annotation.Validated;

Expand All @@ -31,6 +29,7 @@ public static class CreateRequest {
private String description;

@NotNull
@Future
private LocalDateTime dueDate;

@NotNull
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface CheckboxChoiceRepository extends JpaRepository<CheckboxChoice, Long> {
// ํŠน์ • ์งˆ๋ฌธ(questionId)์— ์†ํ•œ ์—ฌ๋Ÿฌ ์„ ํƒ์ง€(ids) ์กฐํšŒ
List<CheckboxChoice> findAllByIdInAndSurveyQuestionId(List<Long> ids, Long questionId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.Optional;

@Repository
public interface MultipleChoiceRepository extends JpaRepository<MultipleChoice, Long> {
// ํŠน์ • ์งˆ๋ฌธ(questionId)์— ์†ํ•œ ํŠน์ • ์„ ํƒ์ง€(id)๋งŒ ์กฐํšŒ
Optional<MultipleChoice> findByIdAndSurveyQuestionId(Long id, Long questionId);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.haru.api.domain.moodTracker.service;

import com.haru.api.domain.lastOpened.repository.UserDocumentLastOpenedRepository;
import com.haru.api.domain.lastOpened.service.UserDocumentLastOpenedService;
import com.haru.api.domain.moodTracker.converter.MoodTrackerConverter;
import com.haru.api.domain.moodTracker.dto.MoodTrackerRequestDTO;
Expand Down Expand Up @@ -108,7 +107,6 @@ public void updateTitle(
MoodTracker moodTracker,
MoodTrackerRequestDTO.UpdateTitleRequest request
) {

UserWorkspace foundUserWorkspace = userWorkspaceRepository.findByWorkspaceIdAndUserId(moodTracker.getWorkspace().getId(), user.getId())
.orElseThrow(() -> new UserWorkspaceHandler(ErrorStatus.USER_WORKSPACE_NOT_FOUND));

Expand All @@ -117,8 +115,17 @@ public void updateTitle(
|| moodTracker.getCreator().getId().equals(user.getId())))
throw new MoodTrackerHandler(ErrorStatus.MOOD_TRACKER_MODIFY_NOT_ALLOWED);

// ์—”ํ‹ฐํ‹ฐ ์—…๋ฐ์ดํŠธ
moodTracker.updateTitle(request.getTitle());

moodTrackerRepository.save(moodTracker);

// ๋งˆ๊ฐ์ผ ์ดํ›„ && ์ธ๋„ค์ผ์ด ์ƒ์„ฑ๋œ ์‹œ์ ์ด๋ผ๋ฉด,
if(moodTracker.getDueDate().isBefore(LocalDateTime.now()) && moodTracker.getThumbnailKey()!=null) {
// ๊ธฐ์กด ์ธ๋„ค์ผ ๋ฐ ๋‹ค์šด๋กœ๋“œ ํŒŒ์ผ ์‚ญ์ œ
moodTrackerReportService.deleteReportFileAndThumbnail(moodTracker.getId());
// S3์—์„œ ์ธ๋„ค์ผ ๋ฐ ๋‹ค์šด๋กœ๋“œ ํŒŒ์ผ ์—…๋ฐ์ดํŠธ
moodTrackerReportService.updateAndUploadReportFileAndThumbnail(moodTracker.getId());
}
}

/**
Expand All @@ -131,7 +138,6 @@ public void delete(
User user,
MoodTracker moodTracker
) {

UserWorkspace foundUserWorkspace = userWorkspaceRepository.findByWorkspaceIdAndUserId(moodTracker.getWorkspace().getId(), user.getId())
.orElseThrow(() -> new UserWorkspaceHandler(ErrorStatus.USER_WORKSPACE_NOT_FOUND));

Expand All @@ -140,8 +146,14 @@ public void delete(
|| moodTracker.getCreator().getId().equals(user.getId())))
throw new MoodTrackerHandler(ErrorStatus.MOOD_TRACKER_MODIFY_NOT_ALLOWED);

moodTrackerRepository.delete(moodTracker);
// ๋งˆ๊ฐ์ผ ์ดํ›„ && ์ธ๋„ค์ผ์ด ์ƒ์„ฑ๋œ ์‹œ์ ์ด๋ผ๋ฉด,
if(moodTracker.getDueDate().isBefore(LocalDateTime.now()) && moodTracker.getThumbnailKey()!=null) {
// S3์—์„œ ์ธ๋„ค์ผ ๋ฐ ๋‹ค์šด๋กœ๋“œ ํŒŒ์ผ ์‚ญ์ œ
moodTrackerReportService.deleteReportFileAndThumbnail(moodTracker.getId());
}

// ์—”ํ‹ฐํ‹ฐ ์‚ญ์ œ
moodTrackerRepository.delete(moodTracker);
}

/**
Expand Down Expand Up @@ -171,6 +183,11 @@ public void submitSurveyAnswers(
MoodTracker moodTracker,
MoodTrackerRequestDTO.SurveyAnswerList request
) {
// ๋งˆ๊ฐ์ผ ์ดํ›„์ด๋ฉด ๋‹ต๋ณ€ ๋ถˆ๊ฐ€๋Šฅ
if(moodTracker.getDueDate().isBefore(LocalDateTime.now())){
throw new MoodTrackerHandler(ErrorStatus.MOOD_TRACKER_FINISHED);
}

List<SubjectiveAnswer> subjectiveAnswers = new ArrayList<>();
List<MultipleChoiceAnswer> multipleChoiceAnswers = new ArrayList<>();
List<CheckboxChoiceAnswer> checkboxChoiceAnswers = new ArrayList<>();
Expand All @@ -193,16 +210,25 @@ public void submitSurveyAnswers(

switch (dto.getType()) {
case MULTIPLE_CHOICE -> {
// ์„ ํƒ์ง€ ์—”ํ‹ฐํ‹ฐ ์กฐํšŒ ํ›„ ์ถ”๊ฐ€
MultipleChoice foundMultipleChoice = multipleChoiceRepository.findById(dto.getMultipleChoiceId())
.orElseThrow();
// ์งˆ๋ฌธ id์™€ ์„ ํƒ์ง€ id ํ•จ๊ป˜ ๊ฐ๊ด€์‹ ์„ ํƒ์ง€ ์—”ํ‹ฐํ‹ฐ ์กฐํšŒ ํ›„ ์ถ”๊ฐ€
MultipleChoice foundMultipleChoice = multipleChoiceRepository
.findByIdAndSurveyQuestionId(dto.getMultipleChoiceId(), dto.getQuestionId())
.orElseThrow(() -> new MoodTrackerHandler(ErrorStatus.INVALID_CHOICE_FOR_QUESTION));

multipleChoiceAnswers.add(
MoodTrackerConverter.toMultipleChoiceAnswer(foundMultipleChoice)
);
}
case CHECKBOX_CHOICE -> {
// ์ฒดํฌ๋ฐ•์Šค ์„ ํƒ์ง€ ์—”ํ‹ฐํ‹ฐ ๋ชฉ๋ก ์กฐํšŒ ํ›„ ์ถ”๊ฐ€
List<CheckboxChoice> foundCheckboxChoices = checkboxChoiceRepository.findAllById(dto.getCheckboxChoiceIdList());
// ์งˆ๋ฌธ id์™€ ์„ ํƒ์ง€ id ํ•จ๊ป˜ ์ฒดํฌ๋ฐ•์Šค ์„ ํƒ์ง€ ์—”ํ‹ฐํ‹ฐ ๋ชฉ๋ก ์กฐํšŒ ํ›„ ์ถ”๊ฐ€
List<CheckboxChoice> foundCheckboxChoices = checkboxChoiceRepository
.findAllByIdInAndSurveyQuestionId(dto.getCheckboxChoiceIdList(), dto.getQuestionId());

// ์š”์ฒญ ๊ฐœ์ˆ˜์™€ ์กฐํšŒ ๊ฐœ์ˆ˜๊ฐ€ ๋‹ค๋ฅด๋ฉด โ†’ ์œ ํšจํ•˜์ง€ ์•Š์€ ์„ ํƒ์ง€ ํฌํ•จ
if (foundCheckboxChoices.size() != dto.getCheckboxChoiceIdList().size()) {
throw new MoodTrackerHandler(ErrorStatus.INVALID_CHOICE_FOR_QUESTION);
}

checkboxChoiceAnswers.addAll(
MoodTrackerConverter.toCheckboxChoiceAnswerList(foundCheckboxChoices)
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
import com.haru.api.domain.workspace.entity.Workspace;

public interface MoodTrackerQueryService {
MoodTrackerResponseDTO.PreviewList getMoodTrackerPreviewList(User user, Workspace workspace);
MoodTrackerResponseDTO.PreviewList getPreviewList(User user, Workspace workspace);

MoodTrackerResponseDTO.BaseResult getBaseResult(User user, MoodTracker moodTracker);

MoodTrackerResponseDTO.QuestionResult getQuestionResult(User user, MoodTracker moodTracker);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public class MoodTrackerQueryServiceImpl implements MoodTrackerQueryService {
private final SurveyQuestionRepository surveyQuestionRepository;

@Override
public MoodTrackerResponseDTO.PreviewList getMoodTrackerPreviewList(User user, Workspace workspace) {
public MoodTrackerResponseDTO.PreviewList getPreviewList(User user, Workspace workspace) {

UserWorkspace foundUserWorkspace = userWorkspaceRepository.findByWorkspaceIdAndUserId(workspace.getId(), user.getId())
.orElseThrow(() -> new UserWorkspaceHandler(ErrorStatus.USER_WORKSPACE_NOT_FOUND));
Expand All @@ -64,6 +64,32 @@ public MoodTrackerResponseDTO.PreviewList getMoodTrackerPreviewList(User user, W
return previewList;
}

@Override
@Transactional(readOnly = true)
@TrackLastOpened
public MoodTrackerResponseDTO.BaseResult getBaseResult(User user, MoodTracker moodTracker) {

// ์›Œํฌ์ŠคํŽ˜์ด์Šค ๊ถŒํ•œ ์กฐํšŒ
UserWorkspace foundUserWorkspace = userWorkspaceRepository.findByWorkspaceIdAndUserId(
moodTracker.getWorkspace().getId(), user.getId()
).orElseThrow(() -> new UserWorkspaceHandler(ErrorStatus.USER_WORKSPACE_NOT_FOUND));

// ๊ถŒํ•œ ๊ฒ€์ฆ
boolean hasAccess =
// ์›Œํฌ์ŠคํŽ˜์ด์Šค ์ƒ์„ฑ์ž
foundUserWorkspace.getAuth().equals(Auth.ADMIN)
// ํ•ด๋‹น MoodTracker ์ƒ์„ฑ์ž
|| moodTracker.getCreator().getId().equals(user.getId())
// ๊ณต๊ฐœ๋œ ์„ค๋ฌธ
|| moodTracker.getVisibility().equals(MoodTrackerVisibility.PUBLIC);

if (!hasAccess) {
throw new MoodTrackerHandler(ErrorStatus.MOOD_TRACKER_ACCESS_DENIED);
}

return MoodTrackerConverter.toBaseResultDTO(moodTracker, hashIdUtil);
}

@Override
@Transactional(readOnly = true)
@TrackLastOpened
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ public interface MoodTrackerReportService {
void generateAndUploadReportFileAndThumbnail(
Long moodTrackerId
);
void updateAndUploadReportFileAndThumbnail(
Long moodTrackerId
);
void deleteReportFileAndThumbnail(
Long moodTrackerId
);
String generateDownloadLink(
MoodTracker moodTracker,
Format format
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import com.haru.api.infra.api.client.ChatGPTClient;
import com.haru.api.infra.s3.AmazonS3Manager;
import com.haru.api.infra.s3.MarkdownFileUploader;
import com.lowagie.text.pdf.BaseFont;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.xwpf.usermodel.ParagraphAlignment;
Expand Down Expand Up @@ -201,15 +200,11 @@ public String generateDownloadLink(
return downloadLink;
}

@Override
public void generateAndUploadReportFileAndThumbnail(Long moodTrackerId){

// ๋ฆฌํฌํŠธ ์ƒ์„ฑ
generateReport(moodTrackerId);

private void uploadReportFileAndThumbnail(Long moodTrackerId){
MoodTracker foundMoodTracker = moodTrackerRepository.findById(moodTrackerId)
.orElseThrow(() -> new MoodTrackerHandler(ErrorStatus.MOOD_TRACKER_NOT_FOUND));

// ๋ฆฌํฌํŠธ ํŒŒ์ผ ์ƒ์„ฑ
byte[] pdfReportBytes;
byte[] docxReportBytes;

Expand Down Expand Up @@ -251,9 +246,9 @@ public void generateAndUploadReportFileAndThumbnail(Long moodTrackerId){

amazonS3Manager.uploadFile(pdfReportKey, pdfReportBytes, "application/pdf");
amazonS3Manager.uploadFile(wordReportKey, docxReportBytes, "application/vnd.openxmlformats-officedocument.wordprocessingml.document");

foundMoodTracker.updateReportKeyName(pdfReportKey, wordReportKey);

// ๋ฆฌํฌํŠธ ์ธ๋„ค์ผ ์ƒ์„ฑ
String thumbnailKey = markdownFileUploader.createOrUpdateThumbnailWithPdfBytes(
pdfReportBytes,
"mood-tracker",
Expand All @@ -262,6 +257,31 @@ public void generateAndUploadReportFileAndThumbnail(Long moodTrackerId){
foundMoodTracker.updateThumbnailKey(thumbnailKey);
}

@Override
public void generateAndUploadReportFileAndThumbnail(Long moodTrackerId){
// ๋ฆฌํฌํŠธ ์ƒ์„ฑ
generateReport(moodTrackerId);

// ๋ฆฌํฌํŠธ ํŒŒ์ผ ๋ฐ ์ธ๋„ค์ผ ์—…๋ฐ์ดํŠธ
uploadReportFileAndThumbnail(moodTrackerId);
}

@Override
public void updateAndUploadReportFileAndThumbnail(Long moodTrackerId) {
// ๋ฆฌํฌํŠธ ํŒŒ์ผ ๋ฐ ์ธ๋„ค์ผ ์—…๋ฐ์ดํŠธ
uploadReportFileAndThumbnail(moodTrackerId);
}

@Override
public void deleteReportFileAndThumbnail(Long moodTrackerId) {
MoodTracker foundMoodTracker = moodTrackerRepository.findById(moodTrackerId)
.orElseThrow(() -> new MoodTrackerHandler(ErrorStatus.MOOD_TRACKER_NOT_FOUND));

amazonS3Manager.deleteFile(foundMoodTracker.getPdfReportKey());
amazonS3Manager.deleteFile(foundMoodTracker.getWordReportKey());
amazonS3Manager.deleteFile(foundMoodTracker.getThumbnailKey());
}

/* ========================= ํ—ฌํผ๋“ค ========================= */

// PDF: ์ œ๋ชฉ + ๋ฉ”ํƒ€(์ž‘์„ฑ์ž/๋งˆ๊ฐ์ผ) + ๋ณธ๋ฌธ(๋งˆํฌ๋‹ค์šด) โ†’ ํ…œํ”Œ๋ฆฟ ๋ Œ๋”๋ง ํ›„ HTMLโ†’PDF ๋ณ€ํ™˜
Expand Down
Loading