Skip to content

Commit e30efa1

Browse files
authored
All fetch endpoints now have conditional headers (#176)
Signed-off-by: Arnau Mora Gras <[email protected]>
1 parent f5cf508 commit e30efa1

File tree

15 files changed

+179
-5
lines changed

15 files changed

+179
-5
lines changed

src/main/kotlin/database/entity/Path.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ class Path(id: EntityID<Int>): BaseEntity(id), ResponseData {
141141
result = 31 * result + (builder?.hashCode() ?: 0)
142142
result = 31 * result + (reBuilder?.hashCode() ?: 0)
143143
result = 31 * result + (_images?.hashCode() ?: 0)
144-
result = 31 * result + sector.hashCode()
144+
result = 31 * result + sector.id.value.hashCode()
145145
return result
146146
}
147147

src/main/kotlin/database/entity/Sector.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ class Sector(id: EntityID<Int>): BaseEntity(id), ResponseData {
8888
result = 31 * result + (gpx?.hashCode() ?: 0)
8989
result = 31 * result + (point?.hashCode() ?: 0)
9090
result = 31 * result + (weight.hashCode())
91-
result = 31 * result + zone.hashCode()
91+
result = 31 * result + zone.id.value.hashCode()
9292
return result
9393
}
9494

src/main/kotlin/database/entity/Zone.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ class Zone(id: EntityID<Int>): DataEntity(id), ResponseData {
100100
result = 31 * result + webUrl.toString().hashCode()
101101
result = 31 * result + (point?.hashCode() ?: 0)
102102
result = 31 * result + points.hashCode()
103-
result = 31 * result + area.hashCode()
103+
result = 31 * result + area.id.value.hashCode()
104104
return result
105105
}
106106

src/main/kotlin/server/endpoints/query/AreaEndpoint.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,15 @@ package server.endpoints.query
22

33
import ServerDatabase
44
import database.entity.Area
5+
import io.ktor.http.HttpHeaders
56
import io.ktor.server.plugins.ParameterConversionException
7+
import io.ktor.server.response.header
68
import io.ktor.server.routing.RoutingContext
79
import io.ktor.server.util.getValue
810
import server.endpoints.EndpointBase
911
import server.error.Errors
12+
import server.response.ResourceId
13+
import server.response.ResourceType
1014
import server.response.respondFailure
1115
import server.response.respondSuccess
1216

@@ -22,6 +26,9 @@ object AreaEndpoint : EndpointBase("/area/{areaId}") {
2226
val area = ServerDatabase.instance.query { Area.findById(areaId) }
2327
?: return respondFailure(Errors.ObjectNotFound)
2428

29+
call.response.header(HttpHeaders.ResourceType, "Area")
30+
call.response.header(HttpHeaders.ResourceId, areaId.toString())
31+
2532
respondSuccess(data = area)
2633
}
2734
}

src/main/kotlin/server/endpoints/query/PathEndpoint.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,14 @@ import database.entity.Path
55
import io.ktor.http.HttpHeaders
66
import io.ktor.server.plugins.ParameterConversionException
77
import io.ktor.server.request.header
8+
import io.ktor.server.response.header
89
import io.ktor.server.routing.RoutingContext
910
import io.ktor.server.util.getValue
1011
import localization.Localization
1112
import server.endpoints.EndpointBase
1213
import server.error.Errors
14+
import server.response.ResourceId
15+
import server.response.ResourceType
1316
import server.response.respondFailure
1417
import server.response.respondSuccess
1518

@@ -37,6 +40,9 @@ object PathEndpoint : EndpointBase("/path/{pathId}") {
3740
}
3841
}
3942

43+
call.response.header(HttpHeaders.ResourceType, "Path")
44+
call.response.header(HttpHeaders.ResourceId, pathId.toString())
45+
4046
respondSuccess(path)
4147
}
4248
}

