Skip to content

Commit

Permalink
Merge pull request #2 from IZIVIA/IDEV-130
Browse files Browse the repository at this point in the history
Tokens module
  • Loading branch information
lilgallon authored Jul 19, 2022
2 parents 22f4160 + 11361a0 commit 856ae55
Show file tree
Hide file tree
Showing 68 changed files with 4,542 additions and 81 deletions.
23 changes: 12 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Examples:
```kotlin
// Http4kTransportServer is an implementation of TransportServer using htt4k. You have to code your own implementation.
// It defines the HTTP server, and how to handle requests.
// You can see an example in the list above
// You can see an example in the list above
val server = Http4kTransportServer(
baseUrl = config.baseUrl, // Example: http://localhost:8080, only used for pagination
port = config.port // Example: 8080, used to know on which port the server will run
Expand All @@ -28,14 +28,14 @@ val server = Http4kTransportServer(
// PlatformMongoRepository is an implementation of PlatformRepository using mongo
// It will be used to store information about platforms with whom the server is communicating:
// A platform has: Tokens (A, B, C), Endpoints, Versions
// You can see an example in the list above
// You can see an example in the list above
val platformRepository = PlatformMongoRepository(
collection = mongoDatabase.getCollection<Location>(config.platformCollection)
)

// VersionsCacheRepository is an implementation of VersionsRepository
// It defines which OCPI version the server support, and the endpoints associated with it
// You can see an example in the list above
// You can see an example in the list above
val versionsRepository = VersionsCacheRepository()

// VersionDetailsCacheRepository is an implementation of VersionDetailRepository
Expand Down Expand Up @@ -154,14 +154,14 @@ Examples:
// PlatformMongoRepository is an implementation of PlatformRepository using mongo
// It will be used to store information about platforms with whom the client is communicating:
// A platform has: Tokens (A, B, C), Endpoints, Versions
// You can see an example in the list above
// You can see an example in the list above
val senderPlatformRepository = PlatformMongoRepository(
collection = mongoDatabase.getCollection<Location>(config.platformCollection)
)

// VersionsCacheRepository is an implementation of VersionsRepository
// It defines which OCPI version the client supports, and the endpoints associated with it
// You can see an example in the list above
// You can see an example in the list above
val senderVersionsRepository = VersionsCacheRepository()

// Will be sent during registration for the receiver to use to request what versions the sender supports
Expand Down Expand Up @@ -240,9 +240,10 @@ What actually changed in the lib:

### ocpi-lib-2.1.1 -> ocpi-lib-2.1.1-gireve

| Module | Changements |
|-------------|------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Common | nothing changed |
| Versions | - Removed V_2_0 and V_2_1 in VersionNumber enum |
| Credentials | - Removed PUT (the user has to first delete() then register() to update (so token A has to be exchanged outside OCPI protocol between delete and register) |
| Locations | - evse_id is now required (added doc to explain that in Locations object) |
| Module | Changements |
|-------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Common | nothing changed |
| Versions | - Removed V_2_0 and V_2_1 in VersionNumber enum |
| Credentials | - Removed PUT (the user has to first delete() then register() to update (so token A has to be exchanged outside OCPI protocol between delete and register) |
| Locations | - evse_id is now required (added doc to explain that in Locations object) |
| Tokens | - Too many changements, see [this commit for an insight (things may have changed since then)](https://github.com/4sh/ocpi-lib/commit/4f664247a888dcc475270f92855a191e13b42342) |
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,13 @@ data class OcpiResponseBody<T>(
timestamp = Instant.now()
)

fun <T> notEnoughInformation(message: String) = OcpiResponseBody<T>(
data = null,
status_code = OcpiStatus.CLIENT_NOT_ENOUGH_INFORMATION.code,
status_message = message,
timestamp = Instant.now()
)

fun <T> of(data: () -> T) =
try {
success(data = data())
Expand Down Expand Up @@ -148,4 +155,4 @@ fun <T> HttpRequest.httpResponse(fn: () -> OcpiResponseBody<T>): HttpResponse =
HttpResponse(
status = HttpStatus.BAD_REQUEST
)
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.izivia.ocpi.toolkit.modules.locations.domain

import com.izivia.ocpi.toolkit.annotations.Partial
import com.izivia.ocpi.toolkit.modules.types.DisplayText

/**
* This class defines an additional geolocation that is relevant for the Charge Point. The geodetic system to be used
Expand All @@ -18,4 +19,4 @@ data class AdditionalGeoLocation(
val latitude: String,
val longitude: String,
val name: DisplayText?
)
)
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.izivia.ocpi.toolkit.modules.locations.domain

import com.izivia.ocpi.toolkit.annotations.Partial
import com.izivia.ocpi.toolkit.modules.types.DisplayText
import java.time.Instant

/**
Expand Down Expand Up @@ -52,4 +53,4 @@ data class Evse(
val parking_restrictions: List<ParkingRestriction>?,
val images: List<Image>?,
val last_updated: Instant
)
)
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.izivia.ocpi.toolkit.modules.locations.domain

import com.izivia.ocpi.toolkit.annotations.Partial
import com.izivia.ocpi.toolkit.modules.types.DisplayText
import java.time.Instant

/**
Expand Down Expand Up @@ -59,4 +60,4 @@ data class Location(
val images: List<Image>?,
val energy_mix: EnergyMix?,
val last_updated: Instant
)
)
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package com.izivia.ocpi.toolkit.modules.locations.validation

import com.izivia.ocpi.toolkit.common.validation.*
import com.izivia.ocpi.toolkit.modules.locations.domain.*
import com.izivia.ocpi.toolkit.modules.types.DisplayText
import com.izivia.ocpi.toolkit.modules.types.DisplayTextPartial
import com.izivia.ocpi.toolkit.modules.types.toPartial
import org.valiktor.DefaultConstraintViolation
import org.valiktor.constraints.Greater
import org.valiktor.constraints.NotNull
Expand Down Expand Up @@ -222,4 +225,4 @@ fun Evse.validate(): Evse = validate(this) {
}
fun Connector.validate(): Connector = validate(this) {
toPartial().validate()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package com.izivia.ocpi.toolkit.modules.tokens

import com.izivia.ocpi.toolkit.common.*
import com.izivia.ocpi.toolkit.modules.credentials.repositories.PlatformRepository
import com.izivia.ocpi.toolkit.modules.tokens.domain.AuthorizationInfo
import com.izivia.ocpi.toolkit.modules.tokens.domain.LocationReferences
import com.izivia.ocpi.toolkit.modules.tokens.domain.Token
import com.izivia.ocpi.toolkit.modules.tokens.domain.TokenType
import com.izivia.ocpi.toolkit.modules.versions.domain.ModuleID
import com.izivia.ocpi.toolkit.transport.TransportClient
import com.izivia.ocpi.toolkit.transport.TransportClientBuilder
import com.izivia.ocpi.toolkit.transport.domain.HttpMethod
import com.izivia.ocpi.toolkit.transport.domain.HttpRequest
import java.time.Instant

class TokensCpoClient(
private val transportClientBuilder: TransportClientBuilder,
private val serverVersionsEndpointUrl: String,
private val platformRepository: PlatformRepository
) : TokensEmspInterface {

private fun buildTransport(): TransportClient = transportClientBuilder
.buildFor(
module = ModuleID.tokens,
platform = serverVersionsEndpointUrl,
platformRepository = platformRepository
)

override fun getTokens(
dateFrom: Instant?,
dateTo: Instant?,
offset: Int,
limit: Int?,
countryCode: String?,
partyId: String?
): OcpiResponseBody<SearchResult<Token>> =
buildTransport()
.send(
HttpRequest(
method = HttpMethod.GET,
queryParams = listOfNotNull(
dateFrom?.let { "date_from" to dateFrom.toString() },
dateTo?.let { "date_to" to dateTo.toString() },
"offset" to offset.toString(),
limit?.let { "limit" to limit.toString() },
countryCode?.let { "ocpi-to-country-code" to countryCode },
partyId?.let { "ocpi-to-party-id" to partyId }
).toMap()
).authenticate(platformRepository = platformRepository, baseUrl = serverVersionsEndpointUrl)
)
.parsePaginatedBody(offset)

override fun getToken(tokenUid: String, tokenType: TokenType): OcpiResponseBody<Token?> =
buildTransport()
.send(
HttpRequest(
method = HttpMethod.GET,
path = "/$tokenUid",
queryParams = mapOf("type" to tokenType.name),
).authenticate(platformRepository = platformRepository, baseUrl = serverVersionsEndpointUrl)
)
.parseBody()

override fun postToken(
tokenUid: String,
tokenType: TokenType,
locationReferences: LocationReferences
): OcpiResponseBody<AuthorizationInfo> =
buildTransport()
.send(
HttpRequest(
method = HttpMethod.POST,
path = "/$tokenUid/authorize",
queryParams = mapOf("type" to tokenType.name),
body = locationReferences.run(mapper::writeValueAsString)
).authenticate(platformRepository = platformRepository, baseUrl = serverVersionsEndpointUrl)
)
.parseBody()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.izivia.ocpi.toolkit.modules.tokens

import com.izivia.ocpi.toolkit.common.OcpiResponseBody
import com.izivia.ocpi.toolkit.modules.tokens.domain.Token
import com.izivia.ocpi.toolkit.modules.tokens.domain.TokenPartial

/**
* With this interface the eMSP can push the Token information to the CPO. Tokens is a client owned object, so the
* end-points need to contain the required extra fields: {party_id} and {country_code}. Example endpoint structure:
* /ocpi/cpo/2.0/tokens/{country_code}/{party_id}/{token_uid}
*
* - GET: Retrieve a Token as it is stored in the CPO system.
* - POST: n/a
* - PUT: Push new/updated Token object to the CPO.
* - PATCH: Notify the CPO of partial updates to a Token.
* - DELETE: n/a, (Use PUT, Tokens cannot be removed).
*/
interface TokensCpoInterface {

/**
* If the eMSP wants to check the status of a Token in the CPO system it might GET the object from the CPO system
* for validation purposes. The eMSP is the owner of the objects, so it would be illogical if the CPO system had a
* different status or was missing an object.
*
* @param countryCode (max-length 2) Country code of the eMSP requesting this GET from the CPO system.
* @param partyId (max-length 3) Party ID (Provider ID) of the eMSP requesting this GET from the CPO system.
* @param tokenUid (max-length 36) Token.uid of the Token object to retrieve.
* @return The requested Token object.
*/
fun getToken(
countryCode: String,
partyId: String,
tokenUid: String
): OcpiResponseBody<Token?>

/**
* New or updated Token objects are pushed from the eMSP to the CPO.
*
* @param countryCode (max-length 2) Country code of the eMSP sending this PUT request to the CPO system.
* @param partyId (max-length 3) Party ID (Provider ID) of the eMSP sending this PUT request to the CPO system.
* @param tokenUid (max-length 36) Token.uid of the (new) Token object (to replace).
* @param token New or updated Token object.
*/
fun putToken(
countryCode: String,
partyId: String,
tokenUid: String,
token: Token
): OcpiResponseBody<Token>

/**
* Same as the PUT method, but only the fields/objects that have to be updated have to be present, other
* fields/objects that are not specified are considered unchanged.
*
* @param countryCode (max-length 2) Country code of the eMSP sending this PUT request to the CPO system.
* @param partyId (max-length 3) Party ID (Provider ID) of the eMSP sending this PUT request to the CPO system.
* @param tokenUid (max-length 36) Token.uid of the (new) Token object (to replace).
* @param token New or updated Token object.
*/
fun patchToken(
countryCode: String,
partyId: String,
tokenUid: String,
token: TokenPartial
): OcpiResponseBody<Token>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package com.izivia.ocpi.toolkit.modules.tokens

import com.izivia.ocpi.toolkit.common.httpResponse
import com.izivia.ocpi.toolkit.common.mapper
import com.izivia.ocpi.toolkit.common.tokenFilter
import com.izivia.ocpi.toolkit.modules.credentials.repositories.PlatformRepository
import com.izivia.ocpi.toolkit.modules.tokens.domain.Token
import com.izivia.ocpi.toolkit.modules.tokens.domain.TokenPartial
import com.izivia.ocpi.toolkit.transport.TransportServer
import com.izivia.ocpi.toolkit.transport.domain.FixedPathSegment
import com.izivia.ocpi.toolkit.transport.domain.HttpMethod
import com.izivia.ocpi.toolkit.transport.domain.VariablePathSegment

class TokensCpoServer(
private val transportServer: TransportServer,
private val platformRepository: PlatformRepository,
private val service: TokensCpoInterface,
basePath: List<FixedPathSegment> = listOf(
FixedPathSegment("/2.1.1/tokens")
)
) {
init {
transportServer.handle(
method = HttpMethod.GET,
path = basePath + listOf(
VariablePathSegment("countryCode"),
VariablePathSegment("partyId"),
VariablePathSegment("tokenUid")
),
filters = listOf(platformRepository::tokenFilter)
) { req ->
req.httpResponse {
service
.getToken(
countryCode = req.pathParams["countryCode"]!!,
partyId = req.pathParams["partyId"]!!,
tokenUid = req.pathParams["tokenUid"]!!
)
}
}

transportServer.handle(
method = HttpMethod.PUT,
path = basePath + listOf(
VariablePathSegment("countryCode"),
VariablePathSegment("partyId"),
VariablePathSegment("tokenUid")
),
filters = listOf(platformRepository::tokenFilter)
) { req ->
req.httpResponse {
service
.putToken(
countryCode = req.pathParams["countryCode"]!!,
partyId = req.pathParams["partyId"]!!,
tokenUid = req.pathParams["tokenUid"]!!,
token = mapper.readValue(req.body, Token::class.java)
)
}
}

transportServer.handle(
method = HttpMethod.PATCH,
path = basePath + listOf(
VariablePathSegment("countryCode"),
VariablePathSegment("partyId"),
VariablePathSegment("tokenUid")
),
filters = listOf(platformRepository::tokenFilter)
) { req ->
req.httpResponse {
service
.patchToken(
countryCode = req.pathParams["countryCode"]!!,
partyId = req.pathParams["partyId"]!!,
tokenUid = req.pathParams["tokenUid"]!!,
token = mapper.readValue(req.body!!, TokenPartial::class.java)
)
}
}
}
}
Loading

0 comments on commit 856ae55

Please sign in to comment.