diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ce8469c..16aca97 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -13,4 +13,5 @@ dependencies { implementation(libs.bundles.logger) implementation(libs.bundles.database) + implementation("io.ktor:ktor-client-logging-jvm:2.3.4") } diff --git a/app/src/main/kotlin/Application.kt b/app/src/main/kotlin/Application.kt index 1095bc9..6e2dfbd 100644 --- a/app/src/main/kotlin/Application.kt +++ b/app/src/main/kotlin/Application.kt @@ -23,8 +23,10 @@ */ package dev.triumphteam.docsly +import com.zaxxer.hikari.HikariDataSource import dev.triumphteam.docsly.config.createOrGetConfig import dev.triumphteam.docsly.controller.apiGuild +import dev.triumphteam.docsly.database.entity.DocsTable import dev.triumphteam.docsly.defaults.Defaults import dev.triumphteam.docsly.meilisearch.Meili import dev.triumphteam.docsly.project.Projects @@ -39,6 +41,9 @@ import io.ktor.server.request.path import io.ktor.server.resources.Resources import io.ktor.server.routing.routing import kotlinx.serialization.Serializable +import org.jetbrains.exposed.sql.Database +import org.jetbrains.exposed.sql.SchemaUtils +import org.jetbrains.exposed.sql.transactions.transaction import org.slf4j.event.Level private val config = createOrGetConfig() @@ -48,7 +53,11 @@ public fun main() { } public fun Application.module() { - // Database.connect(HikariDataSource(config.postgres.toHikariConfig())) + Database.connect(HikariDataSource(config.postgres.toHikariConfig())) + + transaction { + SchemaUtils.create(DocsTable) + } /*install(CORS) { anyHost() @@ -71,7 +80,6 @@ public fun Application.module() { install(Projects) routing { - // Setup guild api/routing apiGuild() diff --git a/app/src/main/kotlin/controller/ApiGuildController.kt b/app/src/main/kotlin/controller/ApiGuildController.kt index 868e2df..689c530 100644 --- a/app/src/main/kotlin/controller/ApiGuildController.kt +++ b/app/src/main/kotlin/controller/ApiGuildController.kt @@ -13,7 +13,6 @@ import io.ktor.server.response.respond import io.ktor.server.routing.Routing public fun Routing.apiGuild() { - val defaults = plugin(Defaults) val projects = plugin(Projects) diff --git a/app/src/main/kotlin/database/entity/DocsEntity.kt b/app/src/main/kotlin/database/entity/DocsEntity.kt index cdd8119..7309a8c 100644 --- a/app/src/main/kotlin/database/entity/DocsEntity.kt +++ b/app/src/main/kotlin/database/entity/DocsEntity.kt @@ -5,23 +5,22 @@ import dev.triumphteam.docsly.elements.DocElement import org.jetbrains.exposed.dao.LongEntity import org.jetbrains.exposed.dao.LongEntityClass import org.jetbrains.exposed.dao.id.EntityID -import org.jetbrains.exposed.dao.id.IdTable +import org.jetbrains.exposed.dao.id.LongIdTable import org.jetbrains.exposed.sql.Column -public object DocsTable : IdTable() { - public val guild: Column = long("guild_id").uniqueIndex() - public val project: Column = text("defaults").uniqueIndex() - public val version: Column = text("version").uniqueIndex() - public val location: Column = text("location").uniqueIndex() - public val doc: Column = serializable("doc") +public object DocsTable : LongIdTable() { - override val primaryKey: PrimaryKey = PrimaryKey(guild, version, location) - override val id: Column> = guild.entityId() + public val guild: Column = text("guild_id") + public val project: Column = text("project") + public val version: Column = text("version") + public val doc: Column = serializable("doc") } public class DocEntity(entityId: EntityID) : LongEntity(entityId) { public companion object : LongEntityClass(DocsTable) + public var guild: String by DocsTable.guild + public var project: String by DocsTable.project public var version: String by DocsTable.version - public var guild: Long by DocsTable.guild + public var doc: DocElement by DocsTable.doc } diff --git a/app/src/main/kotlin/database/exposed/SerializableColumn.kt b/app/src/main/kotlin/database/exposed/SerializableColumn.kt index 8725c8b..f5c4d74 100644 --- a/app/src/main/kotlin/database/exposed/SerializableColumn.kt +++ b/app/src/main/kotlin/database/exposed/SerializableColumn.kt @@ -24,9 +24,9 @@ package dev.triumphteam.docsly.database.exposed import com.impossibl.postgres.jdbc.PGArray +import dev.triumphteam.docsly.project.Projects import kotlinx.serialization.KSerializer import kotlinx.serialization.builtins.ListSerializer -import kotlinx.serialization.json.Json import kotlinx.serialization.serializer import org.jetbrains.exposed.sql.Column import org.jetbrains.exposed.sql.Table @@ -34,13 +34,11 @@ import org.jetbrains.exposed.sql.TextColumnType import java.sql.Clob import kotlin.reflect.KClass -public val json: Json = Json - public inline fun Table.serializable(name: String, serializer: KSerializer): Column = registerColumn(name, SerializableColumnType(T::class, serializer)) public inline fun Table.serializable(name: String): Column = - registerColumn(name, SerializableColumnType(T::class, json.serializersModule.serializer())) + registerColumn(name, SerializableColumnType(T::class, Projects.JSON.serializersModule.serializer())) @Suppress("UNCHECKED_CAST") public class SerializableColumnType( @@ -50,16 +48,16 @@ public class SerializableColumnType( /** When writing the value, it can either be a full on list, or individual values. */ override fun notNullValueToDB(value: Any): Any = when { - klass.isInstance(value) -> json.encodeToString(serializer, value as T) + klass.isInstance(value) -> Projects.JSON.encodeToString(serializer, value as T) else -> error("$value of ${value::class.qualifiedName} is not an instance of ${klass.simpleName}") } /** When getting the value it can be more than just [PGArray]. */ override fun valueFromDB(value: Any): Any = when (value) { - is Clob -> json.decodeFromString(serializer, value.characterStream.readText()) - is ByteArray -> json.decodeFromString(serializer, String(value)) - is String -> json.decodeFromString(serializer, value) - else -> error("$value of ${value::class.qualifiedName} could not be decoded into ${klass.simpleName}") + is Clob -> Projects.JSON.decodeFromString(serializer, value.characterStream.readText()) + is ByteArray -> Projects.JSON.decodeFromString(serializer, String(value)) + is String -> Projects.JSON.decodeFromString(serializer, value) + else -> value } } @@ -74,13 +72,13 @@ public class SerializableListColumnType( /** When writing the value, it can either be a full on list, or individual values. */ override fun notNullValueToDB(value: Any): Any = when (value) { - is List<*> -> json.encodeToString(serializer, value as List) + is List<*> -> Projects.JSON.encodeToString(serializer, value as List) else -> error("$value of ${value::class.qualifiedName} is not an instance of ${klass.simpleName}") } /** When getting the value it can be more than just [PGArray]. */ override fun valueFromDB(value: Any): Any = when (value) { - is String -> json.decodeFromString(serializer, value) + is String -> Projects.JSON.decodeFromString(serializer, value) else -> { println("Oh boy! ${value.javaClass}") } diff --git a/app/src/main/kotlin/meilisearch/MeiliClient.kt b/app/src/main/kotlin/meilisearch/MeiliClient.kt index c46c5fa..0883eed 100644 --- a/app/src/main/kotlin/meilisearch/MeiliClient.kt +++ b/app/src/main/kotlin/meilisearch/MeiliClient.kt @@ -30,6 +30,10 @@ import io.ktor.client.engine.cio.CIO import io.ktor.client.plugins.auth.Auth import io.ktor.client.plugins.contentnegotiation.ContentNegotiation import io.ktor.client.plugins.defaultRequest +import io.ktor.client.plugins.logging.DEFAULT +import io.ktor.client.plugins.logging.LogLevel +import io.ktor.client.plugins.logging.Logger +import io.ktor.client.plugins.logging.Logging import io.ktor.client.plugins.resources.Resources import io.ktor.client.plugins.resources.delete import io.ktor.client.plugins.resources.post @@ -56,6 +60,10 @@ public class MeiliClient( install(Resources) install(Auth) { api(apiKey) } // Auto setup authentication install(ContentNegotiation) { json() } // Using Kotlin serialization for content negotiation + install(Logging) { + logger = Logger.DEFAULT + level = LogLevel.BODY + } defaultRequest { this.host = host @@ -114,10 +122,11 @@ public class MeiliClient( return client.post(Indexes.Uid.Documents(Indexes.Uid(uid = uid))) { contentType(ContentType.Application.Json) - parameter(PRIMARY_KEY_PARAM, pk) - setBody(documents) + pk?.let { parameter(PRIMARY_KEY_PARAM, it) } + setBody>(documents) }.also { println(it) + println(it.body()) } } diff --git a/app/src/main/kotlin/project/Projects.kt b/app/src/main/kotlin/project/Projects.kt index ec96cfd..2b076f1 100644 --- a/app/src/main/kotlin/project/Projects.kt +++ b/app/src/main/kotlin/project/Projects.kt @@ -1,17 +1,25 @@ package dev.triumphteam.docsly.project +import dev.triumphteam.docsly.database.entity.DocEntity import dev.triumphteam.docsly.defaults.Defaults import dev.triumphteam.docsly.elements.DocElement import dev.triumphteam.docsly.meilisearch.Meili -import dev.triumphteam.docsly.meilisearch.annotation.PrimaryKey import io.ktor.server.application.Application import io.ktor.server.application.BaseApplicationPlugin import io.ktor.server.application.plugin import io.ktor.util.AttributeKey -import kotlinx.coroutines.runBlocking import kotlinx.serialization.Serializable +import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json +import org.jetbrains.exposed.sql.transactions.transaction +/** + * A class representing a Projects plugin. + * Used for setting up projects, deleting, etc. + * + * @property meili The Meili plugin instance. + * @property defaults The Defaults plugin instance. + */ public class Projects(private val meili: Meili, private val defaults: Defaults) { public companion object Plugin : BaseApplicationPlugin { @@ -23,43 +31,59 @@ public class Projects(private val meili: Meili, private val defaults: Defaults) } public fun indexKeyFor(guild: String, project: String, version: String): String { - return "$guild:$project:$version" + return "${guild}_${project}_$version" } - } - private val json = Json { - explicitNulls = false - ignoreUnknownKeys = true + public val JSON: Json = Json { + explicitNulls = false + ignoreUnknownKeys = true + } } + /** + * Sets up projects for a specified guild with their projects. + * + * @param guild The name of the guild. + * @param projects A map containing project names as keys and sets of versions as values. + */ public suspend fun setupProjects(guild: String, projects: Map>) { - // transaction { - val mapped = projects.mapValues { (project, versions) -> - versions.associateWith { version -> - val jsonFile = defaults.resolve(project, version.replace(".", "_")) - json.decodeFromString>(jsonFile.readText()) + val mapped = transaction { + projects.mapValues { (project, versions) -> + versions.associateWith { version -> + val jsonFile = defaults.resolve(project, version.replace(".", "_")) + val docs = JSON.decodeFromString>(jsonFile.readText()) + + docs.map { doc -> + DocEntity.new { + this.guild = guild + this.project = project + this.version = version + this.doc = doc + } + } + } } } - runBlocking { - mapped.forEach { (project, versions) -> - versions.forEach { (version, docs) -> - meili.client.index(indexKeyFor(guild, project, version)).addDocuments( - docs.map { doc -> - IndexDocument(doc.location, doc.createReferences()) - } + mapped.forEach { (project, versions) -> + versions.forEach { (version, docs) -> + docs.chunked(1000).forEach { chunk -> + meili.client.index( + indexKeyFor(guild, project, version.replace(".", "_")), + primaryKey = "id", + ).addDocuments( + chunk.map { doc -> + IndexDocument(doc.id.value, doc.doc.createReferences()) + }.also { println(JSON.encodeToString(it)) }, ) } } } - - // TODO: Postgres - // } } } @Serializable public data class IndexDocument( - @PrimaryKey public val location: String, + public val id: Long, public val references: List, ) diff --git a/dokka-plugin/src/main/kotlin/renderer/JsonRenderer.kt b/dokka-plugin/src/main/kotlin/renderer/JsonRenderer.kt index 097b461..39a7c56 100644 --- a/dokka-plugin/src/main/kotlin/renderer/JsonRenderer.kt +++ b/dokka-plugin/src/main/kotlin/renderer/JsonRenderer.kt @@ -24,17 +24,6 @@ package dev.triumphteam.docsly.renderer import dev.triumphteam.docsly.DocslyDokkaPlugin -import dev.triumphteam.docsly.renderer.ext.description -import dev.triumphteam.docsly.renderer.ext.extraModifiers -import dev.triumphteam.docsly.renderer.ext.finalVisibility -import dev.triumphteam.docsly.renderer.ext.getDocumentation -import dev.triumphteam.docsly.renderer.ext.language -import dev.triumphteam.docsly.renderer.ext.returnType -import dev.triumphteam.docsly.renderer.ext.serialGenerics -import dev.triumphteam.docsly.renderer.ext.toPath -import dev.triumphteam.docsly.renderer.ext.toSerialAnnotations -import dev.triumphteam.docsly.renderer.ext.toSerialModifiers -import dev.triumphteam.docsly.renderer.ext.toSerialType import dev.triumphteam.docsly.elements.ClassKind import dev.triumphteam.docsly.elements.ClassLike import dev.triumphteam.docsly.elements.Language @@ -51,6 +40,17 @@ import dev.triumphteam.docsly.elements.SerializableParameter import dev.triumphteam.docsly.elements.SerializableProperty import dev.triumphteam.docsly.elements.SerializableTypeAlias import dev.triumphteam.docsly.elements.SuperType +import dev.triumphteam.docsly.renderer.ext.description +import dev.triumphteam.docsly.renderer.ext.extraModifiers +import dev.triumphteam.docsly.renderer.ext.finalVisibility +import dev.triumphteam.docsly.renderer.ext.getDocumentation +import dev.triumphteam.docsly.renderer.ext.language +import dev.triumphteam.docsly.renderer.ext.returnType +import dev.triumphteam.docsly.renderer.ext.serialGenerics +import dev.triumphteam.docsly.renderer.ext.toPath +import dev.triumphteam.docsly.renderer.ext.toSerialAnnotations +import dev.triumphteam.docsly.renderer.ext.toSerialModifiers +import dev.triumphteam.docsly.renderer.ext.toSerialType import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.coroutineScope diff --git a/gradle-plugin/src/main/kotlin/DocslyGradlePlugin.kt b/gradle-plugin/src/main/kotlin/DocslyGradlePlugin.kt index 95da0fe..b90b02f 100644 --- a/gradle-plugin/src/main/kotlin/DocslyGradlePlugin.kt +++ b/gradle-plugin/src/main/kotlin/DocslyGradlePlugin.kt @@ -34,7 +34,7 @@ import org.jetbrains.dokka.gradle.DokkaTask import org.jetbrains.dokka.gradle.DokkaTaskPartial private const val DOKKA_ID = "org.jetbrains.dokka" -private const val DOCLOPEDIA_DOKKA = "dev.triumphteam:docsly-dokka-plugin:0.0.2" +private const val DOCSLY_DOKKA_PLUGIN_VERSION = "dev.triumphteam:docsly-dokka-plugin:0.0.3" public open class DocslyGradlePlugin : Plugin { @@ -44,7 +44,7 @@ public open class DocslyGradlePlugin : Plugin { } setupDokkaTask("dokkaDocsly") { - plugins.dependencies.add(project.dependencies.create(DOCLOPEDIA_DOKKA)) + plugins.dependencies.add(project.dependencies.create(DOCSLY_DOKKA_PLUGIN_VERSION)) description = "Generates JSON documentation to be used by Docsly." } } diff --git a/gradle.properties b/gradle.properties index 3fffe5f..ea1691e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ group=dev.triumphteam -version=0.0.2 +version=0.0.4 org.gradle.jvmargs=-Xmx4G diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2bf478e..9092de0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -50,6 +50,7 @@ ktor-client-auth = { module = "io.ktor:ktor-client-auth", version.ref = "ktor" } ktor-client-negociation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" } ktor-client-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" } ktor-client-resources = { module = "io.ktor:ktor-client-resources", version.ref = "ktor" } +ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" } # DB exposed = { module = "org.jetbrains.exposed:exposed-jdbc", version.ref = "exposed" } @@ -89,7 +90,8 @@ ktor-client = [ "ktor-client-auth", "ktor-client-negociation", "ktor-client-json", - "ktor-client-resources" + "ktor-client-resources", + "ktor-client-logging", ] logger = [ "logger-api", diff --git a/serializable/src/main/kotlin/elements/ClassLike.kt b/serializable/src/main/kotlin/elements/ClassLike.kt index bdf087a..524714e 100644 --- a/serializable/src/main/kotlin/elements/ClassLike.kt +++ b/serializable/src/main/kotlin/elements/ClassLike.kt @@ -38,8 +38,8 @@ public sealed interface ClassLike : override fun createReferences(): List { return listOf( + "${path.packagePath}.$name", name, - "${path.packagePath}.$name" ) } } diff --git a/serializable/src/main/kotlin/elements/SerializableMember.kt b/serializable/src/main/kotlin/elements/SerializableMember.kt index c7f929d..c7ded24 100644 --- a/serializable/src/main/kotlin/elements/SerializableMember.kt +++ b/serializable/src/main/kotlin/elements/SerializableMember.kt @@ -38,9 +38,9 @@ public sealed interface SerializableMember : override fun createReferences(): List { return listOf( - name, - createReference("."), createReference("#"), + createReference("."), + name, ) } @@ -147,7 +147,6 @@ public data class SerializableEnumEntry( override fun createReferences(): List { return listOf( - name, buildString { append(path.packagePath) @@ -158,7 +157,8 @@ public data class SerializableEnumEntry( append("#") append(name) - } + }, + name, ) } } diff --git a/serializable/src/main/kotlin/elements/SerializableType.kt b/serializable/src/main/kotlin/elements/SerializableType.kt index 9e89aa6..0269066 100644 --- a/serializable/src/main/kotlin/elements/SerializableType.kt +++ b/serializable/src/main/kotlin/elements/SerializableType.kt @@ -75,6 +75,7 @@ public data class GenericType( /** A start type, or Java wildcard. */ @Serializable +@SerialName("STAR") public object StarType : SerializableType /** The type of projection to be used. */