Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

STUD-380 | Added GetSongSmartLinks API #619

Merged
merged 1 commit into from
Jan 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -81,5 +81,6 @@ interface ConfigRepository {
const val CONFIG_KEY_CLIENT_CONFIG_STUDIO = "clientConfig.studio"
const val CONFIG_KEY_CLIENT_CONFIG_MARKETPLACE = "clientConfig.marketplace"
const val CONFIG_KEY_CLIENT_CONFIG_MOBILE = "clientConfig.mobile"
const val CONFIG_KEY_SONG_SMART_LINKS_CACHE_TTL = "songSmartLinks.cacheTimeToLive"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package io.newm.server.database.migration

import org.flywaydb.core.api.migration.BaseJavaMigration
import org.flywaydb.core.api.migration.Context
import org.jetbrains.exposed.sql.transactions.transaction

@Suppress("unused")
class V72__CreateSongSmartLinks : BaseJavaMigration() {
override fun migrate(context: Context?) {
transaction {
execInBatch(
listOf(
"""
CREATE TABLE IF NOT EXISTS song_smart_links (
id uuid PRIMARY KEY,
created_at timestamp without time zone NOT NULL DEFAULT CURRENT_TIMESTAMP,
song_id uuid NOT NULL,
store_name TEXT NOT NULL,
url TEXT NOT NULL,
CONSTRAINT fk_song_smart_links_song_id__id FOREIGN KEY (song_id) REFERENCES songs(id) ON DELETE NO ACTION
)
""".trimIndent(),
"CREATE INDEX IF NOT EXISTS song_smart_links_song_id_index ON song_smart_links(song_id)",
"INSERT INTO config VALUES ('songSmartLinks.cacheTimeToLive','172800') ON CONFLICT(id) DO NOTHING",
),
)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,45 @@
package io.newm.server.features.distribution

import io.newm.server.features.collaboration.model.Collaboration
import io.newm.server.features.distribution.model.*
import io.newm.server.features.distribution.model.AddAlbumResponse
import io.newm.server.features.distribution.model.AddArtistRequest
import io.newm.server.features.distribution.model.AddArtistResponse
import io.newm.server.features.distribution.model.AddParticipantResponse
import io.newm.server.features.distribution.model.AddTrackResponse
import io.newm.server.features.distribution.model.AddUserLabelResponse
import io.newm.server.features.distribution.model.AddUserResponse
import io.newm.server.features.distribution.model.AddUserSubscriptionResponse
import io.newm.server.features.distribution.model.DeleteUserLabelResponse
import io.newm.server.features.distribution.model.DistributeReleaseResponse
import io.newm.server.features.distribution.model.DistributionOutletReleaseStatusResponse
import io.newm.server.features.distribution.model.EvearaSimpleResponse
import io.newm.server.features.distribution.model.GetAlbumResponse
import io.newm.server.features.distribution.model.GetArtistResponse
import io.newm.server.features.distribution.model.GetCountriesResponse
import io.newm.server.features.distribution.model.GetGenresResponse
import io.newm.server.features.distribution.model.GetLanguagesResponse
import io.newm.server.features.distribution.model.GetOutletProfileNamesResponse
import io.newm.server.features.distribution.model.GetOutletsResponse
import io.newm.server.features.distribution.model.GetParticipantsResponse
import io.newm.server.features.distribution.model.GetPayoutBalanceResponse
import io.newm.server.features.distribution.model.GetPayoutHistoryResponse
import io.newm.server.features.distribution.model.GetRolesResponse
import io.newm.server.features.distribution.model.GetTracksResponse
import io.newm.server.features.distribution.model.GetUserLabelResponse
import io.newm.server.features.distribution.model.GetUserResponse
import io.newm.server.features.distribution.model.GetUserSubscriptionResponse
import io.newm.server.features.distribution.model.InitiatePayoutResponse
import io.newm.server.features.distribution.model.SmartLink
import io.newm.server.features.distribution.model.UpdateArtistRequest
import io.newm.server.features.distribution.model.UpdateArtistResponse
import io.newm.server.features.distribution.model.UpdateUserLabelResponse
import io.newm.server.features.distribution.model.ValidateAlbumResponse
import io.newm.server.features.song.model.Release
import io.newm.server.features.song.model.Song
import io.newm.server.features.user.model.User
import io.newm.server.typealiases.UserId
import java.io.File
import java.time.LocalDate
import java.util.*

/**
* Higher level api for working with a music distribution service
Expand Down Expand Up @@ -162,4 +193,9 @@ interface DistributionRepository {
suspend fun createDistributionUserIfNeeded(user: User)

suspend fun createDistributionSubscription(user: User)

suspend fun getSmartLinks(
distributionUserId: String,
distributionReleaseId: Long
): List<SmartLink>
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ import io.newm.server.features.distribution.model.GetParticipantsResponse
import io.newm.server.features.distribution.model.GetPayoutBalanceResponse
import io.newm.server.features.distribution.model.GetPayoutHistoryResponse
import io.newm.server.features.distribution.model.GetRolesResponse
import io.newm.server.features.distribution.model.GetSmartLinksResponse
import io.newm.server.features.distribution.model.GetTrackStatusResponse
import io.newm.server.features.distribution.model.GetTracksResponse
import io.newm.server.features.distribution.model.GetUserLabelResponse
Expand All @@ -81,6 +82,7 @@ import io.newm.server.features.distribution.model.OutletStatusCode
import io.newm.server.features.distribution.model.OutletsDetail
import io.newm.server.features.distribution.model.Participant
import io.newm.server.features.distribution.model.Preview
import io.newm.server.features.distribution.model.SmartLink
import io.newm.server.features.distribution.model.Subscription
import io.newm.server.features.distribution.model.Track
import io.newm.server.features.distribution.model.UpdateArtistRequest
Expand Down Expand Up @@ -1761,6 +1763,26 @@ class EvearaDistributionRepositoryImpl(
}
}

override suspend fun getSmartLinks(
distributionUserId: String,
distributionReleaseId: Long
): List<SmartLink> {
val httpResponse = httpClient.get("$evearaApiBaseUrl/smartlinks/$distributionReleaseId") {
contentType(ContentType.Application.Json)
accept(ContentType.Application.Json)
bearerAuth(getEvearaApiToken())
parameter("uuid", distributionUserId)
}
if (!httpResponse.status.isSuccess()) {
throw ServerResponseException(httpResponse, "Error getting smart-links: ${httpResponse.bodyAsText()}")
}
val response: GetSmartLinksResponse = httpResponse.body()
if (!response.success) {
throw ServerResponseException(httpResponse, "Error getting smart-links: success==false")
}
return response.data.orEmpty()
}

private suspend fun createDistributionParticipants(
user: User,
collabs: List<Collaboration>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.newm.server.features.distribution.model

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

@Serializable
data class GetSmartLinksResponse(
@SerialName("success")
val success: Boolean,
@SerialName("message")
val message: String? = null,
@SerialName("data")
val data: List<SmartLink>? = null,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.newm.server.features.distribution.model

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

@Serializable
data class SmartLink(
@SerialName("store_name")
val storeName: String,
@SerialName("smart_link_url")
val url: String
)
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,9 @@ fun Routing.createSongRoutes() {
)
respond(HttpStatusCode.NoContent)
}
get("smartlinks") {
respond(songRepository.getSmartLinks(songId))
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package io.newm.server.features.song.database

import io.newm.server.features.song.model.SongSmartLink
import io.newm.server.typealiases.SongId
import org.jetbrains.exposed.dao.UUIDEntity
import org.jetbrains.exposed.dao.UUIDEntityClass
import org.jetbrains.exposed.dao.id.EntityID
import java.time.LocalDateTime
import java.util.UUID

class SongSmartLinkEntity(
id: EntityID<UUID>
) : UUIDEntity(id) {
val createdAt: LocalDateTime by SongSmartLinkTable.createdAt
var songId: EntityID<SongId> by SongSmartLinkTable.songId
var storeName: String by SongSmartLinkTable.storeName
var url: String by SongSmartLinkTable.url

fun toModel(): SongSmartLink =
SongSmartLink(
id = id.value,
storeName = storeName,
url = url
)

companion object : UUIDEntityClass<SongSmartLinkEntity>(SongSmartLinkTable) {
fun findBySongId(
songId: SongId
): List<SongSmartLinkEntity> = find { SongSmartLinkTable.songId eq songId }.toList()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.newm.server.features.song.database

import io.newm.server.typealiases.SongId
import org.jetbrains.exposed.dao.id.EntityID
import org.jetbrains.exposed.dao.id.UUIDTable
import org.jetbrains.exposed.sql.Column
import org.jetbrains.exposed.sql.ReferenceOption
import org.jetbrains.exposed.sql.javatime.CurrentDateTime
import org.jetbrains.exposed.sql.javatime.datetime
import java.time.LocalDateTime

object SongSmartLinkTable : UUIDTable(name = "song_smart_links") {
val createdAt: Column<LocalDateTime> = datetime("created_at").defaultExpression(CurrentDateTime)
val songId: Column<EntityID<SongId>> = reference("song_id", SongTable, onDelete = ReferenceOption.NO_ACTION).index()
val storeName: Column<String> = text("store_name")
val url: Column<String> = text("url")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package io.newm.server.features.song.model

import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable
import java.util.UUID

@Serializable
data class SongSmartLink(
@Contextual
val id: UUID,
val storeName: String,
val url: String
)
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ import io.newm.server.features.song.model.AudioUploadReport
import io.newm.server.features.song.model.MintPaymentResponse
import io.newm.server.features.song.model.MintingStatus
import io.newm.server.features.song.model.RefundPaymentResponse
import io.newm.server.features.song.model.Release
import io.newm.server.features.song.model.Song
import io.newm.server.features.song.model.SongFilters
import io.newm.server.features.song.model.Release
import io.newm.server.features.song.model.SongSmartLink
import io.newm.server.typealiases.ReleaseId
import io.newm.server.typealiases.SongId
import io.newm.server.typealiases.UserId
Expand Down Expand Up @@ -116,4 +117,6 @@ interface SongRepository {
songId: SongId,
mintPaymentResponse: MintPaymentResponse
)

suspend fun getSmartLinks(songId: SongId): List<SongSmartLink>
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import io.newm.server.aws.s3.s3UrlStringOf
import io.newm.server.config.repo.ConfigRepository
import io.newm.server.config.repo.ConfigRepository.Companion.CONFIG_KEY_DISTRIBUTION_PRICE_USD
import io.newm.server.config.repo.ConfigRepository.Companion.CONFIG_KEY_MINT_PRICE
import io.newm.server.config.repo.ConfigRepository.Companion.CONFIG_KEY_SONG_SMART_LINKS_CACHE_TTL
import io.newm.server.features.cardano.database.KeyTable
import io.newm.server.features.cardano.model.Key
import io.newm.server.features.cardano.repo.CardanoRepository
Expand All @@ -33,6 +34,7 @@ import io.newm.server.features.song.database.SongEntity
import io.newm.server.features.song.database.SongErrorHistoryTable
import io.newm.server.features.song.database.SongReceiptEntity
import io.newm.server.features.song.database.SongReceiptTable
import io.newm.server.features.song.database.SongSmartLinkEntity
import io.newm.server.features.song.database.SongTable
import io.newm.server.features.song.model.AudioEncodingStatus
import io.newm.server.features.song.model.AudioStreamData
Expand All @@ -44,6 +46,7 @@ import io.newm.server.features.song.model.Release
import io.newm.server.features.song.model.ReleaseType
import io.newm.server.features.song.model.Song
import io.newm.server.features.song.model.SongFilters
import io.newm.server.features.song.model.SongSmartLink
import io.newm.server.features.user.database.UserEntity
import io.newm.server.features.user.database.UserTable
import io.newm.server.features.user.model.UserVerificationStatus
Expand Down Expand Up @@ -856,6 +859,48 @@ internal class SongRepositoryImpl(
}
}

override suspend fun getSmartLinks(songId: SongId): List<SongSmartLink> {
logger.debug { "getSmartLinks: songId = $songId" }
val minCreatedAt = LocalDateTime
.now()
.minusSeconds(configRepository.getLong(CONFIG_KEY_SONG_SMART_LINKS_CACHE_TTL))
val cachedSmartLinks = transaction {
with(SongSmartLinkEntity.findBySongId(songId)) {
takeIf { any { it.createdAt >= minCreatedAt } } ?: run {
forEach { it.delete() }
null
}
}
}
if (!cachedSmartLinks.isNullOrEmpty()) {
logger.debug { "Found ${cachedSmartLinks.size} cached smart-links" }
return cachedSmartLinks.map { it.toModel() }
}
val (distributionUserId, distributionReleaseId) = transaction {
SongEntity[songId].run {
UserEntity[ownerId].distributionUserId to releaseId?.let { ReleaseEntity[it].distributionReleaseId }
}
}
if (distributionUserId == null || distributionReleaseId == null) {
logger.debug { "No distribution: userId = $distributionUserId, releaseId = $distributionReleaseId" }
return emptyList()
}
val networkSmartLinks = distributionRepository
.getSmartLinks(distributionUserId, distributionReleaseId)
.filter { it.url.isNotEmpty() }
logger.debug { "Found ${networkSmartLinks.size} network smart-links" }
return transaction {
val songEntityId = EntityID(songId, SongTable)
networkSmartLinks.map {
SongSmartLinkEntity.new {
this.songId = songEntityId
this.storeName = it.storeName
this.url = it.url
}
}
}.map { it.toModel() }
}

private suspend fun sendMintingStartedNotification(songId: SongId) {
collaborationRepository.invite(songId)
sendMintingNotification("started", songId)
Expand Down
Loading