Skip to content

Commit

Permalink
Checks both private and public channels.
Browse files Browse the repository at this point in the history
Adds channel cache.
  • Loading branch information
outofcoffee committed Aug 22, 2019
1 parent e48274f commit 1c157d2
Show file tree
Hide file tree
Showing 7 changed files with 65 additions and 24 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package com.gatehill.slackgateway.backend.slack.model

import com.gatehill.slackgateway.model.ChannelType

interface SlackChannel {
val channelType: ChannelType
val id: String
val name: String
val members: List<String>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.gatehill.slackgateway.backend.slack.model

import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import com.gatehill.slackgateway.model.ChannelType

/**
* This is actually a private channel.
Expand All @@ -10,4 +11,6 @@ data class SlackGroup(
override val id: String,
override val name: String,
override val members: List<String>
) : SlackChannel
) : SlackChannel {
override val channelType = ChannelType.PRIVATE
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package com.gatehill.slackgateway.backend.slack.model

import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import com.gatehill.slackgateway.model.ChannelType

@JsonIgnoreProperties(ignoreUnknown = true)
data class SlackPublicChannel(
override val id: String,
override val name: String,
override val members: List<String>
) : SlackChannel
) : SlackChannel {
override val channelType = ChannelType.PUBLIC
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@ package com.gatehill.slackgateway.backend.slack.service
import com.fasterxml.jackson.module.kotlin.readValue
import com.gatehill.slackgateway.backend.slack.config.SlackSettings
import com.gatehill.slackgateway.backend.slack.model.SlackChannel
import com.gatehill.slackgateway.config.Settings
import com.gatehill.slackgateway.exception.HttpCodeException
import com.gatehill.slackgateway.model.ChannelType
import com.gatehill.slackgateway.service.OutboundMessageService
import com.gatehill.slackgateway.util.jsonMapper
import com.google.common.cache.CacheBuilder
import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.Logger
import java.util.concurrent.TimeUnit
import javax.inject.Inject

/**
Expand All @@ -22,37 +25,65 @@ class SlackOutboundMessageService @Inject constructor(

private val logger: Logger = LogManager.getLogger(SlackOutboundMessageService::class.java)

override fun forward(raw: String, channelType: ChannelType) {
private val channelCache = CacheBuilder.newBuilder()
.expireAfterWrite(SlackSettings.cacheSeconds, TimeUnit.SECONDS)
.build<ChannelType, List<SlackChannel>>()

override fun forward(raw: String, channelType: ChannelType?) {
val message = jsonMapper.readValue<Map<String, *>>(raw).toMutableMap()
forward(message, channelType)
}

override fun forward(message: Map<String, *>, channelType: ChannelType) {
override fun forward(message: Map<String, *>, channelType: ChannelType?) {
val channelName = message["channel"] as String? ?: throw HttpCodeException(400, "No channel in message")
val channel = ensureChannelExists(channelName, channelType)
checkParticipants(channel, channelType)
checkParticipants(channel)
slackOperationsService.sendMessage(message)
}

private fun ensureChannelExists(channelName: String, channelType: ChannelType): SlackChannel {
slackOperationsService.listChannels(channelType).firstOrNull { it.name == channelName }?.let {
private fun ensureChannelExists(channelName: String, channelType: ChannelType?): SlackChannel =
searchForChannel(channelName, channelType)?.let { channel ->
// channel already exists
logger.debug("Channel $channelName ($channelType) already exists")
return it
logger.debug("Channel $channelName (${channel.channelType}) already exists")
channel

} ?: run {
// create the channel
logger.debug("Channel $channelName ($channelType) does not exist - creating")
return slackOperationsService.createChannel(channelName, channelType)
val createChannelType = channelType ?: Settings.defaultChannelType
logger.debug("Channel $channelName ($createChannelType) does not exist - creating")
slackOperationsService.createChannel(channelName, createChannelType)
}

private fun searchForChannel(channelName: String, channelType: ChannelType?): SlackChannel? =
channelType?.let {
// search for a channel of a particular type
searchCacheForChannel(channelName, channelType)
?: searchSlackApiForChannel(channelName, channelType)

} ?: run {
// search caches first, then call APIs
searchCacheForChannel(channelName, ChannelType.PRIVATE)
?: searchCacheForChannel(channelName, ChannelType.PUBLIC)
?: searchSlackApiForChannel(channelName, ChannelType.PRIVATE)
?: searchSlackApiForChannel(channelName, ChannelType.PUBLIC)
}

private fun searchCacheForChannel(channelName: String, channelType: ChannelType): SlackChannel? =
channelCache.getIfPresent(channelType)?.firstOrNull { it.name == channelName }

private fun searchSlackApiForChannel(channelName: String, channelType: ChannelType): SlackChannel? {
val channels = slackOperationsService.listChannels(channelType)
channelCache.put(channelType, channels)
return channels.firstOrNull { it.name == channelName }
}

private fun checkParticipants(channel: SlackChannel, channelType: ChannelType) {
private fun checkParticipants(channel: SlackChannel) {
if (SlackSettings.inviteGroups.isEmpty() && SlackSettings.inviteMembers.isEmpty()) {
logger.debug("Skipping check for participants of channel: ${channel.name} ($channelType) - no participants are configured")
logger.debug("Skipping check for participants of channel: ${channel.name} (${channel.channelType}) - no participants are configured")
return
}

logger.debug("Checking participants of channel: ${channel.name} ($channelType)")
logger.debug("Checking participants of channel: ${channel.name} (${channel.channelType})")

val memberIds = mutableListOf<String>()
SlackSettings.inviteGroups.forEach { userGroupName ->
Expand All @@ -69,16 +100,16 @@ class SlackOutboundMessageService @Inject constructor(
slackOperationsService.users.firstOrNull { user -> user.name == memberUsername }?.id
}

logger.info("Inviting ${memberIds.size} members to channel: ${channel.name} ($channelType)")
logger.info("Inviting ${memberIds.size} members to channel: ${channel.name} (${channel.channelType})")

memberIds
.filterNot { memberId: String -> channel.members.contains(memberId) }
.forEach { memberId ->
logger.info("Inviting member $memberId to channel ${channel.name} ($channelType)")
logger.info("Inviting member $memberId to channel ${channel.name} (${channel.channelType})")
try {
slackOperationsService.inviteToChannel(channel, channelType, memberId)
slackOperationsService.inviteToChannel(channel, channel.channelType, memberId)
} catch (e: Exception) {
logger.warn("Error inviting member $memberId to channel ${channel.name} ($channelType) - continuing", e)
logger.warn("Error inviting member $memberId to channel ${channel.name} (${channel.channelType}) - continuing", e)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,8 @@ enum class ChannelType {
.firstOrNull { it.name.equals(channelType, ignoreCase = true) }
?: throw IllegalStateException("Unsupported channel type: $channelType")
}

override fun toString(): String {
return name.toLowerCase()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ import com.gatehill.slackgateway.model.ChannelType
* @author Pete Cornish {@literal <outofcoffee@gmail.com>}
*/
interface OutboundMessageService {
fun forward(raw: String, channelType: ChannelType)
fun forward(message: Map<String, *>, channelType: ChannelType)
fun forward(raw: String, channelType: ChannelType?)
fun forward(message: Map<String, *>, channelType: ChannelType?)
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.gatehill.slackgateway.http.chat

import com.fasterxml.jackson.module.kotlin.readValue
import com.gatehill.slackgateway.config.Settings
import com.gatehill.slackgateway.exception.HttpCodeException
import com.gatehill.slackgateway.http.config.ChatSettings
import com.gatehill.slackgateway.model.ChannelType
Expand Down Expand Up @@ -162,9 +161,7 @@ open class HttpInboundMessageServiceImpl @Inject constructor(

private fun determineChannelType(
routingContext: RoutingContext
): ChannelType = routingContext.request().getParam("channel_type")
?.let { ChannelType.parse(it) }
?: Settings.defaultChannelType
): ChannelType? = routingContext.request().getParam("channel_type")?.let { ChannelType.parse(it) }

private fun buildTextMessage(routingContext: RoutingContext) =
(routingContext.request().getParam("text") ?: "") +
Expand Down

0 comments on commit 1c157d2

Please sign in to comment.