Files
TrisolarisServer/src/main/kotlin/com/android/trisolarisserver/controller/InboundEmailManual.kt
androidlover5842 6b6d84e40a more codes
2026-01-24 22:22:37 +05:30

99 lines
3.8 KiB
Kotlin

package com.android.trisolarisserver.controller
import com.android.trisolarisserver.component.EmailStorage
import com.android.trisolarisserver.component.PropertyAccess
import com.android.trisolarisserver.db.repo.InboundEmailRepo
import com.android.trisolarisserver.models.booking.InboundEmail
import com.android.trisolarisserver.models.booking.InboundEmailStatus
import com.android.trisolarisserver.models.property.Role
import com.android.trisolarisserver.repo.PropertyRepo
import com.android.trisolarisserver.security.MyPrincipal
import com.android.trisolarisserver.service.EmailIngestionService
import org.apache.pdfbox.pdmodel.PDDocument
import org.apache.pdfbox.text.PDFTextStripper
import org.springframework.http.HttpStatus
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.ResponseStatus
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.multipart.MultipartFile
import org.springframework.web.server.ResponseStatusException
import java.time.OffsetDateTime
import java.util.UUID
@RestController
@RequestMapping("/properties/{propertyId}/inbound-emails")
class InboundEmailManual(
private val propertyAccess: PropertyAccess,
private val propertyRepo: PropertyRepo,
private val inboundEmailRepo: InboundEmailRepo,
private val emailStorage: EmailStorage,
private val emailIngestionService: EmailIngestionService
) {
@PostMapping("/manual")
@ResponseStatus(HttpStatus.CREATED)
fun uploadManualPdf(
@PathVariable propertyId: UUID,
@AuthenticationPrincipal principal: MyPrincipal?,
@RequestParam("file") file: MultipartFile
): ManualInboundResponse {
requirePrincipal(principal)
propertyAccess.requireMember(propertyId, principal!!.userId)
propertyAccess.requireAnyRole(propertyId, principal.userId, Role.ADMIN, Role.MANAGER)
if (file.isEmpty) {
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "File is empty")
}
val contentType = file.contentType
if (contentType != null && !contentType.equals("application/pdf", ignoreCase = true)) {
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Only PDF is supported")
}
val property = propertyRepo.findById(propertyId).orElseThrow {
ResponseStatusException(HttpStatus.NOT_FOUND, "Property not found")
}
val bytes = file.bytes
val pdfPath = emailStorage.storeUploadedPdf(propertyId, file.originalFilename, bytes)
val text = extractPdfText(bytes)
val inbound = InboundEmail(
property = property,
messageId = "manual-${UUID.randomUUID()}",
subject = file.originalFilename ?: "manual-upload",
fromAddress = null,
receivedAt = OffsetDateTime.now(),
status = InboundEmailStatus.PENDING,
rawPdfPath = pdfPath
)
inboundEmailRepo.save(inbound)
emailIngestionService.ingestManualPdf(property, inbound, text)
return ManualInboundResponse(inboundId = inbound.id!!)
}
private fun extractPdfText(bytes: ByteArray): String {
return try {
PDDocument.load(bytes).use { doc ->
PDFTextStripper().getText(doc)
}
} catch (_: Exception) {
""
}
}
private fun requirePrincipal(principal: MyPrincipal?) {
if (principal == null) {
throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "Missing principal")
}
}
}
data class ManualInboundResponse(
val inboundId: UUID
)