collavre_slack 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +80 -0
- data/Rakefile +3 -0
- data/app/assets/stylesheets/collavre_slack/slack_integration.css +15 -0
- data/app/controllers/collavre_slack/application_controller.rb +12 -0
- data/app/controllers/collavre_slack/creatives/slack_integrations_controller.rb +101 -0
- data/app/controllers/collavre_slack/slack_auth_controller.rb +105 -0
- data/app/controllers/collavre_slack/slack_events_controller.rb +63 -0
- data/app/controllers/collavre_slack/slack_messages_controller.rb +24 -0
- data/app/javascript/collavre_slack.js +509 -0
- data/app/jobs/collavre_slack/application_job.rb +4 -0
- data/app/jobs/collavre_slack/slack_channel_sync_job.rb +45 -0
- data/app/jobs/collavre_slack/slack_inbound_message_delete_job.rb +24 -0
- data/app/jobs/collavre_slack/slack_inbound_message_job.rb +129 -0
- data/app/jobs/collavre_slack/slack_inbound_message_update_job.rb +22 -0
- data/app/jobs/collavre_slack/slack_inbound_reaction_job.rb +47 -0
- data/app/jobs/collavre_slack/slack_message_delete_job.rb +29 -0
- data/app/jobs/collavre_slack/slack_message_job.rb +45 -0
- data/app/jobs/collavre_slack/slack_message_update_job.rb +32 -0
- data/app/jobs/collavre_slack/slack_reaction_job.rb +45 -0
- data/app/models/collavre_slack/application_record.rb +5 -0
- data/app/models/collavre_slack/slack_account.rb +13 -0
- data/app/models/collavre_slack/slack_channel_link.rb +16 -0
- data/app/models/collavre_slack/slack_comment_link.rb +18 -0
- data/app/models/collavre_slack/slack_message_log.rb +11 -0
- data/app/models/collavre_slack/slack_user_mapping.rb +10 -0
- data/app/models/concerns/collavre_slack/slack_dispatchable.rb +97 -0
- data/app/models/concerns/collavre_slack/slack_reaction_dispatchable.rb +55 -0
- data/app/services/collavre_slack/emoji_mapping.rb +90 -0
- data/app/services/collavre_slack/mention_mapping.rb +45 -0
- data/app/services/collavre_slack/slack_client.rb +141 -0
- data/app/services/collavre_slack/slack_event_handler.rb +259 -0
- data/app/services/collavre_slack/slack_integration_service.rb +26 -0
- data/app/services/collavre_slack/slack_message_dispatcher.rb +24 -0
- data/app/views/collavre_slack/creatives/slack_integrations/index.html.erb +18 -0
- data/app/views/collavre_slack/integrations/_modal.html.erb +66 -0
- data/config/initializers/collavre_slack.rb +7 -0
- data/config/locales/en.yml +57 -0
- data/config/locales/ko.yml +57 -0
- data/config/routes.rb +11 -0
- data/db/migrate/20250201000001_create_slack_accounts.rb +17 -0
- data/db/migrate/20250201000002_create_slack_channel_links.rb +17 -0
- data/db/migrate/20250201000003_create_slack_user_mappings.rb +15 -0
- data/db/migrate/20250201000004_create_slack_message_logs.rb +14 -0
- data/db/migrate/20250201000005_add_last_synced_at_to_slack_channel_links.rb +5 -0
- data/db/migrate/20260130010000_create_slack_comment_links.rb +13 -0
- data/db/migrate/20260130010001_add_comment_id_to_slack_message_logs.rb +5 -0
- data/db/migrate/20260130020000_add_cascade_delete_to_slack_comment_foreign_keys.rb +11 -0
- data/db/migrate/20260131000000_remove_is_active_from_slack_channel_links.rb +5 -0
- data/lib/collavre_slack/configuration.rb +13 -0
- data/lib/collavre_slack/engine.rb +97 -0
- data/lib/collavre_slack/version.rb +3 -0
- data/lib/collavre_slack.rb +13 -0
- metadata +151 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: a0a684e5d2b0b7f2ebcd19950a0782e515d24ad1f935360f1b3c10dc59b77636
|
|
4
|
+
data.tar.gz: bf77b0f047f322017289c622509460e1128f013a6c871b004b199e7675612826
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 4b3eb5ff2a65bfda47e5d478a8fefd0f843bf52f783e061f9227afa593f3c2cab2c9de123234ef71d5af4a7339b2c5dd34cbb6ad13643ac76ae6f6fc41809958
|
|
7
|
+
data.tar.gz: d5d9db76818d0d5e271efae68349c7d6f87cd91a8b765597e36ed4a8f3f2dff119f5eb9e7998b1ef45cead4372c2bf878d462e78597cc65bd4e62607df6dddf1
|
data/README.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Collavre Slack Engine
|
|
2
|
+
|
|
3
|
+
Slack integration plugin engine for Collavre. Provides OAuth installation, channel linking, and bi-directional chat sync.
|
|
4
|
+
|
|
5
|
+
## Documentation
|
|
6
|
+
|
|
7
|
+
- [Setup Guide](docs/SETUP.md) - Step-by-step instructions for creating and configuring a Slack App
|
|
8
|
+
- [Architecture](docs/ARCHITECTURE.md) - Technical documentation about the integration
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
### 1. Add to Gemfile
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
# Gemfile
|
|
16
|
+
gem "collavre_slack", path: "engines/collavre_slack"
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### 2. Add JavaScript Import (Required)
|
|
20
|
+
|
|
21
|
+
The JavaScript must be explicitly imported in your host application:
|
|
22
|
+
|
|
23
|
+
```javascript
|
|
24
|
+
// app/javascript/application.js
|
|
25
|
+
import "collavre_slack"
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
This is required because the host app controls which integrations are loaded. Without this import, the Slack integration modal and badge functionality will not work.
|
|
29
|
+
|
|
30
|
+
### 3. Add Stylesheet (Required)
|
|
31
|
+
|
|
32
|
+
Include the Slack integration stylesheet in your layout:
|
|
33
|
+
|
|
34
|
+
```erb
|
|
35
|
+
<%# app/views/layouts/application.html.erb %>
|
|
36
|
+
<%= stylesheet_link_tag "collavre_slack/slack_integration" %>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### 4. Configure Environment Variables
|
|
40
|
+
|
|
41
|
+
| Variable | Required | Description |
|
|
42
|
+
|----------|----------|-------------|
|
|
43
|
+
| `SLACK_CLIENT_ID` | Yes | OAuth Client ID |
|
|
44
|
+
| `SLACK_CLIENT_SECRET` | Yes | OAuth Client Secret |
|
|
45
|
+
| `SLACK_SIGNING_SECRET` | Yes | Webhook signature verification |
|
|
46
|
+
| `SLACK_REDIRECT_URI` | Yes | OAuth callback URL |
|
|
47
|
+
| `SLACK_SCOPES` | No | OAuth scopes (has sensible defaults) |
|
|
48
|
+
|
|
49
|
+
### 5. Run Migrations
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
rails db:migrate
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Automatic Configuration
|
|
56
|
+
|
|
57
|
+
The following are automatically configured by the engine:
|
|
58
|
+
|
|
59
|
+
- **Routes**: Mounted at `/slack` (no manual `mount` needed in `routes.rb`)
|
|
60
|
+
- **Asset Paths**: Stylesheets are automatically added to Propshaft asset paths
|
|
61
|
+
- **Migrations**: Database migrations are automatically included
|
|
62
|
+
- **i18n**: Locale files (en, ko) are automatically loaded
|
|
63
|
+
- **Integration Registry**: Automatically registers with `Collavre::IntegrationRegistry`
|
|
64
|
+
|
|
65
|
+
## Slack App Setup
|
|
66
|
+
|
|
67
|
+
1. Create a Slack App at [api.slack.com/apps](https://api.slack.com/apps)
|
|
68
|
+
2. Configure OAuth scopes and redirect URL
|
|
69
|
+
3. Set up Event Subscriptions
|
|
70
|
+
|
|
71
|
+
See [docs/SETUP.md](docs/SETUP.md) for detailed instructions.
|
|
72
|
+
|
|
73
|
+
## Features
|
|
74
|
+
|
|
75
|
+
- OAuth 2.0 installation flow
|
|
76
|
+
- Channel linking to Creatives
|
|
77
|
+
- Bi-directional message sync (Slack ↔ Collavre)
|
|
78
|
+
- User mention mapping
|
|
79
|
+
- Rate limit handling with automatic retry
|
|
80
|
+
- Request signature verification for security
|
data/Rakefile
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/* Slack channel selection */
|
|
2
|
+
.slack-channel-item {
|
|
3
|
+
padding: 0.5em 1em;
|
|
4
|
+
cursor: pointer;
|
|
5
|
+
border-bottom: 1px solid var(--color-border);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.slack-channel-item:hover {
|
|
9
|
+
background: var(--color-drag-over);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.slack-channel-item.active {
|
|
13
|
+
background: var(--color-drag-over);
|
|
14
|
+
border-left: 3px solid var(--color-secondary-active);
|
|
15
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
module CollavreSlack
|
|
2
|
+
module Creatives
|
|
3
|
+
class SlackIntegrationsController < ApplicationController
|
|
4
|
+
before_action :set_creative
|
|
5
|
+
|
|
6
|
+
def index
|
|
7
|
+
# Always use origin creative for Slack links (chat messages are on origin)
|
|
8
|
+
target_creative = @creative.effective_origin
|
|
9
|
+
@links = SlackChannelLink.where(creative: target_creative)
|
|
10
|
+
respond_to do |format|
|
|
11
|
+
format.json do
|
|
12
|
+
slack_account = Current.user ? SlackAccount.find_by(user: Current.user) : nil
|
|
13
|
+
channels = slack_account ? fetch_channels(slack_account) : []
|
|
14
|
+
|
|
15
|
+
render json: {
|
|
16
|
+
connected: slack_account.present?,
|
|
17
|
+
channels: channels,
|
|
18
|
+
links: @links.map { |link|
|
|
19
|
+
{
|
|
20
|
+
id: link.id,
|
|
21
|
+
channel_id: link.channel_id,
|
|
22
|
+
channel_name: link.channel_name,
|
|
23
|
+
last_synced_at: link.last_synced_at
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
end
|
|
28
|
+
format.html
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def create
|
|
33
|
+
unless @creative.has_permission?(Current.user, :feedback)
|
|
34
|
+
render json: { success: false, error: I18n.t("collavre_slack.errors.forbidden") }, status: :forbidden
|
|
35
|
+
return
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Find the user's Slack account
|
|
39
|
+
slack_account = if params[:slack_account_id].present?
|
|
40
|
+
SlackAccount.find(params[:slack_account_id])
|
|
41
|
+
else
|
|
42
|
+
SlackAccount.find_by(user: Current.user)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
unless slack_account
|
|
46
|
+
render json: { success: false, error: I18n.t("collavre_slack.errors.no_slack_account") }, status: :unprocessable_entity
|
|
47
|
+
return
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Always link to origin creative (chat messages are on origin)
|
|
51
|
+
target_creative = @creative.effective_origin
|
|
52
|
+
|
|
53
|
+
service = SlackIntegrationService.new(user: Current.user, slack_account: slack_account)
|
|
54
|
+
link = service.link_channel(creative: target_creative, channel_id: params[:channel_id], channel_name: params[:channel_name])
|
|
55
|
+
|
|
56
|
+
if link.persisted?
|
|
57
|
+
render json: { success: true, link: { id: link.id, channel_id: link.channel_id, channel_name: link.channel_name } }, status: :created
|
|
58
|
+
else
|
|
59
|
+
render json: { success: false, error: link.errors.full_messages.join(", ") }, status: :unprocessable_entity
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def destroy
|
|
64
|
+
link = SlackChannelLink.find(params[:id])
|
|
65
|
+
# Check against origin creative
|
|
66
|
+
target_creative = @creative.effective_origin
|
|
67
|
+
unless link.creative_id == target_creative.id
|
|
68
|
+
render json: { success: false, error: I18n.t("collavre_slack.errors.not_found") }, status: :not_found
|
|
69
|
+
return
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
unless @creative.has_permission?(Current.user, :feedback)
|
|
73
|
+
render json: { success: false, error: I18n.t("collavre_slack.errors.forbidden") }, status: :forbidden
|
|
74
|
+
return
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
SlackIntegrationService.new(user: Current.user, slack_account: link.slack_account).unlink_channel(link)
|
|
78
|
+
render json: { success: true }
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
def set_creative
|
|
84
|
+
@creative = Collavre::Creative.find(params[:creative_id])
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def fetch_channels(slack_account)
|
|
88
|
+
client = SlackClient.new(access_token: slack_account.access_token)
|
|
89
|
+
response = client.list_channels
|
|
90
|
+
return [] unless response[:ok]
|
|
91
|
+
|
|
92
|
+
(response[:channels] || []).map do |channel|
|
|
93
|
+
{ id: channel[:id], name: channel[:name] }
|
|
94
|
+
end
|
|
95
|
+
rescue StandardError => e
|
|
96
|
+
Rails.logger.error("Failed to fetch Slack channels: #{e.message}")
|
|
97
|
+
[]
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
module CollavreSlack
|
|
2
|
+
class SlackAuthController < ApplicationController
|
|
3
|
+
allow_unauthenticated_access only: :callback
|
|
4
|
+
|
|
5
|
+
def start
|
|
6
|
+
unless Current.user
|
|
7
|
+
render json: { error: I18n.t("collavre_slack.errors.authentication_required") }, status: :unauthorized
|
|
8
|
+
return
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Sign the state with user_id to recover it securely in callback (popup windows don't share session)
|
|
12
|
+
state_data = { nonce: SecureRandom.hex(16), user_id: Current.user.id, exp: 10.minutes.from_now.to_i }
|
|
13
|
+
state = message_verifier.generate(state_data)
|
|
14
|
+
session[:slack_oauth_state] = state
|
|
15
|
+
|
|
16
|
+
redirect_to slack_client.oauth_authorize_url(state: state), allow_other_host: true
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def callback
|
|
20
|
+
unless params[:code].present?
|
|
21
|
+
render json: { error: I18n.t("collavre_slack.errors.missing_oauth_code") }, status: :unprocessable_entity
|
|
22
|
+
return
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Verify and decode user_id from signed state (required - popup windows don't share session)
|
|
26
|
+
unless params[:state].present?
|
|
27
|
+
render json: { error: I18n.t("collavre_slack.errors.missing_oauth_state") }, status: :unprocessable_entity
|
|
28
|
+
return
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
begin
|
|
32
|
+
state_data = message_verifier.verify(params[:state]).with_indifferent_access
|
|
33
|
+
Rails.logger.info("[SlackAuth] State data: #{state_data.inspect}")
|
|
34
|
+
# Check expiration
|
|
35
|
+
if state_data[:exp] && Time.at(state_data[:exp]) < Time.current
|
|
36
|
+
render json: { error: I18n.t("collavre_slack.errors.invalid_oauth_state") }, status: :unprocessable_entity
|
|
37
|
+
return
|
|
38
|
+
end
|
|
39
|
+
user = ::User.find(state_data[:user_id])
|
|
40
|
+
Rails.logger.info("[SlackAuth] Verified user_id=#{user.id} from signed state")
|
|
41
|
+
rescue ActiveSupport::MessageVerifier::InvalidSignature => e
|
|
42
|
+
Rails.logger.warn("[SlackAuth] Invalid state signature: #{e.message}")
|
|
43
|
+
render json: { error: I18n.t("collavre_slack.errors.invalid_oauth_state") }, status: :unprocessable_entity
|
|
44
|
+
return
|
|
45
|
+
rescue ActiveRecord::RecordNotFound
|
|
46
|
+
Rails.logger.warn("[SlackAuth] User not found from state")
|
|
47
|
+
render json: { error: I18n.t("collavre_slack.errors.user_not_found") }, status: :unprocessable_entity
|
|
48
|
+
return
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
oauth_response = slack_client.oauth_access(code: params[:code], redirect_uri: slack_client.redirect_uri)
|
|
52
|
+
if oauth_response[:ok] != true
|
|
53
|
+
render json: { error: oauth_response[:error] || I18n.t("collavre_slack.errors.oauth_failed") }, status: :unprocessable_entity
|
|
54
|
+
return
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
Rails.logger.info("[SlackAuth] OAuth response: team=#{oauth_response.dig(:team, :id)}, user=#{user&.id}")
|
|
58
|
+
|
|
59
|
+
account = SlackAccount.find_or_initialize_by(team_id: oauth_response.dig(:team, :id))
|
|
60
|
+
account.user ||= user
|
|
61
|
+
account.team_name = oauth_response.dig(:team, :name)
|
|
62
|
+
account.access_token = oauth_response[:access_token]
|
|
63
|
+
account.authed_user_id = oauth_response.dig(:authed_user, :id)
|
|
64
|
+
account.scopes = oauth_response[:scope]
|
|
65
|
+
|
|
66
|
+
if account.save
|
|
67
|
+
# Render HTML that closes the popup and notifies the parent window
|
|
68
|
+
render html: close_popup_html.html_safe, layout: false
|
|
69
|
+
else
|
|
70
|
+
Rails.logger.error("[SlackAuth] Failed to save account: #{account.errors.full_messages.join(', ')}")
|
|
71
|
+
render json: { error: I18n.t("collavre_slack.errors.save_failed", errors: account.errors.full_messages.join(", ")) }, status: :unprocessable_entity
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
def slack_client
|
|
78
|
+
@slack_client ||= SlackClient.new
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def message_verifier
|
|
82
|
+
@message_verifier ||= Rails.application.message_verifier("slack_oauth")
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def close_popup_html
|
|
86
|
+
title = I18n.t("collavre_slack.views.auth.success_title")
|
|
87
|
+
message = I18n.t("collavre_slack.views.auth.success_message")
|
|
88
|
+
<<~HTML
|
|
89
|
+
<!DOCTYPE html>
|
|
90
|
+
<html>
|
|
91
|
+
<head><title>#{title}</title></head>
|
|
92
|
+
<body>
|
|
93
|
+
<p>#{message}</p>
|
|
94
|
+
<script>
|
|
95
|
+
if (window.opener) {
|
|
96
|
+
window.opener.postMessage({ type: 'slack-auth-success' }, '*');
|
|
97
|
+
}
|
|
98
|
+
window.close();
|
|
99
|
+
</script>
|
|
100
|
+
</body>
|
|
101
|
+
</html>
|
|
102
|
+
HTML
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
module CollavreSlack
|
|
2
|
+
class SlackEventsController < ApplicationController
|
|
3
|
+
allow_unauthenticated_access only: :create
|
|
4
|
+
skip_forgery_protection only: :create
|
|
5
|
+
|
|
6
|
+
def create
|
|
7
|
+
body = request.raw_post
|
|
8
|
+
payload = JSON.parse(body, symbolize_names: true)
|
|
9
|
+
|
|
10
|
+
# Handle URL verification challenge first (needed for initial Slack app setup)
|
|
11
|
+
# Slack sends a signed request, but we allow this through to complete setup
|
|
12
|
+
if payload[:type] == "url_verification"
|
|
13
|
+
render json: { challenge: payload[:challenge] }
|
|
14
|
+
return
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# For all other events, validate the signature
|
|
18
|
+
unless valid_signature?(body)
|
|
19
|
+
Rails.logger.warn("[SlackEvents] Invalid signature for event type: #{payload[:type]}")
|
|
20
|
+
render json: { error: I18n.t("collavre_slack.errors.invalid_signature") }, status: :unauthorized
|
|
21
|
+
return
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
handler = SlackEventHandler.new(payload: payload)
|
|
25
|
+
normalized = handler.call
|
|
26
|
+
|
|
27
|
+
if normalized
|
|
28
|
+
case normalized[:type]
|
|
29
|
+
when :message
|
|
30
|
+
SlackInboundMessageJob.perform_later(normalized)
|
|
31
|
+
when :reaction_added, :reaction_removed
|
|
32
|
+
SlackInboundReactionJob.perform_later(normalized)
|
|
33
|
+
when :message_updated
|
|
34
|
+
SlackInboundMessageUpdateJob.perform_later(normalized)
|
|
35
|
+
when :message_deleted
|
|
36
|
+
SlackInboundMessageDeleteJob.perform_later(normalized)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
head :ok
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def valid_signature?(body)
|
|
46
|
+
return false if signing_secret.blank?
|
|
47
|
+
|
|
48
|
+
timestamp = request.headers["X-Slack-Request-Timestamp"].to_s
|
|
49
|
+
signature = request.headers["X-Slack-Signature"].to_s
|
|
50
|
+
return false if timestamp.blank? || signature.blank?
|
|
51
|
+
|
|
52
|
+
return false if (Time.now.to_i - timestamp.to_i).abs > 300
|
|
53
|
+
|
|
54
|
+
base = "v0:#{timestamp}:#{body}"
|
|
55
|
+
expected = "v0=" + OpenSSL::HMAC.hexdigest("SHA256", signing_secret, base)
|
|
56
|
+
ActiveSupport::SecurityUtils.secure_compare(expected, signature)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def signing_secret
|
|
60
|
+
CollavreSlack.config.signing_secret
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module CollavreSlack
|
|
2
|
+
class SlackMessagesController < ApplicationController
|
|
3
|
+
before_action :set_creative
|
|
4
|
+
|
|
5
|
+
def create
|
|
6
|
+
unless @creative.has_permission?(Current.user, :feedback)
|
|
7
|
+
render json: { error: I18n.t("collavre_slack.errors.forbidden") }, status: :forbidden
|
|
8
|
+
return
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
channel_link = SlackChannelLink.find_by!(creative: @creative)
|
|
12
|
+
dispatcher = SlackMessageDispatcher.new(channel_link: channel_link)
|
|
13
|
+
log = dispatcher.enqueue(message: params[:message], sender: Current.user)
|
|
14
|
+
|
|
15
|
+
render json: { status: "queued", message_log_id: log.id }, status: :accepted
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def set_creative
|
|
21
|
+
@creative = Collavre::Creative.find(params[:creative_id])
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|