src/main/kotlin/server/endpoints/query/SectorEndpoint.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,15 @@ package server.endpoints.query
22

33
import ServerDatabase
44
import database.entity.Sector
5+
import io.ktor.http.HttpHeaders
56
import io.ktor.server.plugins.ParameterConversionException
7+
import io.ktor.server.response.header
68
import io.ktor.server.routing.RoutingContext
79
import io.ktor.server.util.getValue
810
import server.endpoints.EndpointBase
911
import server.error.Errors
12+
import server.response.ResourceId
13+
import server.response.ResourceType
1014
import server.response.respondFailure
1115
import server.response.respondSuccess
1216

@@ -22,6 +26,9 @@ object SectorEndpoint : EndpointBase("/sector/{sectorId}") {
2226
val sector = ServerDatabase.instance.query { Sector.findById(sectorId) }
2327
?: return respondFailure(Errors.ObjectNotFound)
2428

29+
call.response.header(HttpHeaders.ResourceType, "Sector")
30+
call.response.header(HttpHeaders.ResourceId, sectorId.toString())
31+
2532
respondSuccess(sector)
2633
}
2734
}

src/main/kotlin/server/endpoints/query/TreeEndpoint.kt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,41 @@ import database.entity.Area
55
import database.entity.Path
66
import database.entity.Sector
77
import database.entity.Zone
8+
import database.table.Areas
9+
import database.table.Paths
10+
import database.table.Sectors
11+
import database.table.Zones
12+
import io.ktor.http.HttpHeaders
13+
import io.ktor.server.response.header
814
import io.ktor.server.routing.RoutingContext
15+
import org.jetbrains.exposed.sql.SortOrder
916
import server.endpoints.EndpointBase
1017
import server.response.query.TreeResponseData
1118
import server.response.respondSuccess
1219

