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
@@ -1,13 +1,16 @@
package com.devkor.ifive.nadab.domain.admin.api;

import com.devkor.ifive.nadab.domain.admin.api.dto.response.AdminAskChatRagBackfillResponse;
import com.devkor.ifive.nadab.domain.admin.api.dto.response.AdminAskChatRagBackfillStatusResponse;
import com.devkor.ifive.nadab.domain.askchat.application.AskChatRagBackfillService;
import com.devkor.ifive.nadab.domain.askchat.core.dto.AskChatRagBackfillResultDto;
import com.devkor.ifive.nadab.domain.askchat.core.dto.AskChatRagBackfillStatusDto;
import com.devkor.ifive.nadab.global.core.response.ApiResponseDto;
import com.devkor.ifive.nadab.global.core.response.ApiResponseEntity;
import io.swagger.v3.oas.annotations.Hidden;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
Expand All @@ -20,6 +23,12 @@ public class AdminAskChatRagController {

private final AskChatRagBackfillService askChatRagBackfillService;

@GetMapping("/backfill/daily-answers/status")
public ResponseEntity<ApiResponseDto<AdminAskChatRagBackfillStatusResponse>> getDailyAnswerBackfillStatus() {
AskChatRagBackfillStatusDto status = askChatRagBackfillService.getCompletedDailyAnswerStatus();
return ApiResponseEntity.ok(AdminAskChatRagBackfillStatusResponse.from(status));
}

@PostMapping("/backfill/daily-answers")
public ResponseEntity<ApiResponseDto<AdminAskChatRagBackfillResponse>> backfillDailyAnswers() {
AskChatRagBackfillResultDto result = askChatRagBackfillService.backfillCompletedDailyAnswers();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.devkor.ifive.nadab.domain.admin.api.dto.response;

import com.devkor.ifive.nadab.domain.askchat.core.dto.AskChatRagBackfillStatusDto;

public record AdminAskChatRagBackfillStatusResponse(
long targetCount,
long indexedCount,
long failedCount
) {

public static AdminAskChatRagBackfillStatusResponse from(AskChatRagBackfillStatusDto status) {
return new AdminAskChatRagBackfillStatusResponse(
status.targetCount(),
status.indexedCount(),
status.failedCount()
);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.devkor.ifive.nadab.domain.askchat.application;

import com.devkor.ifive.nadab.domain.askchat.core.dto.AskChatRagBackfillResultDto;
import com.devkor.ifive.nadab.domain.askchat.core.dto.AskChatRagBackfillStatusDto;
import com.devkor.ifive.nadab.domain.askchat.core.dto.AskChatRagBackfillTargetDto;
import com.devkor.ifive.nadab.domain.askchat.core.repository.AskChatRagBackfillQueryRepository;
import com.devkor.ifive.nadab.domain.askchat.infra.AskChatEmbeddingClient;
Expand All @@ -19,6 +20,10 @@ public class AskChatRagBackfillService {
private final AskChatRagIndexingService indexingService;
private final AskChatEmbeddingClient embeddingClient;

public AskChatRagBackfillStatusDto getCompletedDailyAnswerStatus() {
return backfillQueryRepository.findCompletedDailyAnswerStatus(embeddingClient.version());
}

public AskChatRagBackfillResultDto backfillCompletedDailyAnswers() {
List<AskChatRagBackfillTargetDto> targets = backfillQueryRepository.findCompletedDailyAnswerTargets(
embeddingClient.version(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.devkor.ifive.nadab.domain.askchat.core.dto;

public record AskChatRagBackfillStatusDto(
long targetCount,
long indexedCount,
long failedCount
) {
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.devkor.ifive.nadab.domain.askchat.core.repository;

import com.devkor.ifive.nadab.domain.askchat.core.dto.AskChatRagBackfillStatusDto;
import com.devkor.ifive.nadab.domain.askchat.core.dto.AskChatRagBackfillTargetDto;
import com.devkor.ifive.nadab.domain.user.core.entity.InterestCode;
import lombok.RequiredArgsConstructor;
Expand Down Expand Up @@ -43,6 +44,33 @@ AND NOT EXISTS (
.addValue("limit", limit), this::mapTarget);
}

public AskChatRagBackfillStatusDto findCompletedDailyAnswerStatus(int embeddingVersion) {
String sql = """
SELECT COUNT(*) FILTER (WHERE d.id IS NULL) AS target_count,
COUNT(*) FILTER (WHERE d.embedding_status = 'COMPLETED') AS indexed_count,
COUNT(*) FILTER (
WHERE d.embedding_status IN ('FAILED', 'DEAD_LETTER')
) AS failed_count
FROM daily_reports dr
JOIN answer_entries ae ON ae.id = dr.answer_entry_id
LEFT JOIN ask_chat_rag_documents d
ON d.source_type = 'ANSWER_ENTRY'
AND d.source_id = ae.id
AND d.embedding_version = :embeddingVersion
WHERE dr.status = 'COMPLETED'
""";

return jdbcTemplate.queryForObject(
sql,
new MapSqlParameterSource("embeddingVersion", embeddingVersion),
(rs, rowNum) -> new AskChatRagBackfillStatusDto(
rs.getLong("target_count"),
rs.getLong("indexed_count"),
rs.getLong("failed_count")
)
);
}

private AskChatRagBackfillTargetDto mapTarget(ResultSet rs, int rowNum) throws SQLException {
String interestCode = rs.getString("interest_code");
return new AskChatRagBackfillTargetDto(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ public class LocalAuthController {
@Operation(
summary = "더미 유저 로그인",
description = """
로컬 환경에서 더미 유저로 로그인하여 액세스 토큰을 발급받습니다.
로컬 환경에서 더미 유저로 로그인하여 액세스 토큰을 발급받습니다. <br/>
더미 유저가 없을 시에는 자동 생성합니다.
"""
)
public ResponseEntity<ApiResponseDto<LocalTokenResponse>> dummyLogin() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,44 +1,38 @@
package com.devkor.ifive.nadab.domain.auth.application;

import com.devkor.ifive.nadab.domain.auth.api.dto.response.LocalTokenResponse;
import com.devkor.ifive.nadab.domain.auth.infra.LocalDummyUserRepository;
import com.devkor.ifive.nadab.domain.user.core.entity.User;
import com.devkor.ifive.nadab.domain.user.core.repository.UserRepository;
import com.devkor.ifive.nadab.domain.wallet.core.entity.UserWallet;
import com.devkor.ifive.nadab.domain.wallet.core.repository.UserWalletRepository;
import com.devkor.ifive.nadab.global.core.response.ErrorCode;
import com.devkor.ifive.nadab.global.exception.NotFoundException;
import com.devkor.ifive.nadab.global.security.token.AccessTokenProvider;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Profile("local")
@Service
@Transactional
@RequiredArgsConstructor
public class LocalTokenService {

private final AccessTokenProvider accessTokenProvider;
private final UserRepository userRepository;
private final UserWalletRepository userWalletRepository;
private final LocalDummyUserRepository localDummyUserRepository;

// 로컬 로그인용 토큰 발급
public LocalTokenResponse issueDummyAccessToken() {
User user = userRepository.findById(11111L)
Long dummyUserId = localDummyUserRepository.createIfAbsent();
User user = userRepository.findById(dummyUserId)
.orElseThrow(() -> new NotFoundException(ErrorCode.AUTH_DUMMY_USER_NOT_FOUND));

UserWallet wallet = getOrCreateWallet(user);

List<String> roles = List.of("USER");
String accessToken = accessTokenProvider.generateToken(user.getId(), roles);

return new LocalTokenResponse(accessToken);
}

public UserWallet getOrCreateWallet(User user) {
return userWalletRepository.findByUserId(user.getId())
.orElseGet(() -> userWalletRepository.save(UserWallet.create(user)));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package com.devkor.ifive.nadab.domain.auth.infra;

import com.devkor.ifive.nadab.domain.user.core.entity.InterestCode;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Profile;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Repository;

@Profile("local")
@Repository
@RequiredArgsConstructor
public class LocalDummyUserRepository {

private static final long DUMMY_USER_ID = 11111L;
private static final String DUMMY_EMAIL = "test@example.com";
private static final String DUMMY_PASSWORD_HASH = "hashed_pw";
private static final String DUMMY_NICKNAME = "TestUser";
private static final long INITIAL_CRYSTAL_BALANCE = 1_000L;
private static final int INITIAL_FREE_TURN_BALANCE = 3;
private static final int INITIAL_PAID_TURN_BALANCE = 1_000;

private final NamedParameterJdbcTemplate jdbcTemplate;

public Long createIfAbsent() {
MapSqlParameterSource parameters = new MapSqlParameterSource()
.addValue("userId", DUMMY_USER_ID)
.addValue("email", DUMMY_EMAIL)
.addValue("passwordHash", DUMMY_PASSWORD_HASH)
.addValue("nickname", DUMMY_NICKNAME)
.addValue("interestCode", InterestCode.ROUTINE.name())
.addValue("crystalBalance", INITIAL_CRYSTAL_BALANCE)
.addValue("freeTurnBalance", INITIAL_FREE_TURN_BALANCE)
.addValue("paidTurnBalance", INITIAL_PAID_TURN_BALANCE);

jdbcTemplate.update("""
INSERT INTO users (
id,
email,
password_hash,
nickname,
profile_image_key,
default_profile_type,
signup_status,
registered_at,
created_at,
updated_at
)
VALUES (
:userId,
:email,
:passwordHash,
:nickname,
NULL,
'DEFAULT',
'PROFILE_INCOMPLETE',
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP
)
ON CONFLICT (id) DO NOTHING
""", parameters);

jdbcTemplate.update("""
INSERT INTO user_interests (user_id, interest_id)
SELECT :userId, id
FROM interests
WHERE code = :interestCode
ON CONFLICT (user_id) DO NOTHING
""", parameters);

jdbcTemplate.update("""
INSERT INTO user_wallets (user_id, crystal_balance)
VALUES (:userId, :crystalBalance)
ON CONFLICT (user_id) DO NOTHING
""", parameters);

jdbcTemplate.update("""
INSERT INTO ask_chat_wallets (
user_id,
free_turn_balance,
paid_turn_balance,
version,
created_at,
updated_at
)
VALUES (
:userId,
:freeTurnBalance,
:paidTurnBalance,
0,
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP
)
ON CONFLICT (user_id) DO NOTHING
""", parameters);

return DUMMY_USER_ID;
}
}
Loading
Loading