Skip to content
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
2 changes: 2 additions & 0 deletions buildSrc/src/main/kotlin/Dependencies.kt
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ object Dependencies {
const val SERVER_AUTH = "io.ktor:ktor-server-auth:$VERSION"
const val SERVER_AUTH_JWT = "io.ktor:ktor-server-auth-jwt:$VERSION"
const val SERVER_HTML_BUILDER = "io.ktor:ktor-server-html-builder:$VERSION"
const val SERVER_COMPRESSION = "io.ktor:ktor-server-compression:$VERSION"
const val SERVER_COMPRESSION_ZSTD = "io.ktor:ktor-server-compression-zstd:$VERSION"
const val CLIENT_CORE = "io.ktor:ktor-client-core:$VERSION"
const val CLIENT_CIO = "io.ktor:ktor-client-cio:$VERSION"
const val CLIENT_OKHTTP = "io.ktor:ktor-client-okhttp:$VERSION"
Expand Down
2 changes: 1 addition & 1 deletion buildSrc/src/main/kotlin/Versions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ object Versions {
const val KOTLIN_PLUGIN = "2.3.0"
const val KTLINT = "1.8.0"
const val KTLINT_PLUGIN = "12.1.1"
const val KTOR = "3.3.3"
const val KTOR = "3.4.0"
const val KTOR_FLYWAY = "3.3.0"
const val LOGBACK = "1.5.23"
const val MAVEN_PUBLISH = "0.35.0"
Expand Down
2 changes: 2 additions & 0 deletions newm-server/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ dependencies {
implementation(Dependencies.Ktor.SERVER_AUTH)
implementation(Dependencies.Ktor.SERVER_AUTH_JWT)
implementation(Dependencies.Ktor.SERVER_HTML_BUILDER)
implementation(Dependencies.Ktor.SERVER_COMPRESSION)
implementation(Dependencies.Ktor.SERVER_COMPRESSION_ZSTD)
implementation(Dependencies.Ktor.CLIENT_CORE)
implementation(Dependencies.Ktor.CLIENT_CIO)
implementation(Dependencies.Ktor.CLIENT_OKHTTP)
Expand Down
4 changes: 4 additions & 0 deletions newm-server/src/main/kotlin/io/newm/server/Application.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import io.ktor.server.application.Application
import io.ktor.server.routing.routing
import io.newm.server.auth.createAuthenticationRoutes
import io.newm.server.auth.installAuthentication
import io.newm.server.compression.installCompression
import io.newm.server.content.installContentNegotiation
import io.newm.server.cors.installCORS
import io.newm.server.curator.installCurator
Expand All @@ -20,6 +21,7 @@ import io.newm.server.features.earnings.createEarningsRoutes
import io.newm.server.features.ethereum.createEthereumRoutes
import io.newm.server.features.idenfy.createIdenfyRoutes
import io.newm.server.features.marketplace.createMarketplaceRoutes
import io.newm.server.features.nftsong.createNftSongRoutes
import io.newm.server.features.paypal.createPayPalRoutes
import io.newm.server.features.playlist.createPlaylistRoutes
import io.newm.server.features.song.createSongRoutes
Expand Down Expand Up @@ -65,6 +67,7 @@ fun Application.module() {
installCORS()
installForwarder()
installHealthCheck()
installCompression()

routing {
createStaticContentRoutes()
Expand All @@ -84,6 +87,7 @@ fun Application.module() {
createOpenApiDocumentationRoutes()
createEarningsRoutes()
createPayPalRoutes()
createNftSongRoutes()
}

initializeDaemons()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package io.newm.server.compression

import io.ktor.server.application.Application
import io.ktor.server.application.install
import io.ktor.server.plugins.compression.Compression
import io.ktor.server.plugins.compression.condition
import io.ktor.server.plugins.compression.deflate
import io.ktor.server.plugins.compression.gzip
import io.ktor.server.plugins.compression.minimumSize
import io.ktor.server.plugins.compression.zstd.zstd
import io.ktor.server.request.path
import io.newm.shared.ktx.getConfigLong
import io.newm.shared.ktx.getConfigStrings

fun Application.installCompression() {
val minSize = environment.getConfigLong("compression.minSize")
val pathPrefixes = environment.getConfigStrings("compression.pathPrefixes")

install(Compression) {
minimumSize(minSize)
condition {
val path = request.path()
pathPrefixes.any(path::startsWith)
}

zstd { priority = 1.0 }
gzip { priority = 0.9 }
deflate { priority = 0.8 }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import io.newm.server.features.idenfy.idenfyKoinModule
import io.newm.server.features.marketplace.marketplaceKoinModule
import io.newm.server.features.minting.mintingKoinModule
import io.newm.server.features.nftcdn.nftCdnKoinModule
import io.newm.server.features.nftsong.nftSongKoinModule
import io.newm.server.features.paypal.payPalKoinModule
import io.newm.server.features.playlist.playlistKoinModule
import io.newm.server.features.referralhero.referralHeroKoinModule
Expand Down Expand Up @@ -74,6 +75,7 @@ fun Application.installDependencyInjection() {
dripDropzKoinModule,
referralHeroKoinModule,
payPalKoinModule,
nftSongKoinModule,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import io.ktor.server.response.respond
import io.ktor.server.routing.Routing
import io.newm.server.auth.jwt.AUTH_JWT
import io.newm.server.features.ethereum.repo.EthereumRepository
import io.newm.server.ktx.requiredQueryParam
import io.newm.server.ktx.myUserId
import io.newm.shared.koin.inject
import io.newm.shared.ktx.get

Expand All @@ -14,9 +14,7 @@ fun Routing.createEthereumRoutes() {

authenticate(AUTH_JWT) {
get("/v1/ethereum/nft/songs") {
// TODO: for now, during the initial experimentation phase, we get the owner address directly from
// the client, later we will migrate to a Wallet connection model similar to Cardano
respond(repository.getNftSongs(request.requiredQueryParam("ownerAddress")))
respond(repository.getWalletNftSongs(myUserId))
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package io.newm.server.features.ethereum.repo

import io.newm.server.features.ethereum.model.EthereumNftSong
import io.newm.server.typealiases.UserId

interface EthereumRepository {
suspend fun getNftSongs(ownerAddress: String): List<EthereumNftSong>
suspend fun getWalletNftSongs(userId: UserId): List<EthereumNftSong>
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,26 @@ import io.newm.server.features.ethereum.model.EthereumNft
import io.newm.server.features.ethereum.model.EthereumNftSong
import io.newm.server.features.ethereum.model.GetNftsByOwnerResponse
import io.newm.server.features.ethereum.parser.parseSong
import io.newm.server.features.user.database.UserEntity
import io.newm.server.ktx.checkedBody
import io.newm.server.ktx.getSecureConfigString
import io.newm.server.typealiases.UserId
import io.newm.shared.ktx.getConfigString
import org.jetbrains.exposed.sql.transactions.transaction

internal class EthereumRepositoryImpl(
private val client: HttpClient,
private val environment: ApplicationEnvironment
) : EthereumRepository {
private val logger = KotlinLogging.logger {}

override suspend fun getNftSongs(ownerAddress: String): List<EthereumNftSong> {
logger.debug { "getNftSongs: ownerAddress = $ownerAddress" }
override suspend fun getWalletNftSongs(userId: UserId): List<EthereumNftSong> {
logger.debug { "getWalletNftSongs: userId = $userId" }

// Temporary test hook until Ethereum wallet connections are implemented
Comment thread
AndrewWestberg marked this conversation as resolved.
val email = transaction { UserEntity[userId].email }
if (!email.endsWith("@newm.io")) return emptyList()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍🏼

val address = "0x89fd6e1e7a737293dc9ecaee118753b7abdf5e37"

val apiUrl = environment.getConfigString("alchemy.apiUrl")
val apiKey = environment.getSecureConfigString("alchemy.apiKey")
Expand All @@ -34,7 +42,7 @@ internal class EthereumRepositoryImpl(
delayMillis { 500L }
}
accept(ContentType.Application.Json)
parameter("owner", ownerAddress)
parameter("owner", address)
}.checkedBody<GetNftsByOwnerResponse>()
.ownedNfts
.mapNotNull(EthereumNft::parseSong)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package io.newm.server.features.nftsong

import io.newm.server.features.nftsong.repo.NftSongRepository
import io.newm.server.features.nftsong.repo.NftSongRepositoryImpl
import org.koin.dsl.module

val nftSongKoinModule =
module {
single<NftSongRepository> { NftSongRepositoryImpl(get(), get()) }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.newm.server.features.nftsong

import io.ktor.server.auth.authenticate
import io.ktor.server.response.respond
import io.ktor.server.routing.Routing
import io.newm.server.auth.jwt.AUTH_JWT
import io.newm.server.features.nftsong.repo.NftSongRepository
import io.newm.server.ktx.myUserId
import io.newm.shared.koin.inject
import io.newm.shared.ktx.get

fun Routing.createNftSongRoutes() {
val repository: NftSongRepository by inject()

authenticate(AUTH_JWT) {
get("/v1/nft/songs") {
respond(repository.getNftSongs(myUserId))
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.newm.server.features.nftsong.model

import kotlinx.serialization.Serializable

@Serializable
sealed interface NftChainMetadata {
@Serializable
data class Cardano(
val fingerprint: String,
val policyId: String,
val assetName: String,
val isStreamToken: Boolean
) : NftChainMetadata

@Serializable
data class Ethereum(
val contractAddress: String,
val tokenType: String,
val tokenId: String,
Comment thread
AndrewWestberg marked this conversation as resolved.
) : NftChainMetadata
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package io.newm.server.features.nftsong.model

import kotlinx.serialization.Serializable

@Serializable
enum class NftChainType {
Cardano,
Ethereum
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.newm.server.features.nftsong.model

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

@Serializable
data class NftSong(
@Contextual
val id: UUID,
val title: String,
val imageUrl: String,
val audioUrl: String,
val duration: Long,
val artists: List<String>,
val genres: List<String>,
val moods: List<String>,
val amount: Long,
val chainType: NftChainType,
val chainMetadata: NftChainMetadata
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package io.newm.server.features.nftsong.repo

import io.newm.server.features.nftsong.model.NftSong
import io.newm.server.typealiases.UserId

interface NftSongRepository {
suspend fun getNftSongs(userId: UserId): List<NftSong>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package io.newm.server.features.nftsong.repo

import io.newm.server.features.cardano.model.CardanoNftSong
import io.newm.server.features.cardano.repo.CardanoRepository
import io.newm.server.features.ethereum.model.EthereumNftSong
import io.newm.server.features.ethereum.repo.EthereumRepository
import io.newm.server.features.nftsong.model.NftChainMetadata
import io.newm.server.features.nftsong.model.NftChainType
import io.newm.server.features.nftsong.model.NftSong
import io.newm.server.typealiases.UserId

internal class NftSongRepositoryImpl(
private val cardanoRepository: CardanoRepository,
private val ethereumRepository: EthereumRepository
) : NftSongRepository {
override suspend fun getNftSongs(userId: UserId): List<NftSong> {
val nftSongs = mutableListOf<NftSong>()

nftSongs += cardanoRepository
.getWalletNftSongs(userId, includeLegacy = true, useDripDropz = false)
.map { it.toNftSong() }

nftSongs += ethereumRepository
.getWalletNftSongs(userId)
.map { it.toNftSong() }

return nftSongs
}

private fun CardanoNftSong.toNftSong(): NftSong =
NftSong(
id = id,
title = title,
imageUrl = imageUrl,
audioUrl = audioUrl,
duration = duration,
artists = artists,
genres = genres,
moods = moods,
amount = amount,
chainType = NftChainType.Cardano,
chainMetadata = NftChainMetadata.Cardano(
fingerprint = fingerprint,
policyId = policyId,
assetName = assetName,
isStreamToken = isStreamToken
)
)

private fun EthereumNftSong.toNftSong(): NftSong =
NftSong(
id = id,
title = title,
imageUrl = imageUrl,
audioUrl = audioUrl,
duration = duration,
artists = artists,
genres = genres,
moods = moods,
amount = amount,
chainType = NftChainType.Ethereum,
chainMetadata = NftChainMetadata.Ethereum(
contractAddress = contractAddress,
tokenType = tokenType,
tokenId = tokenId
)
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ import io.newm.shared.ktx.orNull
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction
import java.time.LocalDateTime
import org.jetbrains.exposed.sql.transactions.transaction
import java.time.LocalDateTime

internal class UserRepositoryImpl(
private val googleUserProvider: GoogleUserProvider,
Expand Down Expand Up @@ -388,13 +388,13 @@ internal class UserRepositoryImpl(
logger.debug { "updateReferralStatusIfNeeded: userId = $userId, isConfirmed: $isConfirmed" }
val email = transaction {
UserEntity[userId].run {
when {
referralStatus == ReferralStatus.Pending && !isConfirmed -> {
when (referralStatus) {
ReferralStatus.Pending if !isConfirmed -> {
referralStatus = ReferralStatus.Unconfirmed
email
}

referralStatus == ReferralStatus.Unconfirmed && isConfirmed -> {
ReferralStatus.Unconfirmed if isConfirmed -> {
referralStatus = ReferralStatus.Confirmed
email
}
Expand Down
5 changes: 5 additions & 0 deletions newm-server/src/main/resources/application.conf
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,11 @@ cors {
hosts = [${CORS_HOSTS}]
}

compression {
minSize = 1024
pathPrefixes = ["/v1/nft/songs"]
}

sentry {
dsn = ${SENTRY_DSN}
environment = "development"
Expand Down