Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
d2331f5
refactor: 채팅 상태 구조 리팩토링
ThirFir Jan 23, 2026
e9bead9
chore: 이름 변경 ChatApi -> ChattingApi
ThirFir Jan 23, 2026
26b120b
feat: 채팅 조회 Response 클래스 정의
ThirFir Jan 23, 2026
1ad416a
feat: 특정 질문에 대한 채팅 내역 조회 API 명세 선언
ThirFir Jan 23, 2026
ecf4898
feat: 채팅 내역 조회 API DataSource 정의
ThirFir Jan 23, 2026
25cbc70
feat: RemoteDataSource 힐트 모듈 선언
ThirFir Jan 23, 2026
7e44bb6
feat: 채팅 스트리밍 데이터 Response 정의
ThirFir Jan 23, 2026
9d90696
feat: Network Hilt Module 수정
ThirFir Jan 23, 2026
ed058bf
fix: DataSource 구현 관계 추가
ThirFir Jan 23, 2026
aae1e3a
feat: 채팅 스트리밍 API 로직 설계
ThirFir Jan 23, 2026
9bfea03
feat: 자기소개서 업데이트 API 로직 구현
ThirFir Jan 23, 2026
f99fb10
fix: 채팅 내역 조회 파라미터 수정
ThirFir Jan 23, 2026
4e9480d
chore: 커스텀 runCatching inline 함수화
ThirFir Jan 23, 2026
7959ce5
feat: 채팅 API 레포지토리 구조 구현
ThirFir Jan 23, 2026
358f542
feat: 레포지토리 Hilt 모듈 정의
ThirFir Jan 23, 2026
e3021e0
feat: Question Api 선언
ThirFir Jan 23, 2026
52e61e3
feat: QuestionAPI 메서드 선언
ThirFir Jan 23, 2026
6fca813
feat: Question DataSource 정의
ThirFir Jan 23, 2026
8695ca2
refactor: Question 모델에 자소서 프로퍼티 추가
ThirFir Jan 23, 2026
8200874
feat: QuestionResponse 모델 변환 확장함수 추가
ThirFir Jan 23, 2026
ac53008
feat: QuestionRepository 선언
ThirFir Jan 23, 2026
3420991
chore: QuestionDetail -> Question 네이밍 수정
ThirFir Jan 23, 2026
d6e0b93
fix: API 호출에 필요한 Request 파라미터 추가
ThirFir Jan 23, 2026
57b8e54
feat: QuestionRepository 구현
ThirFir Jan 23, 2026
e53e3ca
feat: Experience 레트로핏 서비스 선언
ThirFir Jan 23, 2026
ce06e6d
refactor: 문항 생성 시 Response 다른 것과 통일
ThirFir Jan 23, 2026
6295b10
feat: ExperienceAPI 메서드 구현
ThirFir Jan 23, 2026
53228a9
feat: Experience DataSource 구현
ThirFir Jan 23, 2026
0e9802a
feat: Experience Repository 구현
ThirFir Jan 23, 2026
7abb69f
fix: 경험 수정 Request nullability 설정
ThirFir Jan 24, 2026
c3e30a8
feat: Time Format 변환 확장함수 구현
ThirFir Jan 25, 2026
8740698
chore: 경험 모델 클래스 시간 타입 LocalDate로 수정
ThirFir Jan 25, 2026
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,54 @@
package com.useai.core.common.extensions

import java.time.LocalDate
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.time.format.DateTimeParseException

fun String?.toLocalDateTime(pattern: String = "yyyy-MM-dd'T'HH:mm:ss"): LocalDateTime? {
if (this.isNullOrBlank()) return null

return try {
val formatter = DateTimeFormatter.ofPattern(pattern)
LocalDateTime.parse(this, formatter)
} catch (e: DateTimeParseException) {
e.printStackTrace()
null
}
}

