diff options
| author | Sho Sakuma <me@m1sk9.dev> | 2026-01-25 16:19:24 +0900 |
|---|---|---|
| committer | Sho Sakuma <me@m1sk9.dev> | 2026-01-25 16:19:24 +0900 |
| commit | 9f4fb8f4abec562cf13cb1dd225828876875c790 (patch) | |
| tree | 5deccc82809801f45cd98163da936e34cf9bd77d | |
| parent | 3ec21511191be259370056c91549d0b981a3f4c5 (diff) | |
| download | LunaticChat-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.kt | 5 | ||||
| -rw-r--r-- | engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChannelNoOwnerPermissionException.kt | 7 | ||||
| -rw-r--r-- | engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChannelNotFoundException.kt | 5 | ||||
| -rw-r--r-- | engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChannelNotMemberException.kt | 8 | ||||
| -rw-r--r-- | platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/ServiceInitializer.kt | 2 | ||||
| -rw-r--r-- | platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/channel/ChannelManager.kt | 163 | ||||
| -rw-r--r-- | platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/channel/ChannelMembershipManager.kt | 71 | ||||
| -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 { |
