summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSho Sakuma <me@m1sk9.dev>2026-01-25 16:19:24 +0900
committerSho Sakuma <me@m1sk9.dev>2026-01-25 16:19:24 +0900
commit9f4fb8f4abec562cf13cb1dd225828876875c790 (patch)
tree5deccc82809801f45cd98163da936e34cf9bd77d
parent3ec21511191be259370056c91549d0b981a3f4c5 (diff)
downloadLunaticChat-9f4fb8f4abec562cf13cb1dd225828876875c790.tar.gz
LunaticChat-9f4fb8f4abec562cf13cb1dd225828876875c790.tar.bz2
LunaticChat-9f4fb8f4abec562cf13cb1dd225828876875c790.zip
feat: Add Channel CRUD operator
-rw-r--r--engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChannelMemberNotFoundException.kt5
-rw-r--r--engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChannelNoOwnerPermissionException.kt7
-rw-r--r--engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChannelNotFoundException.kt5
-rw-r--r--engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChannelNotMemberException.kt8
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/ServiceInitializer.kt2
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/channel/ChannelManager.kt163
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/channel/ChannelMembershipManager.kt71
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/channel/ChannelStorage.kt (renamed from platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/channel/storage/ChannelStorage.kt)6
8 files changed, 263 insertions, 4 deletions
diff --git a/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChannelMemberNotFoundException.kt b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChannelMemberNotFoundException.kt
new file mode 100644
index 0000000..8dfd6f2
--- /dev/null
+++ b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChannelMemberNotFoundException.kt
@@ -0,0 +1,5 @@
+package dev.m1sk9.lunaticChat.engine.exception
+
+class ChannelMemberNotFoundException(
+ channelId: String,
+) : Exception("Channel member not found in channel with ID $channelId.")
diff --git a/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChannelNoOwnerPermissionException.kt b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChannelNoOwnerPermissionException.kt
new file mode 100644
index 0000000..2cc6625
--- /dev/null
+++ b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChannelNoOwnerPermissionException.kt
@@ -0,0 +1,7 @@
+package dev.m1sk9.lunaticChat.engine.exception
+
+import java.util.UUID
+
+class ChannelNoOwnerPermissionException(
+ ownerId: UUID,
+) : Exception("No permission for channel owned by player with ID $ownerId.")
diff --git a/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChannelNotFoundException.kt b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChannelNotFoundException.kt
new file mode 100644
index 0000000..0f60766
--- /dev/null
+++ b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChannelNotFoundException.kt
@@ -0,0 +1,5 @@
+package dev.m1sk9.lunaticChat.engine.exception
+
+class ChannelNotFoundException(
+ channelId: String,
+) : Exception("Channel with ID $channelId not found.")
diff --git a/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChannelNotMemberException.kt b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChannelNotMemberException.kt
new file mode 100644
index 0000000..b66c791
--- /dev/null
+++ b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChannelNotMemberException.kt
@@ -0,0 +1,8 @@
+package dev.m1sk9.lunaticChat.engine.exception
+
+import java.util.UUID
+
+class ChannelNotMemberException(
+ playerId: UUID,
+ channelId: String,
+) : Exception("Player $playerId is not a member of channel $channelId")
diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/ServiceInitializer.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/ServiceInitializer.kt
index 96dea1d..fa5181b 100644
--- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/ServiceInitializer.kt
+++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/ServiceInitializer.kt
@@ -1,7 +1,7 @@
package dev.m1sk9.lunaticChat.paper
import dev.m1sk9.lunaticChat.engine.converter.GoogleIMEClient
-import dev.m1sk9.lunaticChat.paper.channel.storage.ChannelStorage
+import dev.m1sk9.lunaticChat.paper.channel.ChannelStorage
import dev.m1sk9.lunaticChat.paper.command.handler.DirectMessageHandler
import dev.m1sk9.lunaticChat.paper.config.LunaticChatConfiguration
import dev.m1sk9.lunaticChat.paper.converter.ConversionCache
diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/channel/ChannelManager.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/channel/ChannelManager.kt
new file mode 100644
index 0000000..a332e02
--- /dev/null
+++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/channel/ChannelManager.kt
@@ -0,0 +1,163 @@
+package dev.m1sk9.lunaticChat.paper.channel
+
+import dev.m1sk9.lunaticChat.engine.channel.modal.Channel
+import dev.m1sk9.lunaticChat.engine.channel.modal.ChannelData
+import dev.m1sk9.lunaticChat.engine.channel.modal.ChannelMember
+import dev.m1sk9.lunaticChat.engine.channel.modal.ChannelRole
+import dev.m1sk9.lunaticChat.engine.exception.ChannelNoOwnerPermissionException
+import dev.m1sk9.lunaticChat.engine.exception.ChannelNotFoundException
+import io.ktor.util.collections.ConcurrentMap
+import java.util.UUID
+import java.util.logging.Logger
+import kotlin.collections.forEach
+
+class ChannelManager(
+ private val storage: ChannelStorage,
+ private val logger: Logger,
+) {
+ private val channelsCache = ConcurrentMap<String, Channel>()
+ private val membersCache = ConcurrentMap<String, MutableList<ChannelMember>>()
+
+ /**
+ * Initializes the ChannelManager by loading data from storage.
+ */
+ fun initialize() {
+ val data = storage.loadFromDisk()
+ channelsCache.putAll(data.channels)
+ data.members.forEach { (channelId, members) ->
+ membersCache[channelId] = members.toMutableList()
+ }
+ logger.info("ChannelManager initialized with ${channelsCache.size} channels.")
+ }
+
+ /**
+ * Creates a new channel.
+ *
+ * @param channel The channel to create.
+ * @return Result containing the created channel or an error if the channel already exists.
+ * @throws ChannelNotFoundException if a channel with the same ID already exists.
+ */
+ fun createChannel(channel: Channel): Result<Channel> {
+ if (channelsCache.containsKey(channel.id)) {
+ return Result.failure(ChannelNotFoundException(channel.id))
+ }
+
+ channelsCache[channel.id] = channel
+
+ val ownerMember =
+ ChannelMember(
+ channelId = channel.id,
+ playerId = channel.ownerId,
+ role = ChannelRole.OWNER,
+ )
+ membersCache[channel.id] = mutableListOf(ownerMember)
+
+ saveToStorage()
+ logger.info("Created new channel with ID ${channel.id}.")
+ return Result.success(channel)
+ }
+
+ /**
+ * Deletes a channel.
+ *
+ * @param channelId The ID of the channel to delete.
+ * @param requesterId The ID of the player requesting the deletion.
+ * @return Result indicating success or failure of the deletion.
+ * @throws ChannelNotFoundException if the channel does not exist.
+ */
+ fun deleteChannel(
+ channelId: String,
+ requesterId: UUID,
+ ): Result<Unit> {
+ val channel =
+ channelsCache[channelId]
+ ?: return Result.failure(ChannelNotFoundException(channelId))
+
+ if (channel.ownerId != requesterId) {
+ return Result.failure(ChannelNoOwnerPermissionException(requesterId))
+ }
+
+ channelsCache.remove(channelId)
+ membersCache.remove(channelId)
+
+ saveToStorage()
+ logger.info("Owner with ID $requesterId deleted channel with ID $channelId.")
+ return Result.success(Unit)
+ }
+
+ /**
+ * Retrieves a channel by its ID.
+ *
+ * @param channelId The ID of the channel to retrieve.
+ * @return Result containing the channel or an error if not found.
+ * @throws ChannelNotFoundException if the channel does not exist.
+ */
+ fun getChannel(channelId: String): Result<Channel> {
+ val channel =
+ channelsCache[channelId]
+ ?: return Result.failure(ChannelNotFoundException(channelId))
+ return Result.success(channel)
+ }
+
+ /**
+ * Retrieves all channels.
+ *
+ * @return Result containing the list of all channels.
+ */
+ fun getAllChannels(): Result<List<Channel>> = Result.success(channelsCache.values.toList())
+
+ /**
+ * Retrieves all public channels.
+ *
+ * @return Result containing the list of public channels.
+ */
+ fun getPublicChannels(): Result<List<Channel>> {
+ val channels =
+ channelsCache.values
+ .filter { !it.isPrivate }
+ .sortedBy { it.name }
+ return Result.success(channels)
+ }
+
+ /**
+ * Retrieves members of a channel.
+ *
+ * @param channelId The ID of the channel.
+ * @return Result containing the list of channel members or an error if the channel is not found.
+ * @throws ChannelNotFoundException if the channel does not exist.
+ */
+ fun getChannelMembers(channelId: String): Result<List<ChannelMember>> {
+ val channel =
+ channelsCache[channelId]
+ ?: return Result.failure(ChannelNotFoundException(channelId))
+
+ val members = membersCache[channelId]?.toList() ?: emptyList()
+ return Result.success(members)
+ }
+
+ /**
+ * Saves the current state of channels and members to storage asynchronously.
+ */
+ private fun saveToStorage() {
+ val data =
+ ChannelData(
+ channels = channelsCache.toMap(),
+ members = membersCache.mapValues { it.value.toList() },
+ )
+ storage.queueAsyncSave(data)
+ logger.fine("${channelsCache.size} channels queued for saving to storage.")
+ }
+
+ /**
+ * Saves the current state of channels and members to storage synchronously.
+ * Should only br called during server shutdown.
+ */
+ fun saveToDisk() {
+ val data =
+ ChannelData(
+ channels = channelsCache.toMap(),
+ members = membersCache.mapValues { it.value.toList() },
+ )
+ storage.saveToDisk(data)
+ }
+}
diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/channel/ChannelMembershipManager.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/channel/ChannelMembershipManager.kt
new file mode 100644
index 0000000..29c6bd9
--- /dev/null
+++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/channel/ChannelMembershipManager.kt
@@ -0,0 +1,71 @@
+package dev.m1sk9.lunaticChat.paper.channel
+
+import dev.m1sk9.lunaticChat.engine.channel.modal.ChannelRole
+import dev.m1sk9.lunaticChat.engine.exception.ChannelNotMemberException
+import java.util.UUID
+import java.util.logging.Logger
+
+class ChannelMembershipManager(
+ private val channelManager: ChannelManager,
+ private val logger: Logger,
+) {
+ /**
+ * Checks if a player is a member of a channel.
+ *
+ * @param playerId The UUID of the player.
+ * @param channelId The ID of the channel.
+ * @return Result containing true if the player is a member, false otherwise.
+ */
+ fun isMember(
+ playerId: UUID,
+ channelId: String,
+ ): Result<Boolean> =
+ channelManager.getChannelMembers(channelId).map { members ->
+ members.any { it.playerId == playerId }
+ }
+
+ /**
+ * Gets the role of a member in a channel.
+ *
+ * @param playerId The UUID of the player.
+ * @param channelId The ID of the channel.
+ * @return Result containing the ChannelRole of the member.
+ * @throws ChannelNotMemberException if the player is not a member of the channel.
+ */
+ fun getMemberRole(
+ playerId: UUID,
+ channelId: String,
+ ): Result<ChannelRole> =
+ channelManager.getChannelMembers(channelId).mapCatching { members ->
+ members
+ .find {
+ it.playerId == playerId
+ }?.role ?: throw ChannelNotMemberException(playerId, channelId)
+ }
+
+ /**
+ * Checks if a player has a specific role or higher in a channel.
+ *
+ * @param playerId The UUID of the player.
+ * @param channelId The ID of the channel.
+ * @param requireRole The required ChannelRole.
+ * @return Result containing true if the player has the required role or higher, false otherwise.
+ */
+ fun hasRole(
+ playerId: UUID,
+ channelId: String,
+ requireRole: ChannelRole,
+ ): Result<Boolean> =
+ getMemberRole(playerId, channelId).fold(
+ onSuccess = { playerRole ->
+ val hasRequiredRole =
+ when (requireRole) {
+ ChannelRole.MEMBER -> true
+ ChannelRole.MODERATOR -> playerRole in setOf(ChannelRole.MODERATOR, ChannelRole.OWNER)
+ ChannelRole.OWNER -> playerRole == ChannelRole.OWNER
+ }
+ Result.success(hasRequiredRole)
+ },
+ onFailure = { Result.success(false) },
+ )
+}
diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/channel/storage/ChannelStorage.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/channel/ChannelStorage.kt
index 7b85703..ac7d180 100644
--- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/channel/storage/ChannelStorage.kt
+++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/channel/ChannelStorage.kt
@@ -1,4 +1,4 @@
-package dev.m1sk9.lunaticChat.paper.channel.storage
+package dev.m1sk9.lunaticChat.paper.channel
import dev.m1sk9.lunaticChat.engine.channel.modal.ChannelData
import dev.m1sk9.lunaticChat.engine.exception.ChannelStorageLoadException
@@ -33,7 +33,7 @@ class ChannelStorage(
* Loads channel data from disk.
*
* @return The loaded ChannelData.
- * @throws ChannelStorageLoadException if there is an error loading the data.
+ * @throws dev.m1sk9.lunaticChat.engine.exception.ChannelStorageLoadException if there is an error loading the data.
*/
fun loadFromDisk(): ChannelData {
if (!channelsFile.exists()) {
@@ -61,7 +61,7 @@ class ChannelStorage(
* Saves channel data to disk.
*
* @param data The ChannelData to save.
- * @throws ChannelStorageSaveException if there is an error saving the data.
+ * @throws dev.m1sk9.lunaticChat.engine.exception.ChannelStorageSaveException if there is an error saving the data.
*/
fun saveToDisk(data: ChannelData) {
try {