Skip to content

Commit

Permalink
a
Browse files Browse the repository at this point in the history
  • Loading branch information
MrPowerGamerBR committed Sep 1, 2023
1 parent a1e81d5 commit 7812674
Show file tree
Hide file tree
Showing 76 changed files with 1,832 additions and 1,993 deletions.
3 changes: 3 additions & 0 deletions backend/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ dependencies {
implementation("ch.qos.logback:logback-classic:1.3.0-alpha11")
implementation("commons-codec:commons-codec:1.15")

// https://mvnrepository.com/artifact/org.jsoup/jsoup
implementation("org.jsoup:jsoup:1.16.1")

// Databases
implementation("org.jetbrains.exposed:exposed-core:0.37.3")
implementation("org.jetbrains.exposed:exposed-jdbc:0.37.3")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,7 @@ import kotlinx.coroutines.runBlocking
import mu.KotlinLogging
import net.perfectdreams.dreamstorageservice.client.DreamStorageServiceClient
import net.perfectdreams.galleryofdreams.backend.plugins.configureRouting
import net.perfectdreams.galleryofdreams.backend.routes.GetFanArtArtistRoute
import net.perfectdreams.galleryofdreams.backend.routes.GetFanArtRoute
import net.perfectdreams.galleryofdreams.backend.routes.GetFanArtsListRoute
import net.perfectdreams.galleryofdreams.backend.routes.GetHomeRoute
import net.perfectdreams.galleryofdreams.backend.routes.GetSitemapRoute
import net.perfectdreams.galleryofdreams.backend.routes.*
import net.perfectdreams.galleryofdreams.backend.routes.api.GetFanArtArtistByDiscordIdRoute
import net.perfectdreams.galleryofdreams.backend.routes.api.GetFanArtsRoute
import net.perfectdreams.galleryofdreams.backend.routes.api.GetLanguageInfoRoute
Expand All @@ -40,29 +36,35 @@ import net.perfectdreams.galleryofdreams.backend.tables.FanArts
import net.perfectdreams.galleryofdreams.backend.tables.connections.FanArtArtistDeviantArtConnections
import net.perfectdreams.galleryofdreams.backend.tables.connections.FanArtArtistDiscordConnections
import net.perfectdreams.galleryofdreams.backend.tables.connections.FanArtArtistTwitterConnections
import net.perfectdreams.galleryofdreams.backend.utils.LanguageManager
import net.perfectdreams.galleryofdreams.backend.utils.WebsiteAssetsHashManager
import net.perfectdreams.galleryofdreams.backend.utils.*
import net.perfectdreams.galleryofdreams.backend.utils.exposed.createOrUpdatePostgreSQLEnum
import net.perfectdreams.galleryofdreams.common.FanArtTag
import net.perfectdreams.galleryofdreams.common.data.*
import org.jetbrains.exposed.exceptions.ExposedSQLException
import org.jetbrains.exposed.sql.DEFAULT_REPETITION_ATTEMPTS
import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.DatabaseConfig
import org.jetbrains.exposed.sql.SchemaUtils
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.SqlExpressionBuilder.like
import org.jetbrains.exposed.sql.statements.jdbc.JdbcConnectionImpl
import org.jetbrains.exposed.sql.transactions.TransactionManager
import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction
import java.sql.ResultSet
import java.time.Duration
import kotlin.concurrent.thread

class GalleryOfDreamsBackend(val languageManager: LanguageManager) {
companion object {
private val logger = KotlinLogging.logger {}
val webhookLinkRegex = Regex("https?://(?:[A-z]+\\.)?discord\\.com/api/webhooks/([0-9]+)/([A-z0-9-_]+)")
const val FAN_ARTS_PER_PAGE = 20
const val ARTIST_LIST_COUNT_PER_QUERY = 25
}

val routes = listOf(
GetHomeRoute(this),
GetFanArtsListRoute(this),
GetFanArtArtistRoute(this),
GetFanArtRoute(this),
PostFanArtArtistsSearchRoute(this),

GetSitemapRoute(this),

Expand Down Expand Up @@ -117,6 +119,7 @@ class GalleryOfDreamsBackend(val languageManager: LanguageManager) {
WebhookClientBuilder(webhookId.toLong(), webhookToken)
.build()
}
val svgIconManager = SVGIconManager(this)

fun start() {
runBlocking {
Expand Down Expand Up @@ -230,4 +233,115 @@ class GalleryOfDreamsBackend(val languageManager: LanguageManager) {
}
throw lastException ?: RuntimeException("This should never happen")
}

suspend fun searchFanArtArtists(
sortOrder: FanArtArtistSortOrder,
query: String?,
limit: Int,
offset: Int
): List<FanArtArtistWithFanArtCount> {
return transaction {
// We do this manually because we can optimize it better (we don't want to pull all artists if they don't have enough fan arts, as an example)
val results = mutableListOf<ResultRow>()

(this.connection as JdbcConnectionImpl)
.connection
.let {
when (sortOrder) {
FanArtArtistSortOrder.FAN_ART_COUNT_ASCENDING -> {
it.prepareStatement("select * from fanartartists where fanartartists.name ilike ? order by (select count(*) from fanarts where fanarts.artist = fanartartists.id group by fanarts.artist) asc limit $limit offset $offset;")
.apply {
this.setString(1, if (query == null) "%" else "%$query%")
}
}
FanArtArtistSortOrder.FAN_ART_COUNT_DESCENDING -> {
it.prepareStatement("select * from fanartartists where fanartartists.name ilike ? order by (select count(*) from fanarts where fanarts.artist = fanartartists.id group by fanarts.artist) desc limit $limit offset $offset;")
.apply {
this.setString(1, if (query == null) "%" else "%$query%")
}
}
FanArtArtistSortOrder.ALPHABETICALLY_ASCENDING -> {
it.prepareStatement("select * from fanartartists where fanartartists.name ilike ? order by fanartartists.name asc limit $limit offset $offset;")
.apply {
this.setString(1, if (query == null) "%" else "%$query%")
}
}
FanArtArtistSortOrder.ALPHABETICALLY_DESCENDING -> {
it.prepareStatement("select * from fanartartists where fanartartists.name ilike ? order by fanartartists.name desc limit $limit offset $offset;")
.apply {
this.setString(1, if (query == null) "%" else "%$query%")
}
}
}
}
.executeQuery()
.also {
while (it.next()) {
results.add(ResultRow.create(it, FanArtArtists.realFields.toSet().mapIndexed { index, expression -> expression to index }.toMap()))
}
}

results.map {
val discordSocialConnections = FanArtArtistDiscordConnections.select {
FanArtArtistDiscordConnections.artist eq it[FanArtArtists.id]
}
val twitterSocialConnections = FanArtArtistTwitterConnections.select {
FanArtArtistTwitterConnections.artist eq it[FanArtArtists.id]
}
val deviantArtSocialConnections = FanArtArtistDeviantArtConnections.select {
FanArtArtistDeviantArtConnections.artist eq it[FanArtArtists.id]
}

val count = FanArts.select {
FanArts.artist eq it[FanArtArtists.id]
}.count()

FanArtArtistWithFanArtCount(
FanArtArtistX(
it[FanArtArtists.id].value,
it[FanArtArtists.slug],
it[FanArtArtists.name],
listOf(),
discordSocialConnections.map {
DiscordSocialConnection(it[FanArtArtistDiscordConnections.discordId])
} + twitterSocialConnections.map {
TwitterSocialConnection(it[FanArtArtistTwitterConnections.handle])
} + deviantArtSocialConnections.map {
DeviantArtSocialConnection(it[FanArtArtistDeviantArtConnections.handle])
},
FanArts.select {
FanArts.artist eq it[FanArtArtists.id].value
}.orderBy(FanArts.createdAt, SortOrder.DESC).limit(1).firstOrNull()?.let {
convertToFanArt(it)
}
),
count
)
}
}
}

fun convertToFanArt(fanArt: ResultRow) = FanArt(
fanArt[FanArts.id].value,
fanArt[FanArts.slug],
fanArt[FanArts.title],
fanArt[FanArts.description],
fanArt[FanArts.createdAt],
fanArt[FanArts.dreamStorageServiceImageId],
fanArt[FanArts.file],
fanArt[FanArts.preferredMediaType],
FanArtTags.slice(FanArtTags.tag).select {
FanArtTags.fanArt eq fanArt[FanArts.id]
}.map { it[FanArtTags.tag] }
)

fun <T:Any> execAndMap(string: String, transform : (ResultSet) -> T) : List<T> {
val result = arrayListOf<T>()
TransactionManager.current().exec(string) { rs ->
while (rs.next()) {
result += transform(rs)
}
}
return result
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package net.perfectdreams.galleryofdreams.backend.components

import kotlinx.html.*
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put
import net.perfectdreams.galleryofdreams.backend.utils.FanArtUtils
import net.perfectdreams.galleryofdreams.backend.utils.websiteLocaleIdPath
import net.perfectdreams.galleryofdreams.common.data.FanArtArtistX
import net.perfectdreams.i18nhelper.core.I18nContext

fun FlowContent.fanArtArtist(
i18nContext: I18nContext,
dssBaseUrl: String,
namespace: String,
artist: FanArtArtistX,
fanArtCount: Long
) {
div {
a(classes = "entry", href = "/${i18nContext.websiteLocaleIdPath}/artists/${artist.slug}") {
attributes["hx-target"] = "#content"

val url = FanArtUtils.getArtistAvatarUrl(dssBaseUrl, namespace, artist, 32)

img(src = url) {
style = "object-fit: cover; border-radius: 100%;"
attributes["loading"] = "lazy"
width = "32"
height = "32"
}

div {
style = "display: flex; flex-direction: column;"

div {
text(artist.name)
}

div {
style = "font-size: 0.8em;"
text("$fanArtCount fan arts")
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package net.perfectdreams.galleryofdreams.backend.components

import kotlinx.datetime.toLocalDateTime
import kotlinx.html.*
import net.perfectdreams.galleryofdreams.backend.GalleryOfDreamsBackend
import net.perfectdreams.galleryofdreams.backend.utils.FanArtUtils
import net.perfectdreams.galleryofdreams.backend.utils.icon
import net.perfectdreams.galleryofdreams.backend.utils.websiteLocaleIdPath
import net.perfectdreams.galleryofdreams.common.MediaTypeUtils
import net.perfectdreams.galleryofdreams.common.data.FanArt
import net.perfectdreams.galleryofdreams.common.data.FanArtArtistX
import net.perfectdreams.galleryofdreams.common.i18n.I18nKeysData
import net.perfectdreams.i18nhelper.core.I18nContext

fun FlowContent.fanArtCard(m: GalleryOfDreamsBackend, i18nContext: I18nContext, dssBaseUrl: String, namespace: String, fanArtArtist: FanArtArtistX, fanArt: FanArt) {
a(classes = "fan-art-card", href = "/${i18nContext.websiteLocaleIdPath}/artists/${fanArtArtist.slug}/${fanArt.slug}") {
attributes["hx-target"] = "#content"

div(classes = "fan-art-info-card") {
div(classes = "fan-art-tags") {
for (tag in fanArt.tags) {
val icon = tag.icon(m)
icon?.apply(this) {
style = "height: 100%;"
}
}
}

div(classes = "fan-art-info") {
div(classes = "fan-art-info-wrapper") {
div(classes = "fan-art-title") {
text(fanArt.title ?: i18nContext.get(I18nKeysData.FanArtBy(fanArtArtist.name)))
}

div(classes = "fan-art-avatar-artist-and-date") {
img(src = FanArtUtils.getArtistAvatarUrl(dssBaseUrl, namespace, fanArtArtist, 32)) {
style = "object-fit: cover; border-radius: 100%;"
width = "32"
height = "32"
attributes["loading"] = "lazy"
}

div(classes = "fan-art-artist-and-date") {
div(classes = "fan-art-artist") {
text(fanArtArtist.name)
}
div(classes = "fan-art-info-date") {
val date = fanArt.createdAt
.toLocalDateTime(kotlinx.datetime.TimeZone.UTC)
.date

val year = date.year.toString().padStart(4, '0')
val month = date.monthNumber.toString().padStart(2, '0')
val day = date.dayOfMonth.toString().padStart(2, '0')
text("$day/$month/$year")
}
}
}
}
}
}

val extension = MediaTypeUtils.convertContentTypeToExtension(fanArt.preferredMediaType)
img(src = "$dssBaseUrl/$namespace/fan-arts/${fanArt.file}.$extension") {
style = "width: 100%; height: 100%; object-fit: cover;"
attributes["loading"] = "lazy"
alt = i18nContext.get(I18nKeysData.FanArtBy(fanArtArtist.name))
}
}
}
Loading

0 comments on commit 7812674

Please sign in to comment.