-
-
Notifications
You must be signed in to change notification settings - Fork 27
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge remote-tracking branch 'origin/root' into root
- Loading branch information
Showing
18 changed files
with
589 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
root = true | ||
|
||
[*] | ||
charset = utf-8 | ||
insert_final_newline = true | ||
indent_style = tab | ||
trim_trailing_whitespace = true | ||
max_line_length = 120 | ||
|
||
[*.yml] | ||
indent_style = space |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
# MongoDB Adapter | ||
|
||
This module provides a data adapter (and some utilities) for MongoDB. | ||
This includes some codecs, for commonly-used types - which you can use in your own codec registry if you wish. | ||
|
||
# Usage | ||
|
||
* **Maven repo:** Maven Central for releases, `https://s01.oss.sonatype.org/content/repositories/snapshots` for | ||
snapshots | ||
* **Maven coordinates:** `com.kotlindiscord.kord.extensions:adapter-mongodb:VERSION` | ||
|
||
To switch to the MongoDB data adapter follow these steps: | ||
|
||
1. Set the `ADAPTER_MONGODB_URI` environmental variable to a MongoDB connection string. | ||
2. Use the `mongoDB` function to set up the data adapter. | ||
|
||
```kotlin | ||
suspend fun main() { | ||
val bot = ExtensibleBot(System.getenv("TOKEN")) { | ||
mongoDB() | ||
} | ||
|
||
bot.start() | ||
} | ||
``` | ||
|
||
3. If you use MongoDB elsewhere in your project, you can use the provided codecs to handle these types: | ||
- `Instant` (Kotlin Datetime) | ||
- `Snowflake` (Kord) | ||
- `StorageType` (KordEx) | ||
|
||
```kotlin | ||
// import: com.kotlindiscord.kord.extensions.adapters.mongodb.kordExCodecRegistry | ||
val registry = CodecRegistries.fromRegistries( | ||
kordexCodecRegistry, | ||
MongoClientSettings.getDefaultCodecRegistry(), | ||
) | ||
|
||
val client = MongoClient.create(MONGODB_URI) | ||
val database = client.getDatabase("database-name") | ||
|
||
val collection = database.getCollection<T>("name") | ||
.withCodecRegistry(registry) | ||
``` | ||
|
||
For more information on working with codecs, | ||
see [the MongoDB documentation](https://www.mongodb.com/docs/drivers/kotlin/coroutine/current/fundamentals/data-formats/codecs). | ||
|
||
# Notes | ||
|
||
* All provided codecs store their respective data types as strings in the database. | ||
* If you need to migrate from another data adapter to this one, you should read the code for both data adapters before | ||
writing your own migration code. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
plugins { | ||
`kordex-module` | ||
`published-module` | ||
`dokka-module` | ||
} | ||
|
||
metadata { | ||
name = "KordEx Adapters: MongoDB" | ||
description = "KordEx data adapter for MongoDB, including extra codecs" | ||
} | ||
|
||
repositories { | ||
maven { | ||
name = "Sonatype Snapshots" | ||
url = uri("https://oss.sonatype.org/content/repositories/snapshots") | ||
} | ||
} | ||
|
||
dependencies { | ||
detektPlugins(libs.detekt) | ||
detektPlugins(libs.detekt.libraries) | ||
|
||
implementation(libs.kotlin.stdlib) | ||
implementation(libs.kx.coro) | ||
implementation(libs.logging) | ||
implementation(libs.mongodb) | ||
|
||
implementation(project(":kord-extensions")) | ||
} | ||
|
||
group = "com.kotlindiscord.kord.extensions" |
27 changes: 27 additions & 0 deletions
27
...r-mongodb/src/main/kotlin/com/kotlindiscord/kord/extensions/adapters/mongodb/Constants.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
/* | ||
* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||
*/ | ||
|
||
package com.kotlindiscord.kord.extensions.adapters.mongodb | ||
|
||
import com.kotlindiscord.kord.extensions.adapters.mongodb.codecs.InstantCodec | ||
import com.kotlindiscord.kord.extensions.adapters.mongodb.codecs.SnowflakeCodec | ||
import com.kotlindiscord.kord.extensions.adapters.mongodb.codecs.StorageTypeCodec | ||
import com.kotlindiscord.kord.extensions.utils.env | ||
import com.mongodb.MongoClientSettings | ||
import org.bson.codecs.configuration.CodecRegistries | ||
import org.bson.codecs.configuration.CodecRegistry | ||
|
||
internal val MONGODB_URI: String = env("ADAPTER_MONGODB_URI") | ||
|
||
public val kordExCodecRegistry: CodecRegistry = CodecRegistries.fromRegistries( | ||
CodecRegistries.fromCodecs( | ||
InstantCodec(), | ||
SnowflakeCodec(), | ||
StorageTypeCodec(), | ||
), | ||
|
||
MongoClientSettings.getDefaultCodecRegistry(), | ||
) |
37 changes: 37 additions & 0 deletions
37
...r-mongodb/src/main/kotlin/com/kotlindiscord/kord/extensions/adapters/mongodb/Functions.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
/* | ||
* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||
*/ | ||
|
||
package com.kotlindiscord.kord.extensions.adapters.mongodb | ||
|
||
import com.kotlindiscord.kord.extensions.adapters.mongodb.db.Database | ||
import com.kotlindiscord.kord.extensions.builders.ExtensibleBotBuilder | ||
|
||
/** | ||
* Configures the bot to use MongoDB as the data storage adapter. | ||
* | ||
* This method sets up the [MongoDBDataAdapter] as the data adapter for the bot, allowing it to interact with a | ||
* MongoDB database for storing and retrieving data provided by storage units. | ||
* | ||
* Additionally, this method registers a hook with `beforeKoinSetup`, which checks that the database is reachable, | ||
* and runs any pending migrations. | ||
* | ||
* Usage: | ||
* | ||
* ``` | ||
* ExtensibleBotBuilder(...) { | ||
* mongoDB() | ||
* } | ||
* ``` | ||
*/ | ||
public suspend fun ExtensibleBotBuilder.mongoDB() { | ||
dataAdapter(::MongoDBDataAdapter) | ||
|
||
hooks { | ||
beforeKoinSetup { | ||
Database.setup() | ||
} | ||
} | ||
} |
162 changes: 162 additions & 0 deletions
162
.../src/main/kotlin/com/kotlindiscord/kord/extensions/adapters/mongodb/MongoDBDataAdapter.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
/* | ||
* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||
*/ | ||
|
||
@file:Suppress("UNCHECKED_CAST") | ||
@file:OptIn(InternalSerializationApi::class) | ||
|
||
package com.kotlindiscord.kord.extensions.adapters.mongodb | ||
|
||
import com.kotlindiscord.kord.extensions.adapters.mongodb.db.AdaptedData | ||
import com.kotlindiscord.kord.extensions.adapters.mongodb.db.Database | ||
import com.kotlindiscord.kord.extensions.koin.KordExKoinComponent | ||
import com.kotlindiscord.kord.extensions.storage.Data | ||
import com.kotlindiscord.kord.extensions.storage.DataAdapter | ||
import com.kotlindiscord.kord.extensions.storage.StorageUnit | ||
import com.mongodb.client.model.Filters | ||
import com.mongodb.client.model.Filters.eq | ||
import com.mongodb.client.model.ReplaceOptions | ||
import com.mongodb.kotlin.client.coroutine.MongoCollection | ||
import kotlinx.coroutines.flow.firstOrNull | ||
import kotlinx.serialization.InternalSerializationApi | ||
import kotlinx.serialization.json.Json | ||
import kotlinx.serialization.serializer | ||
import org.bson.conversions.Bson | ||
|
||
/** | ||
* This class represents a MongoDB data adapter for storing and retrieving data using MongoDB as the underlying | ||
* database for data stored using storage units. | ||
* | ||
* Use the provided [mongoDB] function to add this to your bot, rather than directly referencing the constructor for | ||
* this class. | ||
*/ | ||
public class MongoDBDataAdapter : DataAdapter<String>(), KordExKoinComponent { | ||
private val collectionCache: MutableMap<String, MongoCollection<AdaptedData>> = mutableMapOf() | ||
|
||
private fun StorageUnit<*>.getIdentifier(): String = | ||
buildString { | ||
append("${storageType.type}/") | ||
|
||
if (guild != null) append("guild-$guild/") | ||
if (channel != null) append("channel-$channel/") | ||
if (user != null) append("user-$user/") | ||
if (message != null) append("message-$message/") | ||
|
||
append(identifier) | ||
} | ||
|
||
private fun getCollection(namespace: String): MongoCollection<AdaptedData> { | ||
val collName = "data-$namespace" | ||
|
||
return collectionCache.getOrPut(collName) { Database.getCollection<AdaptedData>(collName) } | ||
} | ||
|
||
private fun constructQuery(unit: StorageUnit<*>): Bson = | ||
Filters.and( | ||
listOf( | ||
eq(AdaptedData::identifier.name, unit.identifier), | ||
|
||
eq(AdaptedData::type.name, unit.storageType), | ||
|
||
eq(AdaptedData::channel.name, unit.channel), | ||
eq(AdaptedData::guild.name, unit.guild), | ||
eq(AdaptedData::message.name, unit.message), | ||
eq(AdaptedData::user.name, unit.user) | ||
) | ||
) | ||
|
||
override suspend fun <R : Data> delete(unit: StorageUnit<R>): Boolean { | ||
removeFromCache(unit) | ||
|
||
val result = getCollection(unit.namespace) | ||
.deleteOne(constructQuery(unit)) | ||
|
||
return result.deletedCount > 0 | ||
} | ||
|
||
override suspend fun <R : Data> get(unit: StorageUnit<R>): R? { | ||
val dataId = unitCache[unit] | ||
|
||
if (dataId != null) { | ||
val data = dataCache[dataId] | ||
|
||
if (data != null) { | ||
return data as R | ||
} | ||
} | ||
|
||
return reload(unit) | ||
} | ||
|
||
override suspend fun <R : Data> reload(unit: StorageUnit<R>): R? { | ||
val dataId = unit.getIdentifier() | ||
val result = getCollection(unit.namespace) | ||
.find(constructQuery(unit)).limit(1).firstOrNull()?.data | ||
|
||
if (result != null) { | ||
dataCache[dataId] = Json.decodeFromString(unit.dataType.serializer(), result) | ||
unitCache[unit] = dataId | ||
} | ||
|
||
return dataCache[dataId] as R? | ||
} | ||
|
||
override suspend fun <R : Data> save(unit: StorageUnit<R>): R? { | ||
val data = get(unit) ?: return null | ||
|
||
getCollection(unit.namespace).replaceOne( | ||
eq(unit.getIdentifier()), | ||
|
||
AdaptedData( | ||
_id = unit.getIdentifier(), | ||
|
||
identifier = unit.identifier, | ||
|
||
type = unit.storageType, | ||
|
||
channel = unit.channel, | ||
guild = unit.guild, | ||
message = unit.message, | ||
user = unit.user, | ||
|
||
data = Json.encodeToString(unit.dataType.serializer(), data) | ||
), | ||
|
||
ReplaceOptions().upsert(true) | ||
) | ||
|
||
return data | ||
} | ||
|
||
override suspend fun <R : Data> save(unit: StorageUnit<R>, data: R): R { | ||
val dataId = unit.getIdentifier() | ||
|
||
dataCache[dataId] = data | ||
unitCache[unit] = dataId | ||
|
||
getCollection(unit.namespace).replaceOne( | ||
eq(unit.getIdentifier()), | ||
|
||
AdaptedData( | ||
_id = unit.getIdentifier(), | ||
|
||
identifier = unit.identifier, | ||
|
||
type = unit.storageType, | ||
|
||
channel = unit.channel, | ||
guild = unit.guild, | ||
message = unit.message, | ||
user = unit.user, | ||
|
||
data = Json.encodeToString(unit.dataType.serializer(), data) | ||
), | ||
|
||
ReplaceOptions().upsert(true) | ||
) | ||
|
||
return data | ||
} | ||
} |
26 changes: 26 additions & 0 deletions
26
...src/main/kotlin/com/kotlindiscord/kord/extensions/adapters/mongodb/codecs/InstantCodec.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
/* | ||
* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||
*/ | ||
|
||
package com.kotlindiscord.kord.extensions.adapters.mongodb.codecs | ||
|
||
import kotlinx.datetime.Instant | ||
import org.bson.BsonReader | ||
import org.bson.BsonWriter | ||
import org.bson.codecs.Codec | ||
import org.bson.codecs.DecoderContext | ||
import org.bson.codecs.EncoderContext | ||
|
||
public class InstantCodec : Codec<Instant> { | ||
override fun decode(reader: BsonReader, decoderContext: DecoderContext): Instant = | ||
Instant.parse(reader.readString()) | ||
|
||
override fun encode(writer: BsonWriter, value: Instant, encoderContext: EncoderContext) { | ||
writer.writeString(value.toString()) | ||
} | ||
|
||
override fun getEncoderClass(): Class<Instant> = | ||
Instant::class.java | ||
} |
26 changes: 26 additions & 0 deletions
26
...c/main/kotlin/com/kotlindiscord/kord/extensions/adapters/mongodb/codecs/SnowflakeCodec.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
/* | ||
* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||
*/ | ||
|
||
package com.kotlindiscord.kord.extensions.adapters.mongodb.codecs | ||
|
||
import dev.kord.common.entity.Snowflake | ||
import org.bson.BsonReader | ||
import org.bson.BsonWriter | ||
import org.bson.codecs.Codec | ||
import org.bson.codecs.DecoderContext | ||
import org.bson.codecs.EncoderContext | ||
|
||
public class SnowflakeCodec : Codec<Snowflake> { | ||
override fun decode(reader: BsonReader, decoderContext: DecoderContext): Snowflake = | ||
Snowflake(reader.readString()) | ||
|
||
override fun encode(writer: BsonWriter, value: Snowflake, encoderContext: EncoderContext) { | ||
writer.writeString(value.toString()) | ||
} | ||
|
||
override fun getEncoderClass(): Class<Snowflake> = | ||
Snowflake::class.java | ||
} |
Oops, something went wrong.