Skip to content

Commit

Permalink
Add GPX files to Sectors (#109)
Browse files Browse the repository at this point in the history
Signed-off-by: Arnau Mora <arnyminerz@proton.me>
  • Loading branch information
ArnyminerZ authored Apr 17, 2024
1 parent f48e9f2 commit fd68274
Show file tree
Hide file tree
Showing 11 changed files with 256 additions and 7 deletions.
2 changes: 1 addition & 1 deletion config/detekt/detekt.yml
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ complexity:
threshold: 600
LongMethod:
active: true
threshold: 60
threshold: 80
LongParameterList:
active: true
functionThreshold: 6
Expand Down
8 changes: 8 additions & 0 deletions src/main/kotlin/database/entity/Sector.kt
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,18 @@ class Sector(id: EntityID<Int>): BaseEntity(id), JsonSerializable {
get() = File(Storage.ImagesDir, _image)
set(value) { _image = value.toRelativeString(Storage.ImagesDir) }

var gpx: File?
get() = _gpx?.let { File(Storage.TracksDir, it) }
set(value) { _gpx = value?.toRelativeString(Storage.TracksDir) }

var point: LatLng?
get() = _latitude?.let { lat -> _longitude?.let { lon -> LatLng(lat, lon) } }
set(value) { _latitude = value?.latitude; _longitude = value?.longitude }

var zone by Zone referencedOn Sectors.zone

private var _image: String by Sectors.imagePath
private var _gpx: String? by Sectors.gpxPath

private var _latitude: Double? by Sectors.latitude
private var _longitude: Double? by Sectors.longitude
Expand All @@ -61,6 +66,7 @@ class Sector(id: EntityID<Int>): BaseEntity(id), JsonSerializable {
* - `sun_time`: [sunTime] ([SunTime])
* - `walking_time`: [walkingTime] ([Int]|`null`)
* - `image`: [image] ([String])
* - `gpx`: [gpx] ([String])
* - `point`: [point] ([String])
* - `zone_id`: [zone] ([Int])
*
Expand All @@ -74,6 +80,7 @@ class Sector(id: EntityID<Int>): BaseEntity(id), JsonSerializable {
"sun_time" to sunTime,
"walking_time" to walkingTime,
"image" to _image.substringBeforeLast('.'),
"gpx" to _gpx?.substringBeforeLast('.'),
"point" to point,
"weight" to weight,
"zone_id" to zone.id.value
Expand Down Expand Up @@ -107,6 +114,7 @@ class Sector(id: EntityID<Int>): BaseEntity(id), JsonSerializable {
result = 31 * result + sunTime.hashCode()
result = 31 * result + (walkingTime?.hashCode() ?: 0)
result = 31 * result + image.hashCode()
result = 31 * result + (gpx?.hashCode() ?: 0)
result = 31 * result + (point?.hashCode() ?: 0)
result = 31 * result + (weight.hashCode())
result = 31 * result + zone.hashCode()
Expand Down
1 change: 1 addition & 0 deletions src/main/kotlin/database/table/Sectors.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ object Sectors : BaseTable() {
val displayName = varchar("display_name", SqlConsts.DISPLAY_NAME_LENGTH)

val imagePath = varchar("image", SqlConsts.FILE_LENGTH)
val gpxPath = varchar("gpx", SqlConsts.FILE_LENGTH).nullable()

val latitude = double("latitude").nullable()
val longitude = double("longitude").nullable()
Expand Down
10 changes: 7 additions & 3 deletions src/main/kotlin/server/endpoints/create/NewSectorEndpoint.kt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ object NewSectorEndpoint : SecureEndpointBase("/sector") {
var zone: Zone? = null

var imageFile: File? = null
var gpxFile: File? = null

receiveMultipart(
forEachFormItem = { partData ->
Expand All @@ -42,25 +43,27 @@ object NewSectorEndpoint : SecureEndpointBase("/sector") {
"walkingTime" -> walkingTime = partData.value.toUIntOrNull()
"weight" -> weight = partData.value
"zone" -> ServerDatabase.instance.query {
zone = Zone.findById(partData.value.toInt())
?: return@query respondFailure(ParentNotFound)
zone = Zone.findById(partData.value.toInt()) ?: return@query respondFailure(ParentNotFound)
}
}
},
forEachFileItem = { partData ->
when (partData.name) {
"image" -> imageFile = partData.save(Storage.ImagesDir)
"gpx" -> gpxFile = partData.save(Storage.TracksDir)
}
}
)

if (isAnyNull(displayName, imageFile, kidsApt, sunTime, zone)) {
imageFile?.delete()
gpxFile?.delete()
return respondFailure(
MissingData,
jsonOf(
"multipart" to rawMultipartFormItems,
"imageFile" to imageFile?.path
"imageFile" to imageFile?.path,
"gpxFile" to gpxFile?.path
).toString()
)
}
Expand All @@ -73,6 +76,7 @@ object NewSectorEndpoint : SecureEndpointBase("/sector") {
this.sunTime = sunTime!!
this.walkingTime = walkingTime
this.image = imageFile!!
this.gpx = gpxFile
weight?.let { this.weight = it }
this.zone = zone!!
}.toJson()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ object PatchSectorEndpoint : SecureEndpointBase("/sector/{sectorId}") {
?: return respondFailure(Errors.ObjectNotFound)

// Nullable types: point, walkingTime
// Nullable files: gpxFile

var displayName: String? = null
var point: LatLng? = null
Expand All @@ -46,6 +47,7 @@ object PatchSectorEndpoint : SecureEndpointBase("/sector/{sectorId}") {
var removeWalkingTime = false

var imageFile: File? = null
var gpxFile: File? = null

receiveMultipart(
forEachFormItem = { partData ->
Expand Down Expand Up @@ -78,11 +80,15 @@ object PatchSectorEndpoint : SecureEndpointBase("/sector/{sectorId}") {
val uuid = ServerDatabase.instance.query { sector.image.nameWithoutExtension }
imageFile = partData.save(Storage.ImagesDir, UUID.fromString(uuid))
}
"gpx" -> {
val uuid = ServerDatabase.instance.query { sector.gpx?.nameWithoutExtension }
gpxFile = partData.save(Storage.TracksDir, uuid?.let(UUID::fromString) ?: UUID.randomUUID())
}
}
}
)

if (areAllNull(displayName, imageFile, kidsApt, point, sunTime, walkingTime, weight, zone) &&
if (areAllNull(displayName, imageFile, gpxFile, kidsApt, point, sunTime, walkingTime, weight, zone) &&
areAllFalse(removePoint, removeWalkingTime)
) {
return respondSuccess(httpStatusCode = HttpStatusCode.NoContent)
Expand Down
4 changes: 2 additions & 2 deletions src/main/kotlin/server/request/MultipartExtensions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ import java.util.UUID
*
* @throws IOException If there's a problem while writing to the file system.
*/
fun PartData.FileItem.save(rootDir: File, uuid: UUID = UUID.randomUUID(), overwrite: Boolean = true): File {
fun PartData.FileItem.save(rootDir: File, uuid: UUID? = null, overwrite: Boolean = true): File {
rootDir.mkdirs()

val fileExtension = originalFileName?.takeLastWhile { it != '.' }
val fileName = "$uuid.$fileExtension"
val fileName = "${uuid ?: UUID.randomUUID()}.$fileExtension"
val targetFile = File(rootDir, fileName)

if (overwrite && targetFile.exists()) {
Expand Down
2 changes: 2 additions & 0 deletions src/test/kotlin/assertions/ResultAssertions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package assertions

import io.ktor.client.statement.HttpResponse
import io.ktor.client.statement.bodyAsText
import io.ktor.client.statement.request
import io.ktor.http.HttpStatusCode
import kotlin.test.assertEquals
import kotlin.test.assertFalse
Expand Down Expand Up @@ -34,6 +35,7 @@ suspend inline fun HttpResponse.assertSuccess(
status,
StringBuilder().apply {
appendLine("expected: $statusCode but was: $status")
appendLine("Url: ${request.url}")
if (errorMessage != null) {
appendLine("Message: $errorMessage")
}
Expand Down
9 changes: 9 additions & 0 deletions src/test/kotlin/server/DataProvider.kt
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ object DataProvider {
skipKidsApt: Boolean = false,
skipSunTime: Boolean = false,
skipImage: Boolean = false,
skipGpx: Boolean = false,
assertion: suspend HttpResponse.() -> Int? = {
var sectorId: Int? = null
assertSuccess(HttpStatusCode.Created) { data ->
Expand All @@ -174,6 +175,9 @@ object DataProvider {
val image = this::class.java.getResourceAsStream("/images/desploms1.jpg")!!.use {
it.readBytes()
}
val gpx = this::class.java.getResourceAsStream("/tracks/ulldelmoro.gpx")!!.use {
it.readBytes()
}

var sectorId: Int?

Expand All @@ -195,6 +199,11 @@ object DataProvider {
append(HttpHeaders.ContentType, "image/jpeg")
append(HttpHeaders.ContentDisposition, "filename=sector.jpg")
})
if (!skipGpx)
append("gpx", gpx, Headers.build {
append(HttpHeaders.ContentType, "application/gpx+xml")
append(HttpHeaders.ContentDisposition, "filename=sector.gpx")
})
}
) {
header(HttpHeaders.Authorization, "Bearer $AUTH_TOKEN")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
import kotlin.test.assertNotNull
import kotlin.test.assertNull
import kotlin.test.assertTrue
import server.DataProvider
import server.base.ApplicationTestBase
Expand Down Expand Up @@ -39,6 +40,42 @@ class TestSectorCreationEndpoint: ApplicationTestBase() {
val imageFile = sector.image
assertTrue(imageFile.exists())

val gpxFile = sector.gpx
assertNotNull(gpxFile)
assertTrue(gpxFile.exists())

assertNotEquals(LastUpdate.get(), lastUpdate)
}
}

@Test
fun `test sector creation - without gpx`() = test {
val lastUpdate = ServerDatabase.instance.query { LastUpdate.get() }

val areaId: Int? = DataProvider.provideSampleArea()
assertNotNull(areaId)

val zoneId: Int? = DataProvider.provideSampleZone(areaId)
assertNotNull(zoneId)

val sectorId: Int? = DataProvider.provideSampleSector(zoneId, skipGpx = true)
assertNotNull(sectorId)

ServerDatabase.instance.query {
val sector = Sector[sectorId]
assertNotNull(sector)
assertEquals(DataProvider.SampleSector.displayName, sector.displayName)
assertEquals(DataProvider.SampleSector.point, sector.point)
assertEquals(DataProvider.SampleSector.kidsApt, sector.kidsApt)
assertEquals(DataProvider.SampleSector.walkingTime, sector.walkingTime)
assertEquals(DataProvider.SampleSector.sunTime, sector.sunTime)

val imageFile = sector.image
assertTrue(imageFile.exists())

val gpxFile = sector.gpx
assertNull(gpxFile)

assertNotEquals(LastUpdate.get(), lastUpdate)
}
}
Expand Down
60 changes: 60 additions & 0 deletions src/test/kotlin/server/endpoints/patch/TestPatchSectorEndpoint.kt
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,64 @@ class TestPatchSectorEndpoint : ApplicationTestBase() {
}
}

@Test
fun `test patching Sector - update gpx`() = test {
val areaId = DataProvider.provideSampleArea()
assertNotNull(areaId)

val zoneId = DataProvider.provideSampleZone(areaId)
assertNotNull(zoneId)

val sectorId = DataProvider.provideSampleSector(zoneId)
assertNotNull(sectorId)

val oldTimestamp = ServerDatabase.instance.query { Sector[sectorId].timestamp }

val gpx = this::class.java.getResourceAsStream("/tracks/ulldelmoro.gpx")!!.use {
it.readBytes()
}

client.submitFormWithBinaryData(
url = "/sector/$sectorId",
formData = formData {
append("gpx", gpx, Headers.build {
append(HttpHeaders.ContentType, "application/gpx+xml")
append(HttpHeaders.ContentDisposition, "filename=sector.gpx")
})
}
) {
header(HttpHeaders.Authorization, "Bearer $AUTH_TOKEN")
}.apply {
assertSuccess()
}

var sectorGpx: String? = null

ServerDatabase.instance.query {
val sector = Sector[sectorId]
assertNotNull(sector)

val gpxFile = sector.gpx
assertNotNull(gpxFile)
sectorGpx = gpxFile.toRelativeString(Storage.TracksDir)
assertTrue(gpxFile.exists())

assertNotEquals(oldTimestamp, sector.timestamp)
}

get("/file/$sectorGpx").apply {
assertSuccess { data ->
assertNotNull(data)
val serverHash = data.getString("hash")
val localHash = HashUtils.getCheckSumFromStream(
MessageDigest.getInstance(MessageDigestAlgorithm.SHA_256),
this::class.java.getResourceAsStream("/tracks/ulldelmoro.gpx")!!
)
assertEquals(localHash, serverHash)
}
}
}

@Test
fun `test patching Sector - remove walking time`() = test {
val areaId = DataProvider.provideSampleArea()
Expand Down Expand Up @@ -304,6 +362,8 @@ class TestPatchSectorEndpoint : ApplicationTestBase() {
val sector = Sector[sectorId]
assertNotNull(sector)
assertNull(sector.walkingTime)

assertNotEquals(oldTimestamp, sector.timestamp)
}
}

Expand Down
Loading

0 comments on commit fd68274

Please sign in to comment.