feat(android): EventsApi + wire types for /api/events
Retrofit interface + the four request body variants (play_started / play_ended / play_skipped / play_offline) matching flutter_client/lib/api/endpoints/events.dart. play_started's response carries play_event_id (nullable). Not wired into any caller yet — PlayEventsReporter lands later. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,31 @@
|
|||||||
|
package com.fabledsword.minstrel.api.endpoints
|
||||||
|
|
||||||
|
import com.fabledsword.minstrel.models.wire.PlayEndedRequest
|
||||||
|
import com.fabledsword.minstrel.models.wire.PlayOfflineRequest
|
||||||
|
import com.fabledsword.minstrel.models.wire.PlaySkippedRequest
|
||||||
|
import com.fabledsword.minstrel.models.wire.PlayStartedRequest
|
||||||
|
import com.fabledsword.minstrel.models.wire.PlayStartedResponse
|
||||||
|
import retrofit2.http.Body
|
||||||
|
import retrofit2.http.POST
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrofit interface for `POST /api/events`. Mirrors the relevant
|
||||||
|
* slice of `flutter_client/lib/api/endpoints/events.dart`. All four
|
||||||
|
* variants share the same URL — the discriminator is in the request
|
||||||
|
* body's `type` field. Server contract is best-effort per spec;
|
||||||
|
* callers (the live path in PlayEventsReporter) swallow errors and
|
||||||
|
* fall back to the offline mutation queue on failure.
|
||||||
|
*/
|
||||||
|
interface EventsApi {
|
||||||
|
@POST("api/events")
|
||||||
|
suspend fun playStarted(@Body body: PlayStartedRequest): PlayStartedResponse
|
||||||
|
|
||||||
|
@POST("api/events")
|
||||||
|
suspend fun playEnded(@Body body: PlayEndedRequest)
|
||||||
|
|
||||||
|
@POST("api/events")
|
||||||
|
suspend fun playSkipped(@Body body: PlaySkippedRequest)
|
||||||
|
|
||||||
|
@POST("api/events")
|
||||||
|
suspend fun playOffline(@Body body: PlayOfflineRequest)
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package com.fabledsword.minstrel.models.wire
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wire shapes for `POST /api/events`. The endpoint multiplexes four
|
||||||
|
* variants on the `type` discriminator field, mirroring
|
||||||
|
* `flutter_client/lib/api/endpoints/events.dart`.
|
||||||
|
*
|
||||||
|
* play_started returns the server-assigned play_event_id (nullable —
|
||||||
|
* server may suppress under certain conditions); the other three
|
||||||
|
* variants return 204-equivalent (no body).
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class PlayStartedRequest(
|
||||||
|
val type: String = "play_started",
|
||||||
|
@SerialName("track_id") val trackId: String,
|
||||||
|
@SerialName("client_id") val clientId: String,
|
||||||
|
val source: String? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class PlayStartedResponse(
|
||||||
|
@SerialName("play_event_id") val playEventId: String? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class PlayEndedRequest(
|
||||||
|
val type: String = "play_ended",
|
||||||
|
@SerialName("play_event_id") val playEventId: String,
|
||||||
|
@SerialName("duration_played_ms") val durationPlayedMs: Long,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class PlaySkippedRequest(
|
||||||
|
val type: String = "play_skipped",
|
||||||
|
@SerialName("play_event_id") val playEventId: String,
|
||||||
|
@SerialName("position_ms") val positionMs: Long,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class PlayOfflineRequest(
|
||||||
|
val type: String = "play_offline",
|
||||||
|
@SerialName("track_id") val trackId: String,
|
||||||
|
@SerialName("client_id") val clientId: String,
|
||||||
|
val at: String,
|
||||||
|
@SerialName("duration_played_ms") val durationPlayedMs: Long,
|
||||||
|
val source: String? = null,
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user