Reject duplicate guest documents by hash
All checks were successful
build-and-deploy / build-deploy (push) Successful in 31s
All checks were successful
build-and-deploy / build-deploy (push) Successful in 31s
This commit is contained in:
@@ -0,0 +1,41 @@
|
|||||||
|
package com.android.trisolarisserver.config
|
||||||
|
|
||||||
|
import org.springframework.jdbc.core.JdbcTemplate
|
||||||
|
import org.springframework.stereotype.Component
|
||||||
|
|
||||||
|
@Component
|
||||||
|
class GuestDocumentSchemaFix(
|
||||||
|
private val jdbcTemplate: JdbcTemplate
|
||||||
|
) : PostgresSchemaFix(jdbcTemplate) {
|
||||||
|
|
||||||
|
override fun runPostgres(jdbcTemplate: JdbcTemplate) {
|
||||||
|
val hasTable = jdbcTemplate.queryForObject(
|
||||||
|
"""
|
||||||
|
select count(*)
|
||||||
|
from information_schema.tables
|
||||||
|
where table_name = 'guest_document'
|
||||||
|
""".trimIndent(),
|
||||||
|
Int::class.java
|
||||||
|
) ?: 0
|
||||||
|
if (hasTable == 0) return
|
||||||
|
|
||||||
|
val hasHash = jdbcTemplate.queryForObject(
|
||||||
|
"""
|
||||||
|
select count(*)
|
||||||
|
from information_schema.columns
|
||||||
|
where table_name = 'guest_document'
|
||||||
|
and column_name = 'file_hash'
|
||||||
|
""".trimIndent(),
|
||||||
|
Int::class.java
|
||||||
|
) ?: 0
|
||||||
|
if (hasHash == 0) {
|
||||||
|
logger.info("Adding file_hash to guest_document table")
|
||||||
|
jdbcTemplate.execute(
|
||||||
|
"""
|
||||||
|
alter table guest_document
|
||||||
|
add column file_hash varchar
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,6 +28,7 @@ import java.time.OffsetDateTime
|
|||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
import java.security.MessageDigest
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/properties/{propertyId}/guests/{guestId}/documents")
|
@RequestMapping("/properties/{propertyId}/guests/{guestId}/documents")
|
||||||
@@ -82,6 +83,17 @@ class GuestDocuments(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val stored = storage.store(propertyId, guestId, bookingId, file)
|
val stored = storage.store(propertyId, guestId, bookingId, file)
|
||||||
|
val fileHash = hashFile(stored.storagePath)
|
||||||
|
if (fileHash != null && guestDocumentRepo.existsByPropertyIdAndGuestIdAndBookingIdAndFileHash(
|
||||||
|
propertyId,
|
||||||
|
guestId,
|
||||||
|
bookingId,
|
||||||
|
fileHash
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Files.deleteIfExists(Paths.get(stored.storagePath))
|
||||||
|
throw ResponseStatusException(HttpStatus.CONFLICT, "Duplicate document")
|
||||||
|
}
|
||||||
val document = GuestDocument(
|
val document = GuestDocument(
|
||||||
property = property,
|
property = property,
|
||||||
guest = guest,
|
guest = guest,
|
||||||
@@ -90,7 +102,8 @@ class GuestDocuments(
|
|||||||
originalFilename = stored.originalFilename,
|
originalFilename = stored.originalFilename,
|
||||||
contentType = stored.contentType,
|
contentType = stored.contentType,
|
||||||
sizeBytes = stored.sizeBytes,
|
sizeBytes = stored.sizeBytes,
|
||||||
storagePath = stored.storagePath
|
storagePath = stored.storagePath,
|
||||||
|
fileHash = fileHash
|
||||||
)
|
)
|
||||||
val saved = guestDocumentRepo.save(document)
|
val saved = guestDocumentRepo.save(document)
|
||||||
runExtraction(saved.id!!, propertyId, guestId)
|
runExtraction(saved.id!!, propertyId, guestId)
|
||||||
@@ -236,6 +249,27 @@ class GuestDocuments(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun hashFile(storagePath: String): String? {
|
||||||
|
return try {
|
||||||
|
val path = Paths.get(storagePath)
|
||||||
|
if (!Files.exists(path)) return null
|
||||||
|
val digest = MessageDigest.getInstance("SHA-256")
|
||||||
|
Files.newInputStream(path).use { input ->
|
||||||
|
val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
|
||||||
|
var read = input.read(buffer)
|
||||||
|
while (read >= 0) {
|
||||||
|
if (read > 0) {
|
||||||
|
digest.update(buffer, 0, read)
|
||||||
|
}
|
||||||
|
read = input.read(buffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
digest.digest().joinToString("") { "%02x".format(it) }
|
||||||
|
} catch (_: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class GuestDocumentResponse(
|
data class GuestDocumentResponse(
|
||||||
|
|||||||
@@ -8,4 +8,10 @@ interface GuestDocumentRepo : JpaRepository<GuestDocument, UUID> {
|
|||||||
fun findByPropertyIdAndGuestIdOrderByUploadedAtDesc(propertyId: UUID, guestId: UUID): List<GuestDocument>
|
fun findByPropertyIdAndGuestIdOrderByUploadedAtDesc(propertyId: UUID, guestId: UUID): List<GuestDocument>
|
||||||
fun findByIdAndPropertyIdAndGuestId(id: UUID, propertyId: UUID, guestId: UUID): GuestDocument?
|
fun findByIdAndPropertyIdAndGuestId(id: UUID, propertyId: UUID, guestId: UUID): GuestDocument?
|
||||||
fun existsByGuestId(guestId: UUID): Boolean
|
fun existsByGuestId(guestId: UUID): Boolean
|
||||||
|
fun existsByPropertyIdAndGuestIdAndBookingIdAndFileHash(
|
||||||
|
propertyId: UUID,
|
||||||
|
guestId: UUID,
|
||||||
|
bookingId: UUID,
|
||||||
|
fileHash: String
|
||||||
|
): Boolean
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,9 @@ class GuestDocument(
|
|||||||
@Column(name = "storage_path", nullable = false)
|
@Column(name = "storage_path", nullable = false)
|
||||||
var storagePath: String,
|
var storagePath: String,
|
||||||
|
|
||||||
|
@Column(name = "file_hash")
|
||||||
|
var fileHash: String? = null,
|
||||||
|
|
||||||
@Column(name = "extracted_data", columnDefinition = "jsonb")
|
@Column(name = "extracted_data", columnDefinition = "jsonb")
|
||||||
@JdbcTypeCode(SqlTypes.JSON)
|
@JdbcTypeCode(SqlTypes.JSON)
|
||||||
var extractedData: String? = null,
|
var extractedData: String? = null,
|
||||||
|
|||||||
Reference in New Issue
Block a user