Skip to content

Commit

Permalink
Merge pull request #7 from musicorum-app/parties
Browse files Browse the repository at this point in the history
In progress: party routes and schemas
  • Loading branch information
MysteryMS authored Nov 28, 2024
2 parents c3aaa09 + 5dc062c commit cb7033e
Show file tree
Hide file tree
Showing 21 changed files with 484 additions and 0 deletions.
2 changes: 2 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ repositories {
}

dependencies {
implementation("io.github.crackthecodeabhi:kreds:0.9.1")

// OpenAPI docs
implementation("io.ktor:ktor-server-openapi:$ktor_version")

Expand Down
5 changes: 5 additions & 0 deletions src/main/kotlin/io/musicorum/api/Application.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ import io.musicorum.api.plugins.installStatusPages
import io.musicorum.api.koin.installKoin
import io.musicorum.api.plugins.configureHTTP
import io.musicorum.api.realms.auth.createAuthRoutes
import io.musicorum.api.realms.charts.routes.createChartRoutes
import io.musicorum.api.realms.collages.routes.createCollagesRoutes
import io.musicorum.api.realms.docs.createDocsRoute
import io.musicorum.api.realms.party.routes.createPartyRoutes
import io.musicorum.api.realms.resources.createResourcesRoutes
import io.musicorum.api.security.configureSecurity

Expand All @@ -31,4 +33,7 @@ fun Application.module() {
createAuthRoutes()
createCollagesRoutes()
createDocsRoute()
createChartRoutes()

createPartyRoutes()
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ object EnvironmentVariable {
const val DatabaseUri = "DATABASE_URI"
const val DatabaseUser = "DATABASE_USER"
const val DatabasePassword = "DATABASE_PASS"
const val PartiesUrl = "PARTIES_URL"
}
3 changes: 3 additions & 0 deletions src/main/kotlin/io/musicorum/api/koin/KoinPlugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package io.musicorum.api.koin
import io.ktor.server.application.*
import io.musicorum.api.koin.modules.mainModule
import io.musicorum.api.realms.auth.authModule
import io.musicorum.api.realms.charts.chartModules
import io.musicorum.api.realms.collages.collagesModule
import io.musicorum.api.realms.resources.resourcesModule
import org.koin.ktor.plugin.Koin
Expand All @@ -16,5 +17,7 @@ fun Application.installKoin() {
modules(resourcesModule)

modules(collagesModule)

modules(chartModules)
}
}
11 changes: 11 additions & 0 deletions src/main/kotlin/io/musicorum/api/realms/charts/KoinModules.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package io.musicorum.api.realms.charts

import io.musicorum.api.realms.charts.repositories.ChartSnapshotRepository
import io.musicorum.api.realms.charts.repositories.ChartTrackRepository
import org.koin.core.module.dsl.singleOf
import org.koin.dsl.module

val chartModules = module(createdAtStart = true) {
singleOf(::ChartTrackRepository)
singleOf(::ChartSnapshotRepository)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package io.musicorum.api.realms.charts.repositories

import io.musicorum.api.realms.charts.schemas.ChartSnapshot
import io.musicorum.api.services.database
import kotlinx.coroutines.Dispatchers
import org.jetbrains.exposed.sql.SchemaUtils
import org.jetbrains.exposed.sql.Table
import org.jetbrains.exposed.sql.insert
import org.jetbrains.exposed.sql.javatime.CurrentDateTime
import org.jetbrains.exposed.sql.javatime.datetime
import org.jetbrains.exposed.sql.select
import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction
import org.jetbrains.exposed.sql.transactions.transaction
import java.time.ZoneOffset

class ChartSnapshotRepository {
object ChartSnapshot : Table("chart_snapshots") {
val id = integer("id").autoIncrement()
val updatedAt = datetime("updated_at")

override val primaryKey = PrimaryKey(id)
}

init {
transaction(database) {
SchemaUtils.create(ChartSnapshot)
}
}

suspend fun createSnapshot(): Int {
return newSuspendedTransaction(Dispatchers.IO) {
return@newSuspendedTransaction ChartSnapshot.insert {
it[updatedAt] = CurrentDateTime
}[ChartSnapshot.id]
}
}

suspend fun getSnapshot(id: Int): io.musicorum.api.realms.charts.schemas.ChartSnapshot {
return newSuspendedTransaction {
return@newSuspendedTransaction ChartSnapshot.select { ChartSnapshot.id eq id }.map {
ChartSnapshot(
it[ChartSnapshot.id],
it[ChartSnapshot.updatedAt].toEpochSecond(ZoneOffset.UTC)
)
}.first()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package io.musicorum.api.realms.charts.repositories

import io.musicorum.api.realms.charts.schemas.ChartTrack
import io.musicorum.api.services.database
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction
import org.jetbrains.exposed.sql.transactions.transaction

class ChartTrackRepository {
object ChartTracks : Table("chart_tracks") {
val id = integer("id").autoIncrement()
val name = text("name")
val playCount = long("play_count")
val snapshotId = integer("snapshot_id").references(ChartSnapshotRepository.ChartSnapshot.id)
val listeners = long("listeners")
val artist = text("artist")
val position = integer("positions")

override val primaryKey = PrimaryKey(id)
}

init {
transaction(database) {
SchemaUtils.createMissingTablesAndColumns(ChartTracks)
}
}

suspend fun insert(track: ChartTrack): Int {
return newSuspendedTransaction {
return@newSuspendedTransaction ChartTracks.insert {
it[name] = track.name
it[playCount] = track.playCount
it[snapshotId] = track.snapshotId
it[listeners] = track.listeners
it[position] = track.position
it[artist] = track.artist
}[ChartTracks.id]
}
}

suspend fun getAll(snapshotId: Int): List<ChartTrack> {
return newSuspendedTransaction {
return@newSuspendedTransaction ChartTracks.select {
ChartTracks.snapshotId eq snapshotId
}.map {
ChartTrack(
it[ChartTracks.playCount],
it[ChartTracks.snapshotId],
it[ChartTracks.listeners],
it[ChartTracks.artist],
it[ChartTracks.position],
it[ChartTracks.name]
)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package io.musicorum.api.realms.charts.repositories.lastfm

import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.engine.cio.*
import io.ktor.client.plugins.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.request.*
import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.*
import io.musicorum.api.enums.EnvironmentVariable
import io.musicorum.api.realms.charts.schemas.lastfm.BaseChartArtistResponse
import io.musicorum.api.realms.charts.schemas.lastfm.BaseChartTrackResponse
import io.musicorum.api.realms.charts.schemas.lastfm.ChartArtist
import io.musicorum.api.realms.charts.schemas.lastfm.ChartTrack
import io.musicorum.api.utils.getRequiredEnv
import kotlinx.serialization.json.Json

class LastFmChartRepository {
companion object {
private val client = HttpClient(CIO) {
install(ContentNegotiation) {
json(Json { ignoreUnknownKeys = true })
}

defaultRequest {
url("https", "ws.audioscrobbler.com") {
path("2.0")
parameters.append("api_key", getRequiredEnv(EnvironmentVariable.LastfmApiKey))
parameters.append("format", "json")

}
}
}

suspend fun getTopTracks(): List<ChartTrack> {
val res = client.get {
url {
parameter("method", "chart.gettoptracks")
}
}.body<BaseChartTrackResponse>()

return res.tracks.tracks
}

suspend fun getTopArtists(): List<ChartArtist> {
val res = client.get {
url {
parameter("method", "chart.gettopartists")
}
}.body<BaseChartArtistResponse>()

return res.artists.artists
}
}
}
42 changes: 42 additions & 0 deletions src/main/kotlin/io/musicorum/api/realms/charts/routes/Routes.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package io.musicorum.api.realms.charts.routes

import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import io.musicorum.api.realms.charts.repositories.ChartSnapshotRepository
import io.musicorum.api.realms.charts.repositories.ChartTrackRepository
import io.musicorum.api.realms.charts.repositories.lastfm.LastFmChartRepository
import io.musicorum.api.realms.charts.schemas.ChartTrack
import kotlinx.serialization.Serializable
import org.koin.ktor.ext.inject

fun Application.createChartRoutes() {
val chartSnapshotRepository = inject<ChartSnapshotRepository>()
val trackRepository = inject<ChartTrackRepository>()

routing {
route("/charts") {
route("/positions") {
createUpdatePositionsRoute()

get {
val snapshotId = call.request.queryParameters["snapshotId"]?.toIntOrNull()
if (snapshotId == null) {
call.respond(HttpStatusCode.BadRequest)
} else {
val tracks = trackRepository.value.getAll(snapshotId)
val lastUpdated = chartSnapshotRepository.value.getSnapshot(snapshotId).updatedAt
call.respond(PositionResponse(lastUpdated, tracks))
}
}
}
}
}
}

@Serializable
private data class PositionResponse(
val updatedAt: Long,
val trackEntries: List<ChartTrack>
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package io.musicorum.api.realms.charts.routes

import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import io.musicorum.api.realms.charts.repositories.ChartSnapshotRepository
import io.musicorum.api.realms.charts.repositories.ChartTrackRepository
import io.musicorum.api.realms.charts.repositories.lastfm.LastFmChartRepository
import org.koin.ktor.ext.inject

fun Route.createUpdatePositionsRoute() {
val chartTrackRepository = inject<ChartTrackRepository>()
val chartSnapshotRepository = inject<ChartSnapshotRepository>()

route("/update") {
post {
val topTracks = LastFmChartRepository.getTopTracks()
val topArtists = LastFmChartRepository.getTopArtists()

val snapshotId = chartSnapshotRepository.value.createSnapshot()

var trackPos = 1
for (track in topTracks) {
chartTrackRepository.value.insert(
io.musicorum.api.realms.charts.schemas.ChartTrack(
name = track.name,
playCount = track.playCount.toLong(),
listeners = track.listeners.toLong(),
position = trackPos++,
artist = track.artist.name,
snapshotId = snapshotId
)
)
}

call.respond(HttpStatusCode.Created)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package io.musicorum.api.realms.charts.schemas

import kotlinx.serialization.Serializable

@Serializable
data class ChartSnapshot(
val id: Int,
val updatedAt: Long
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.musicorum.api.realms.charts.schemas

import kotlinx.serialization.Serializable
import java.math.BigInteger

@Serializable
data class ChartTrack(
val playCount: Long,
val snapshotId: Int,
val listeners: Long,
val artist: String,
val position: Int,
val name: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package io.musicorum.api.realms.charts.schemas.lastfm

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class BaseChartArtistResponse(
val artists: ChartArtistResponse
)

@Serializable
data class ChartArtistResponse(
@SerialName("artist")
val artists: List<ChartArtist>
)


@Serializable
data class ChartArtist(
val name: String,
@SerialName("playcount")
val playCount: String,
val listeners: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package io.musicorum.api.realms.charts.schemas.lastfm

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class BaseChartTrackResponse(
val tracks: ChartTrackResponse
)

@Serializable
data class ChartTrackResponse(
@SerialName("track")
val tracks: List<ChartTrack>
)

@Serializable
data class ChartTrack(
val name: String,
@SerialName("playcount")
val playCount: String,
val listeners: String,
val artist: Artist
)

@Serializable
data class Artist(
val name: String
)
Loading

0 comments on commit cb7033e

Please sign in to comment.