Skip to content
Closed
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 @@ -11,6 +11,14 @@ import java.util.*
class ChatMessageService (
private val chatMessageRepository: ChatMessageRepository
){
/**
* Creates and persists a new chat message using the provided request data, sender ID, and sequence number.
*
* @param request The request containing chat room ID, receiver ID, and message content.
* @param userId The UUID of the user sending the message.
* @param sequence The sequence number to assign to the message.
* @return The saved ChatMessage entity.
*/
@Transactional
fun save(request: SendChatMessageRequest, userId: UUID, sequence: Long): ChatMessage {
val message = ChatMessage(
Expand All @@ -23,6 +31,13 @@ class ChatMessageService (
return chatMessageRepository.save(message)
}

/**
* Retrieves recent chat messages in a chat room after a specified message.
*
* @param chatroomId The unique identifier of the chat room.
* @param messageId The unique identifier of the message after which to retrieve messages.
* @return A list of recent chat messages following the specified message in the chat room.
*/
@Transactional
fun getRecentMessages(chatroomId: UUID, messageId: UUID): List<ChatMessage> {
return chatMessageRepository.getRecentMessages(chatroomId, messageId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,43 @@ class ChatRoomService(
private val chatRoomRepository: ChatRoomRepository
){

/**
* Creates a chat room for the given carer and center IDs if one does not already exist.
*
* If a chat room already exists for the specified carer and center, returns its ID; otherwise, creates a new chat room and returns its ID.
*
* @param carerId The UUID of the carer.
* @param centerId The UUID of the center.
* @return The UUID of the existing or newly created chat room.
*/
fun create(carerId: UUID, centerId: UUID): UUID {
val existing = chatroomRepository.findByCarerIdAndCenterId(carerId, centerId)
return existing?.id ?: chatroomRepository.save(ChatRoom(carerId = carerId, centerId = centerId)).id
}

/**
* Retrieves a chat room by its unique identifier.
*
* @param chatRoomId The UUID of the chat room to retrieve.
* @return The corresponding ChatRoom entity.
* @throws NoSuchElementException if no chat room with the given ID exists.
*/
fun getById(chatRoomId: UUID): ChatRoom {
return chatroomRepository.findById(chatRoomId)
.orElseThrow()
}

/**
* Retrieves summaries of chat rooms with their latest messages for the given room IDs.
*
* Converts the provided set of string room IDs to UUIDs, fetches chat rooms and their last messages,
* and constructs summary information for each. The opponent ID in each summary is determined by the `isCarer` flag.
*
* @param roomIds Set of chat room IDs as strings.
* @param isCarer Indicates whether the requesting user is a carer (true) or a center (false).
* @return List of chat room summaries, each containing the chat room ID, opponent ID, last message content,
* last message timestamp, and last message sequence number.
*/
fun findChatRoomsWithLastMessages(roomIds: Set<String>, isCarer: Boolean): List<ChatRoomSummaryInfo> {
val uuidSet = roomIds.map(UUID::fromString).toSet()
val projections = chatRoomRepository.findChatRoomsWithLastMessages(uuidSet)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@ class ChatFacadeService(
private val carerService: CarerService,
) {

/**
* Sends a chat message from either a carer or a center user.
*
* Determines the sender's user ID based on the sender type, saves the message with a sequence number, updates unread chat room status, publishes the message, and triggers notification delivery to the recipient.
*
* @param request The chat message request containing message and recipient details.
* @param inputId The UUID of the sender (carer or center manager).
* @param isCarer Indicates whether the sender is a carer (`true`) or a center user (`false`).
*/
@Transactional
fun send(request: SendChatMessageRequest, inputId: UUID, isCarer: Boolean) {
val userId = if(isCarer) inputId else getCenterId(inputId)
Expand All @@ -49,6 +58,15 @@ class ChatFacadeService(
sendNotification(message, request, isCarer)
}

/**
* Sends a push notification for a chat message to the appropriate recipient(s) if they are not actively chatting.
*
* If the sender is a carer, notifications are sent to all center managers of the receiving center who are not currently in the chat. If the sender is a center, a notification is sent to the receiving carer if they are not currently in the chat. No notification is sent if the recipient is actively chatting or if no device token is found.
*
* @param message The chat message to notify about.
* @param request The original chat message request containing sender and receiver information.
* @param isCarer Indicates whether the sender is a carer.
*/
private fun sendNotification(
message: ChatMessage,
request: SendChatMessageRequest,
Expand All @@ -68,12 +86,28 @@ class ChatFacadeService(
}
}

/**
* Retrieves all center managers associated with the specified center ID.
*
* @param centerId The unique identifier of the center.
* @return A list of center managers for the given center, or an empty list if none are found.
*/
private fun getManagersByCenterId(centerId:UUID): List<CenterManager> {
val businessNumber = BusinessRegistrationNumber(centerService.getById(centerId).businessRegistrationNumber)
val centerManagers = centerManagerService.findAllByCenterBusinessRegistrationNumber(businessNumber)?: emptyList()
return centerManagers
}

/**
* Marks chat messages as read for a user in a chat room and updates the read sequence in Redis.
*
* Removes the chat room from the user's unread list, updates the user's read sequence for the chat room,
* and publishes a read event to notify other participants.
*
* @param request The request containing chat room ID, opponent ID, and the sequence number up to which messages are read.
* @param inputId The UUID of the acting user (carer or center manager).
* @param isCarer Indicates whether the acting user is a carer (`true`) or a center manager (`false`).
*/
@Transactional
fun read(request: ReadChatMessagesReqeust, inputId: UUID, isCarer: Boolean) {
val userId = if(isCarer) inputId else getCenterId(inputId)
Expand All @@ -89,13 +123,29 @@ class ChatFacadeService(
chatRedisTemplate.publish(readMessage)
}

/**
* Retrieves the center ID associated with the given center manager ID.
*
* @param managerId The UUID of the center manager.
* @return The UUID of the center managed by the specified manager.
* @throws CenterException.NotFoundException if no center is found for the manager's business registration number.
*/
private fun getCenterId(managerId:UUID): UUID {
val manager = centerManagerService.getById(managerId)
val businessNumber = BusinessRegistrationNumber(manager.centerBusinessRegistrationNumber)
val center = centerService.findByBusinessRegistrationNumber(businessNumber)?: throw CenterException.NotFoundException()
return center.id
}

/**
* Creates a new chat room between a carer and a center based on the sender's role.
*
* Determines the carer and center IDs according to the sender's authentication and the provided opponent ID, then creates the chat room and returns its ID.
*
* @param request The request containing the opponent's ID.
* @param isCarer Indicates whether the sender is a carer.
* @return The response containing the created chat room's ID.
*/
@Transactional
fun createChatroom(request: CreateChatRoomRequest, isCarer: Boolean):CreateChatRoomResponse {
val (carerId, centerId) =
Expand All @@ -110,6 +160,12 @@ class ChatFacadeService(
return CreateChatRoomResponse(chatRoomId)
}

/**
* Retrieves the center ID associated with the currently authenticated center manager.
*
* @return The UUID of the center linked to the authenticated manager.
* @throws CenterException.NotFoundException if no center is found for the manager's business registration number.
*/
fun getCenterIdByAuthentication():UUID {
val managerId = getUserAuthentication().userId
val manager = centerManagerService.getById(managerId)
Expand All @@ -118,6 +174,16 @@ class ChatFacadeService(
return center.id
}

/**
* Retrieves recent chat messages for a chat room along with the opponent's read sequence.
*
* If `messageId` is null, a new UUID is generated to fetch messages. The opponent's ID is determined based on the sender's role, and their read sequence is retrieved from Redis.
*
* @param chatRoomId The ID of the chat room to fetch messages from.
* @param messageId The message ID to start fetching from; if null, a new UUID is used.
* @param isCarer Indicates whether the requester is a carer.
* @return A response containing the list of recent chat messages and the opponent's read sequence.
*/
@Transactional(readOnly = true)
fun getRecentMessages(
chatRoomId: UUID,
Expand All @@ -135,6 +201,14 @@ class ChatFacadeService(
return ChatMessageResponse(messageInfo, opponentReadSequence)
}

/**
* Retrieves a summary of chat rooms for the authenticated user, including unread message counts and opponent details.
*
* For each chat room, the summary includes the last message, adjusted unread message count based on read sequences, and opponent's name and profile image. Opponent information is determined by the user's role (carer or center).
*
* @param isCarer Indicates whether the authenticated user is a carer.
* @return A list of chat room summaries enriched with opponent information and unread message counts.
*/
@Transactional(readOnly = true)
fun getChatroomSummary(isCarer: Boolean): List<ChatRoomSummaryInfo> {
val userId = if (isCarer) getUserAuthentication().userId else getCenterIdByAuthentication()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,30 @@ object PointConverter {
SPATIAL_REFERENCE_IDENTIFIER_NUMBER
)

/**
* Converts the latitude and longitude of a Carer entity into a JTS Point.
*
* The resulting Point uses the longitude as the x-coordinate and latitude as the y-coordinate.
*
* @param carer The Carer entity whose geographic coordinates are to be converted.
* @return A Point representing the Carer's location.
*/
fun convertToPoint(carer: Carer): Point {
val latitude = carer.latitude.toDouble()
val longitude = carer.longitude.toDouble()

return geometryFactory.createPoint(Coordinate(longitude, latitude))
}

/**
* Converts latitude and longitude coordinates to a JTS `Point` object.
*
* The longitude is used as the x-coordinate and the latitude as the y-coordinate.
*
* @param latitude The latitude value.
* @param longitude The longitude value.
* @return A `Point` representing the specified geographic location.
*/
fun convertToPoint(latitude: Double, longitude: Double ): Point {
return geometryFactory.createPoint(Coordinate(longitude, latitude))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,29 @@ class CrawlingJobPostingService(
private val redisJobPostingRepository: RedisJobPostingRepository,
) {

/**
* Retrieves a crawled job posting by its unique identifier.
*
* @param crawlingJobPostingId The UUID of the job posting to retrieve.
* @return The corresponding CrawledJobPosting entity.
* @throws PersistenceException.ResourceNotFound if the job posting does not exist.
*/
fun getById(crawlingJobPostingId: UUID): CrawledJobPosting {
return crawlingJobPostingJpaRepository.findByIdOrNull(crawlingJobPostingId)
?: throw PersistenceException.ResourceNotFound("크롤링한 구인 공고(id=$crawlingJobPostingId)를 찾을 수 없습니다")
}

/**
* Retrieves a list of job posting previews within a specified distance from a given location.
*
* Queries Redis for job posting IDs near the provided location within the given distance, fetches the corresponding postings from the database, and returns preview DTOs for each posting.
*
* @param next Optional UUID for pagination; results start after this posting if provided.
* @param location The geographic point from which to search.
* @param distance The maximum distance from the location, in meters.
* @param limit The maximum number of results to return.
* @return A list of job posting preview DTOs within the specified range.
*/
fun findAllInRange(
next: UUID?,
location: Point,
Expand All @@ -35,6 +53,13 @@ class CrawlingJobPostingService(
}


/**
* Calculates the distance in meters between a job posting's location and a specified carer's location.
*
* @param crawledJobPosting The job posting whose location is used as the starting point.
* @param carerLocation The geographic location to measure the distance to.
* @return The distance in meters as an integer.
*/
fun calculateDistance(
crawledJobPosting: CrawledJobPosting,
carerLocation: Point,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ class CarerPostingFacadeService(
private val jobPostingFavoriteService: JobPostingFavoriteService,
) {

/**
* Retrieves detailed information about a specific job posting for the authenticated carer.
*
* Assembles job posting details including associated weekdays, life assistance types, apply methods, center information, distance from the carer, application status, and favorite status.
*
* @param jobPostingId The unique identifier of the job posting to retrieve.
* @return A response containing comprehensive job posting details tailored to the authenticated carer.
*/
fun getJobPostingDetail(jobPostingId: UUID): CarerJobPostingResponse {
val carer = carerService.getById(getUserAuthentication().userId)
val posting = jobPostingService.getById(jobPostingId)
Expand Down Expand Up @@ -72,6 +80,14 @@ class CarerPostingFacadeService(
)
}

/**
* Retrieves a paginated list of job postings within range of the authenticated carer's location.
*
* Uses the carer's current location to find nearby job postings and returns them in a scrollable response format.
*
* @param request Cursor-based pagination request containing the next cursor and limit.
* @return A scroll response containing job postings near the carer, the next cursor, and the total count in the current page.
*/
fun getJobPostingsInRange(
request: CursorScrollRequest,
): CarerJobPostingScrollResponse {
Expand All @@ -91,6 +107,16 @@ class CarerPostingFacadeService(
)
}

/**
* Retrieves a paginated list of job posting previews within range of the specified location for the authenticated carer.
*
* Calculates the distance from each job posting to the carer's location and determines the next pagination cursor.
*
* @param location The geographic point to search from.
* @param next The pagination cursor indicating the starting point for the next page, or null to start from the beginning.
* @param limit The maximum number of job postings to return.
* @return A pair containing the list of job posting previews (with distances set) and the next cursor UUID, or null if there are no more results.
*/
private fun scrollByCarerLocationInRange(
location: Point,
next: UUID?,
Expand Down Expand Up @@ -126,6 +152,12 @@ class CarerPostingFacadeService(
return items to newNext
}

/**
* Retrieves a paginated list of job postings that the authenticated carer has applied to.
*
* @param request The cursor-based pagination request.
* @return A scroll response containing applied job postings, the next pagination cursor, and the total count.
*/
fun getAppliedJobPostings(request: CursorScrollRequest): CarerAppliedJobPostingScrollResponse {
val (items, next) = scrollByCarerApplyHistory(
next = request.next,
Expand All @@ -140,6 +172,14 @@ class CarerPostingFacadeService(
)
}

/**
* Retrieves a paginated list of job postings the specified carer has applied to, including distance from the carer's current location.
*
* @param carerId The unique identifier of the carer whose application history is being queried.
* @param next The pagination cursor indicating the starting point for the next page, or null to start from the beginning.
* @param limit The maximum number of job postings to return.
* @return A pair containing the list of job posting previews (with distance calculated) and the next pagination cursor, or null if there are no more results.
*/
private fun scrollByCarerApplyHistory(
carerId: UUID,
next: UUID?,
Expand Down Expand Up @@ -172,6 +212,11 @@ class CarerPostingFacadeService(
return items to newNext
}

/**
* Retrieves all job postings favorited by the authenticated carer, including the distance from the carer's current location.
*
* @return A response containing the list of favorite job postings with distance information.
*/
fun getMyFavoriteJobPostings(): JobPostingFavoriteResponse {
val carer = carerService.getById(getUserAuthentication().userId)
val location = PointConverter.convertToPoint(carer)
Expand Down
Loading