Skip to content
Open
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
@@ -0,0 +1,62 @@
package moadong.feedback.prompt.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import moadong.feedback.prompt.payload.request.FeedbackPromptDefinitionRequest;
import moadong.feedback.prompt.payload.response.FeedbackPromptAdminDefinitionResponse;
import moadong.feedback.prompt.payload.response.FeedbackPromptDefinitionResponse;
import moadong.feedback.prompt.payload.response.FeedbackPromptListResponse;
import moadong.feedback.prompt.service.FeedbackPromptDefinitionAdminService;
import moadong.global.payload.Response;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/admin/feedback-prompts")
@RequiredArgsConstructor
@Tag(name = "Feedback_Prompt_Admin", description = "행동 직후 피드백 프롬프트 개발자 API")
@SecurityRequirement(name = "BearerAuth")
public class FeedbackPromptAdminController {

private final FeedbackPromptDefinitionAdminService definitionAdminService;

@GetMapping
@Operation(summary = "피드백 프롬프트 목록", description = "개발자 포털에서 문항 설정 목록을 조회합니다.")
public ResponseEntity<?> getPrompts() {
FeedbackPromptListResponse response = definitionAdminService.getPrompts();
return Response.ok(response);
}

@GetMapping("/{promptId}")
@Operation(summary = "피드백 프롬프트 상세", description = "비활성 선택지를 포함한 문항 설정 상세를 조회합니다.")
public ResponseEntity<?> getPrompt(@PathVariable String promptId) {
FeedbackPromptAdminDefinitionResponse response = definitionAdminService.getPrompt(promptId);
return Response.ok(response);
}

@PostMapping
@Operation(summary = "피드백 프롬프트 생성", description = "새 행동 직후 피드백 프롬프트를 생성합니다.")
public ResponseEntity<?> createPrompt(@RequestBody @Valid FeedbackPromptDefinitionRequest request) {
FeedbackPromptDefinitionResponse response = definitionAdminService.createPrompt(request);
return Response.ok("피드백 프롬프트가 생성되었습니다.", response);
}

@PutMapping("/{promptId}")
@Operation(summary = "피드백 프롬프트 수정", description = "문항, 선택지, 배치 순서, 재노출 정책을 수정합니다.")
public ResponseEntity<?> updatePrompt(
@PathVariable String promptId,
@RequestBody @Valid FeedbackPromptDefinitionRequest request
) {
FeedbackPromptDefinitionResponse response = definitionAdminService.updatePrompt(promptId, request);
return Response.ok("피드백 프롬프트가 수정되었습니다.", response);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package moadong.feedback.prompt.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import moadong.feedback.prompt.enums.FeedbackPromptTriggerType;
import moadong.feedback.prompt.payload.request.FeedbackPromptDismissRequest;
import moadong.feedback.prompt.payload.request.FeedbackPromptResponseCreateRequest;
import moadong.feedback.prompt.payload.response.FeedbackPromptEligibilityResponse;
import moadong.feedback.prompt.payload.response.FeedbackPromptResponseCreateResponse;
import moadong.feedback.prompt.service.FeedbackPromptEligibilityService;
import moadong.feedback.prompt.service.FeedbackPromptResponseService;
import moadong.global.payload.Response;
import moadong.user.annotation.CurrentUser;
import moadong.user.payload.CustomUserDetails;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/feedback-prompts")
@RequiredArgsConstructor
@Tag(name = "Feedback_Prompt", description = "행동 직후 피드백 API")
public class FeedbackPromptController {

private final FeedbackPromptEligibilityService eligibilityService;
private final FeedbackPromptResponseService responseService;

@GetMapping("/eligibility")
@Operation(summary = "피드백 노출 가능 여부", description = "특정 행동 직후 피드백창을 노출할 수 있는지 조회합니다.")
@SecurityRequirement(name = "BearerAuth")
public ResponseEntity<?> getEligibility(
@RequestParam FeedbackPromptTriggerType triggerType,
@RequestParam(required = false) String clubId,
@RequestParam(required = false) String anonymousClientId,
@CurrentUser CustomUserDetails user
) {
FeedbackPromptEligibilityResponse response =
eligibilityService.getEligibility(triggerType, clubId, anonymousClientId, user);
return Response.ok(response);
}

@PostMapping("/{promptId}/responses")
@Operation(summary = "피드백 응답 제출", description = "행동 직후 피드백 응답을 저장합니다.")
@SecurityRequirement(name = "BearerAuth")
public ResponseEntity<?> createResponse(
@PathVariable String promptId,
@RequestBody @Valid FeedbackPromptResponseCreateRequest request,
@CurrentUser CustomUserDetails user
) {
FeedbackPromptResponseCreateResponse response = responseService.createResponse(promptId, request, user);
return Response.ok("피드백이 저장되었습니다.", response);
}

@PostMapping("/{promptId}/dismiss")
@Operation(summary = "피드백 닫기 저장", description = "피드백창을 닫은 이력을 저장해 재노출 정책에 반영합니다.")
@SecurityRequirement(name = "BearerAuth")
public ResponseEntity<?> dismiss(
@PathVariable String promptId,
@RequestBody @Valid FeedbackPromptDismissRequest request,
@CurrentUser CustomUserDetails user
) {
responseService.dismiss(promptId, request, user);
return Response.ok("ok");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package moadong.feedback.prompt.entity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class FeedbackPromptClientContext {

private String path;
private String deviceType;
private String userAgent;
private boolean appWebView;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package moadong.feedback.prompt.entity;

import java.time.Instant;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import moadong.feedback.prompt.enums.FeedbackPromptAudience;
import moadong.feedback.prompt.enums.FeedbackPromptTriggerType;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.index.CompoundIndex;
import org.springframework.data.mongodb.core.mapping.Document;

@Document("feedback_prompt_definitions")
@CompoundIndex(name = "trigger_active_order_idx", def = "{'triggerType': 1, 'active': 1, 'displayOrder': 1}")
@CompoundIndex(name = "audience_trigger_order_idx", def = "{'audience': 1, 'triggerType': 1, 'displayOrder': 1}")
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class FeedbackPromptDefinition {

@Id
private String id;

private FeedbackPromptTriggerType triggerType;
private FeedbackPromptAudience audience;
private String title;
private String description;

@Builder.Default
private List<FeedbackPromptRatingOption> ratingOptions = List.of();

private FeedbackPromptFollowUp followUp;
private FeedbackPromptExposurePolicy exposurePolicy;
private int displayOrder;
private boolean active;

@Builder.Default
private Instant createdAt = Instant.now();

@Builder.Default
private Instant updatedAt = Instant.now();

public void updateFrom(FeedbackPromptDefinition next) {
this.title = next.title;
this.description = next.description;
this.ratingOptions = next.ratingOptions == null ? List.of() : List.copyOf(next.ratingOptions);
this.followUp = next.followUp;
this.exposurePolicy = next.exposurePolicy;
this.displayOrder = next.displayOrder;
this.active = next.active;
this.updatedAt = Instant.now();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package moadong.feedback.prompt.entity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class FeedbackPromptExposurePolicy {

private int answeredCooldownDays;
private int dismissedCooldownDays;
private int shownCooldownHours;
private boolean oncePerClub;
private int dailyExposureLimit;

public static FeedbackPromptExposurePolicy adminDefault() {
return FeedbackPromptExposurePolicy.builder()
.answeredCooldownDays(30)
.dismissedCooldownDays(7)
.shownCooldownHours(24)
.oncePerClub(false)
.dailyExposureLimit(0)
.build();
}

public static FeedbackPromptExposurePolicy userClubExitDefault() {
return FeedbackPromptExposurePolicy.builder()
.answeredCooldownDays(0)
.dismissedCooldownDays(7)
.shownCooldownHours(24)
.oncePerClub(true)
.dailyExposureLimit(1)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package moadong.feedback.prompt.entity;

import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class FeedbackPromptFollowUp {

private String reasonQuestion;

@Builder.Default
private List<FeedbackPromptReasonOption> reasonOptions = List.of();

private String commentQuestion;
private String commentPlaceholder;
private int commentMaxLength;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package moadong.feedback.prompt.entity;

import java.time.Instant;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import moadong.feedback.prompt.enums.FeedbackPromptAudience;
import moadong.feedback.prompt.enums.FeedbackPromptInteractionType;
import moadong.feedback.prompt.enums.FeedbackPromptTriggerType;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.index.CompoundIndex;
import org.springframework.data.mongodb.core.mapping.Document;

@Document("feedback_prompt_interactions")
@CompoundIndex(name = "user_trigger_type_created_idx", def = "{'audience': 1, 'userId': 1, 'triggerType': 1, 'type': 1, 'createdAt': -1}")
@CompoundIndex(name = "anonymous_trigger_club_type_created_idx", def = "{'audience': 1, 'anonymousClientId': 1, 'triggerType': 1, 'clubId': 1, 'type': 1, 'createdAt': -1}")
@CompoundIndex(name = "user_type_created_idx", def = "{'audience': 1, 'userId': 1, 'type': 1, 'createdAt': -1}")
@CompoundIndex(name = "anonymous_type_created_idx", def = "{'audience': 1, 'anonymousClientId': 1, 'type': 1, 'createdAt': -1}")
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class FeedbackPromptInteraction {

@Id
private String id;

private String promptId;
private FeedbackPromptTriggerType triggerType;
private FeedbackPromptAudience audience;
private String userId;
private String anonymousClientId;
private String clubId;
private FeedbackPromptInteractionType type;

@Builder.Default
private Instant createdAt = Instant.now();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package moadong.feedback.prompt.entity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import moadong.feedback.prompt.enums.FeedbackPromptRating;

@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class FeedbackPromptRatingOption {

private FeedbackPromptRating rating;
private String label;
private int displayOrder;
private boolean requiresFollowUp;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package moadong.feedback.prompt.entity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class FeedbackPromptReasonOption {

private String id;
private String label;
private int displayOrder;
private boolean active;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package moadong.feedback.prompt.entity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class FeedbackPromptReasonSnapshot {

private String id;
private String label;
}
Loading
Loading