1320
object TreeEndpoint : EndpointBase("/tree") {
21+
@OptIn(ExperimentalStdlibApi::class)
1422
override suspend fun RoutingContext.endpoint() {
23+
val lastUpdatedArea = ServerDatabase {
24+
Area.all().orderBy(Areas.timestamp to SortOrder.DESC).limit(1).firstOrNull()
25+
}
26+
val lastUpdatedZone = ServerDatabase {
27+
Zone.all().orderBy(Zones.timestamp to SortOrder.DESC).limit(1).firstOrNull()
28+
}
29+
val lastUpdatedSector = ServerDatabase {
30+
Sector.all().orderBy(Sectors.timestamp to SortOrder.DESC).limit(1).firstOrNull()
31+
}
32+
val lastUpdatedPath = ServerDatabase {
33+
Path.all().orderBy(Paths.timestamp to SortOrder.DESC).limit(1).firstOrNull()
34+
}
35+
val lastUpdate = listOf(lastUpdatedArea, lastUpdatedZone, lastUpdatedSector, lastUpdatedPath)
36+
.maxByOrNull { it?.timestamp?.epochSecond ?: 0 }
37+
38+
if (lastUpdate != null) {
39+
call.response.header(HttpHeaders.LastModified, lastUpdate.timestamp)
40+
call.response.header(HttpHeaders.ETag, ServerDatabase { lastUpdate.hashCode().toHexString() })
41+
}
42+
1543
val array = ServerDatabase {
1644
val zones = Zone.all().toList()
1745
val sectors = Sector.all().toList()

src/main/kotlin/server/endpoints/query/ZoneEndpoint.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,15 @@ package server.endpoints.query
22

33
import ServerDatabase
44
import database.entity.Zone
5+
import io.ktor.http.HttpHeaders
56
import io.ktor.server.plugins.ParameterConversionException
7+
import io.ktor.server.response.header
68
import io.ktor.server.routing.RoutingContext
79
import io.ktor.server.util.getValue
810
import server.endpoints.EndpointBase
911
import server.error.Errors
12+
import server.response.ResourceId
13+
import server.response.ResourceType
1014
import server.response.respondFailure
1115
import server.response.respondSuccess
1216

@@ -22,6 +26,9 @@ object ZoneEndpoint : EndpointBase("/zone/{zoneId}") {
2226
val zone = ServerDatabase.instance.query { Zone.findById(zoneId) }
2327
?: return respondFailure(Errors.ObjectNotFound)
2428

29+
call.response.header(HttpHeaders.ResourceType, "Zone")
30+
call.response.header(HttpHeaders.ResourceId, zoneId.toString())
31+
2532
respondSuccess(zone)
2633
}
2734
}

src/main/kotlin/server/plugins/ConditionalHeaders.kt

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,30 @@
11
package server.plugins
22

3+
import ServerDatabase
4+
import database.entity.Area
5+
import database.entity.Path
6+
import database.entity.Sector
7+
import database.entity.Zone
38
import io.ktor.http.HttpHeaders
49
import io.ktor.http.content.EntityTagVersion
510
import io.ktor.server.http.content.LastModifiedVersion
611
import io.ktor.server.plugins.conditionalheaders.ConditionalHeadersConfig
712
import java.security.MessageDigest
813
import server.response.FileSource
914
import server.response.FileUUID
15+
import server.response.ResourceId
16+
import server.response.ResourceType
1017
import storage.FileType
1118
import storage.HashUtils
1219
import storage.MessageDigestAlgorithm
1320

21+
@OptIn(ExperimentalStdlibApi::class)
1422
fun ConditionalHeadersConfig.configure() {
1523
version { call, outgoingContent ->
16-
val fileUUID = call.response.headers[HttpHeaders.FileUUID]
17-
val fileSource = call.response.headers[HttpHeaders.FileSource]
24+
val headers = call.response.headers
25+
26+
val fileUUID = headers[HttpHeaders.FileUUID]
27+
val fileSource = headers[HttpHeaders.FileSource]
1828
val fileType = FileType.entries.find { it.headerValue == fileSource }
1929
if (fileUUID != null && fileType != null) {
2030
val file = fileType.fetcher(fileUUID)?.takeIf { it.exists() }
@@ -31,6 +41,28 @@ fun ConditionalHeadersConfig.configure() {
3141
}
3242
}
3343

44+
val resourceType = headers[HttpHeaders.ResourceType]
45+
val resourceId = headers[HttpHeaders.ResourceId]?.toIntOrNull()
46+
if (resourceType != null && resourceId != null) {
47+
val resource = ServerDatabase {
48+
when (resourceType) {
49+
"Area" -> Area[resourceId]
50+
"Zone" -> Zone[resourceId]
51+
"Sector" -> Sector[resourceId]
52+
"Path" -> Path[resourceId]
53+
else -> null
54+
}
55+
}
56+
if (resource != null) {
57+
return@version listOfNotNull(
58+
EntityTagVersion(
59+
ServerDatabase { resource.hashCode().toHexString() }
60+
),
61+
LastModifiedVersion(resource.timestamp.toEpochMilli())
62+
)
63+
}
64+
}
65+
3466
emptyList()
3567
}
3668
}

src/main/kotlin/server/response/CustomResponseHeaders.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,17 @@ val HttpHeaders.FileUUID: String get() = "X-File-UUID"
1515
* @see FileType
1616
*/
1717
val HttpHeaders.FileSource: String get() = "X-File-Source"
18+
19+
/**
20+
* A header included in the responses of data requests, containing the type of the data. One of:
21+
* - `Area`
22+
* - `Zone`
23+
* - `Sector`
24+
* - `Path`
25+
*/
26+
val HttpHeaders.ResourceType: String get() = "X-Resource-Type"
27+
28+
/**
29+
* A header included in the responses of data requests, containing the ID of the data.
30+
*/
31+
val HttpHeaders.ResourceId: String get() = "X-Resource-Id"

0 commit comments

Comments
 (0)