fun String?.toLocalDate(pattern: String = "yyyy-MM-dd"): LocalDate? {
if (this.isNullOrBlank()) return null

return try {
val formatter = DateTimeFormatter.ofPattern(pattern)
LocalDate.parse(this, formatter)
} catch (e: DateTimeParseException) {
e.printStackTrace()
null
}
}

fun LocalDateTime?.toFormattedString(pattern: String = "yyyy-MM-dd'T'HH:mm:ss"): String? {
if (this == null) return null

return try {
val formatter = DateTimeFormatter.ofPattern(pattern)
this.format(formatter)
} catch (e: Exception) {
e.printStackTrace()
null
}
}

fun LocalDate?.toFormattedString(pattern: String = "yyyy-MM-dd"): String? {
if (this == null) return null

return try {
val formatter = DateTimeFormatter.ofPattern(pattern)
this.format(formatter)
} catch (e: Exception) {
e.printStackTrace()
null
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.useai.core.data.di

import com.useai.core.data.repository.ChattingRepository
import com.useai.core.data.repository.ChattingRepositoryImpl
import com.useai.core.data.repository.ExperienceRepository
import com.useai.core.data.repository.ExperienceRepositoryImpl
import com.useai.core.data.repository.QuestionRepository
import com.useai.core.data.repository.QuestionRepositoryImpl
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ActivityRetainedComponent
import dagger.hilt.android.scopes.ActivityScoped

@Module
@InstallIn(ActivityRetainedComponent::class)
internal interface RepositoryModule {

@Binds
@ActivityScoped

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ActivityRetainedComponent는 화면 회전 시에도 유지되게 하는 설정값이고 ActivityScoped는 무조건 Activity 생명주기에 따라 소멸하는 설정값인 것 같은데(잘 모름)
이거 혹시 @ActivityRetainedScoped를 써야 의도대로 동작하지 않을까요?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

앗 이런!! 수정할게여

fun providesChattingRepository(
impl: ChattingRepositoryImpl
) : ChattingRepository

@Binds
@ActivityScoped
fun providesQuestionRepository(
impl: QuestionRepositoryImpl
) : QuestionRepository

@Binds
@ActivityScoped
fun providesExperienceRepository(
impl: ExperienceRepositoryImpl
) : ExperienceRepository
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.useai.core.data.repository

import com.useai.core.model.chat.ChattingHistory
import com.useai.core.model.chat.ChattingStreaming
import kotlinx.coroutines.flow.Flow

interface ChattingRepository {

fun startChattingStream(questionId: String, sendingMessage: String): Flow<ChattingStreaming>
suspend fun getChatHistory(questionId: String): Result<ChattingHistory>
suspend fun updateLetter(chattingId: String, questionId: String, content: String): Result<Unit>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

혹시 이게 자소서 업데이트하는 함수라면 updatePaper로 이름 지으면 어떨까요?
왜냐하면 Figma에서 자소서 아이콘 이름이 paper라서.. ㅎㅎ
아니면 아예 cover letter라고 표현하는 것도 괜찮을 것 같습니다

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이게 백엔드에서는 Draft로 표현하는거 같더라구요 ...
일단 다같이 얘기를 해봐야할듯 합니다 !!

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.useai.core.data.repository

import com.useai.core.model.chat.ChattingHistory
import com.useai.core.model.chat.ChattingStreaming
import com.useai.core.network.error.runCatchingWith
import com.useai.core.network.request.StartChattingStreamRequest
import com.useai.core.network.request.UpdateLetterRequest
import com.useai.core.network.response.toChattingHistory
import com.useai.core.network.response.toChattingStreaming
import com.useai.core.network.source.ChattingRemoteDataSource
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import javax.inject.Inject

internal class ChattingRepositoryImpl @Inject constructor(
private val chattingRemoteDataSource: ChattingRemoteDataSource
) : ChattingRepository {

override fun startChattingStream(questionId: String, sendingMessage: String): Flow<ChattingStreaming> {
return chattingRemoteDataSource.startChattingStream(
StartChattingStreamRequest(
sendingMessage = sendingMessage,
questionId = questionId,
experienceIds = listOf()
)
).map { it.toChattingStreaming() }
}

override suspend fun getChatHistory(questionId: String): Result<ChattingHistory> {
return runCatchingWith {
chattingRemoteDataSource.getChatHistory(questionId).toChattingHistory()
}
}

override suspend fun updateLetter(
chattingId: String,
questionId: String,
content: String
): Result<Unit> {
return runCatchingWith {
chattingRemoteDataSource.updateLetter(
chattingId = chattingId,
request = UpdateLetterRequest(
questionId = questionId,
content = content
)
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.useai.core.data.repository

import com.useai.core.model.experience.Experience
import com.useai.core.model.experience.ExperienceParam
import com.useai.core.model.experience.MatchingExperience
import com.useai.core.network.request.UpdateExperienceRequest

interface ExperienceRepository {

suspend fun createExperience(experience: ExperienceParam): Result<Experience>
suspend fun getExperiences(): Result<List<Experience>>
suspend fun getExperience(experienceId: String): Result<Experience>
suspend fun searchExperience(query: String): Result<List<MatchingExperience>>
suspend fun updateExperience(experienceId: String, request: UpdateExperienceRequest): Result<Experience>
suspend fun deleteExperience(experienceId: String): Result<Unit>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.useai.core.data.repository

import com.useai.core.common.extensions.toFormattedString
import com.useai.core.model.experience.Experience
import com.useai.core.model.experience.ExperienceParam
import com.useai.core.model.experience.MatchingExperience
import com.useai.core.network.error.runCatchingWith
import com.useai.core.network.request.CreateExperienceRequest
import com.useai.core.network.request.UpdateExperienceRequest
import com.useai.core.network.response.toExperience
import com.useai.core.network.response.toMatchingExperience
import com.useai.core.network.source.ExperienceRemoteDataSource
import javax.inject.Inject

internal class ExperienceRepositoryImpl @Inject constructor(
private val experienceRemoteDataSource: ExperienceRemoteDataSource
) : ExperienceRepository {

override suspend fun createExperience(experience: ExperienceParam): Result<Experience> {
return runCatchingWith {
experienceRemoteDataSource.createExperience(
CreateExperienceRequest(
category = experience.category,
date = experience.date.toFormattedString().orEmpty(),
experienceType = experience.experienceType,
situation = experience.situation,
task = experience.task,
action = experience.action,
result = experience.result,
title = experience.title
)
).toExperience()
}
}

override suspend fun getExperiences(): Result<List<Experience>> {
return runCatchingWith {
experienceRemoteDataSource.getExperiences().experiences.map { it.toExperience() }
}
}

override suspend fun getExperience(experienceId: String): Result<Experience> {
return runCatchingWith {
experienceRemoteDataSource.getExperience(experienceId).toExperience()
}
}

override suspend fun searchExperience(query: String): Result<List<MatchingExperience>> {
return runCatchingWith {
experienceRemoteDataSource.searchExperience(query).results.map { it.toMatchingExperience() }
}
}

override suspend fun updateExperience(
experienceId: String,
request: UpdateExperienceRequest
): Result<Experience> {
return runCatchingWith {
experienceRemoteDataSource.updateExperience(experienceId, request).toExperience()
}
}

override suspend fun deleteExperience(experienceId: String): Result<Unit> {
return runCatchingWith {
experienceRemoteDataSource.deleteExperience(experienceId)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.useai.core.data.repository

import com.useai.core.model.chat.Question

interface QuestionRepository {

suspend fun createQuestion(projectId: String, question: Question): Result<String>
suspend fun getQuestions(projectId: String): Result<List<Question>>
suspend fun getQuestion(projectId: String, questionId: String): Result<Question>
suspend fun updateQuestion(projectId: String, question: Question): Result<Question>
suspend fun deleteQuestion(projectId: String, questionId: String): Result<Unit>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.useai.core.data.repository

import com.useai.core.model.chat.Question
import com.useai.core.network.error.runCatchingWith
import com.useai.core.network.request.CreateQuestionRequest
import com.useai.core.network.request.UpdateQuestionRequest
import com.useai.core.network.response.toQuestion
import com.useai.core.network.source.QuestionRemoteDataSource
import javax.inject.Inject

internal class QuestionRepositoryImpl @Inject constructor(
private val questionRemoteDataSource: QuestionRemoteDataSource
) : QuestionRepository {

override suspend fun createQuestion(projectId: String, question: Question): Result<String> {
return runCatchingWith {
questionRemoteDataSource.createQuestion(
projectId,
CreateQuestionRequest(
title = question.title,
maxLength = question.maxLength
)
).id
}
}

override suspend fun getQuestions(projectId: String): Result<List<Question>> {
return runCatchingWith {
questionRemoteDataSource.getQuestions(projectId).map { it.toQuestion() }
}
}

override suspend fun getQuestion(projectId: String, questionId: String): Result<Question> {
return runCatchingWith {
questionRemoteDataSource.getQuestion(projectId, questionId).toQuestion()
}
}

override suspend fun updateQuestion(projectId: String, question: Question): Result<Question> {
return runCatchingWith {
questionRemoteDataSource.updateQuestion(
projectId,
question.id,
UpdateQuestionRequest(
title = question.title,
maxLength = question.maxLength,
letter = question.letter
)
).toQuestion()
}
}

override suspend fun deleteQuestion(projectId: String, questionId: String): Result<Unit> {
return runCatchingWith {
questionRemoteDataSource.deleteQuestion(projectId, questionId)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ package com.useai.core.model.chat
data class Question(
val id: String,
val title: String,
val maxLength: Int
val maxLength: Int,
val letter: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.useai.core.model.experience

import java.time.LocalDate

data class Experience(
val id: String,
val tags: List<String>,
val situation: String,
val task: String,
val action: String,
val result: String,
val category: ExperienceCategory,
val date: LocalDate,
val experienceType: String,
val title: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.useai.core.model.experience

/**
* @property PROACTIVE_EXECUTION 주도적 실행력
* @property TECHNICAL_EXPERTISE 기술적 전문성
* @property LOGICAL_ANALYSIS 논리적 분석력
* @property CREATIVE_PROBLEM_SOLVING 창의적 문제해결
* @property COLLABORATIVE_COMMUNICATION 협업적 소통
* @property TENACIOUS_RESPONSIBILITY 끈기 있는 책임감
* @property FLEXIBLE_ADAPTABILITY 유연한 적응력
* @property CUSTOMER_VALUE_ORIENTATION 고객 가치 지향
*/
enum class ExperienceCategory {
PROACTIVE_EXECUTION,
TECHNICAL_EXPERTISE,
LOGICAL_ANALYSIS,
CREATIVE_PROBLEM_SOLVING,
COLLABORATIVE_COMMUNICATION,
TENACIOUS_RESPONSIBILITY,
FLEXIBLE_ADAPTABILITY,
CUSTOMER_VALUE_ORIENTATION
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.useai.core.model.experience

import java.time.LocalDate

data class ExperienceParam(
val situation: String,
val task: String,
val action: String,
val result: String,
val category: String,
val date: LocalDate,
val experienceType: String,
val title: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.useai.core.model.experience

data class MatchingExperience(
val experience: Experience,
val score: Float
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.useai.core.network

import com.launchdarkly.eventsource.background.BackgroundEventHandler
import com.launchdarkly.eventsource.background.BackgroundEventSource
import com.useai.core.network.request.StartChattingStreamRequest

internal fun interface ChattingEventSourceFactory {
fun create(handler: BackgroundEventHandler, request: StartChattingStreamRequest): BackgroundEventSource
}

This file was deleted.

Loading