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 )