collavre_github 0.3.2 → 0.4.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 +4 -4
- data/app/controllers/collavre_github/auth_controller.rb +32 -1
- data/app/controllers/collavre_github/webhooks_controller.rb +2 -20
- data/app/services/collavre_github/client.rb +16 -1
- data/app/views/collavre_github/auth/setup.html.erb +389 -0
- data/config/locales/en.yml +8 -0
- data/config/locales/ko.yml +8 -0
- data/config/routes.rb +4 -0
- data/db/migrate/20260219095224_remove_github_gemini_prompt_from_creatives.rb +5 -0
- data/lib/collavre_github/version.rb +1 -1
- data/lib/tasks/mock_server.rake +8 -0
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 198ebcc760561f4ac05a67a9218db8e327bc4655cb89d412f10a5e674e2a18ed
|
|
4
|
+
data.tar.gz: 2ef18344c5388d0573baf3164c71ff99c891380e1c79b65284d69c8df3722221
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ae9f937c945668c02e65e7befc7b9ada33990db0723cb32497a1526bdd601b812575408c398d5e6e34c3afe48b5a7b0d729a5ab9e63a5530f18d5dffc53f6097
|
|
7
|
+
data.tar.gz: 7486890574e6f60b3644885f0dc8f2de2b406725985df31547745b6130fbc3d17961500f9a2245b074342744c3d5d00632e9c2430fbd3b0987e30c276a3a3367
|
|
@@ -21,7 +21,38 @@ module CollavreGithub
|
|
|
21
21
|
account.avatar_url = auth.info.image
|
|
22
22
|
account.save!
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
creative_id = session.delete(:github_creative_id)
|
|
25
|
+
if creative_id.present?
|
|
26
|
+
redirect_to setup_auth_path(creative_id: creative_id)
|
|
27
|
+
else
|
|
28
|
+
redirect_to collavre.creatives_path, notice: I18n.t("collavre_github.auth.connected")
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def setup
|
|
33
|
+
@creative_id = params[:creative_id]
|
|
34
|
+
unless @creative_id.present?
|
|
35
|
+
render plain: I18n.t("collavre_github.integration.missing_creative"), status: :bad_request
|
|
36
|
+
return
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
@creative = Collavre::Creative.find_by(id: @creative_id)
|
|
40
|
+
unless @creative
|
|
41
|
+
render plain: I18n.t("collavre_github.integration.missing_creative"), status: :not_found
|
|
42
|
+
return
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
unless @creative.has_permission?(Current.user, :admin)
|
|
46
|
+
render plain: I18n.t("collavre_github.errors.forbidden"), status: :forbidden
|
|
47
|
+
return
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
render layout: false
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def store_creative
|
|
54
|
+
session[:github_creative_id] = params[:creative_id]
|
|
55
|
+
head :ok
|
|
25
56
|
end
|
|
26
57
|
end
|
|
27
58
|
end
|
|
@@ -28,30 +28,12 @@ module CollavreGithub
|
|
|
28
28
|
|
|
29
29
|
content = format_github_event(event, payload)
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
creative.comments.create!(
|
|
32
32
|
user: nil,
|
|
33
33
|
content: content,
|
|
34
34
|
private: false
|
|
35
35
|
)
|
|
36
|
-
|
|
37
|
-
# Dispatch event for AI Agent routing
|
|
38
|
-
Collavre::SystemEvents::Dispatcher.dispatch("comment_created", {
|
|
39
|
-
comment: {
|
|
40
|
-
id: comment.id,
|
|
41
|
-
content: comment.content,
|
|
42
|
-
user_id: nil
|
|
43
|
-
},
|
|
44
|
-
creative: {
|
|
45
|
-
id: creative.id,
|
|
46
|
-
description: creative.description
|
|
47
|
-
},
|
|
48
|
-
topic: {
|
|
49
|
-
id: comment.topic_id
|
|
50
|
-
},
|
|
51
|
-
chat: {
|
|
52
|
-
content: comment.content
|
|
53
|
-
}
|
|
54
|
-
})
|
|
36
|
+
# Dispatch handled by Comment#after_create_commit (skipped for system messages with user: nil)
|
|
55
37
|
end
|
|
56
38
|
|
|
57
39
|
def format_github_event(event, payload)
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
module CollavreGithub
|
|
2
2
|
class Client
|
|
3
|
+
MOCK_SERVER_DEFAULT = "http://localhost:4567"
|
|
4
|
+
|
|
3
5
|
def initialize(account)
|
|
4
|
-
|
|
6
|
+
options = { access_token: account.token }
|
|
7
|
+
api_endpoint = resolve_api_endpoint
|
|
8
|
+
options[:api_endpoint] = api_endpoint if api_endpoint.present?
|
|
9
|
+
@client = Octokit::Client.new(options)
|
|
5
10
|
@client.auto_paginate = true
|
|
6
11
|
end
|
|
7
12
|
|
|
@@ -106,5 +111,15 @@ module CollavreGithub
|
|
|
106
111
|
private
|
|
107
112
|
|
|
108
113
|
attr_reader :client
|
|
114
|
+
|
|
115
|
+
# Use GITHUB_API_ENDPOINT env var if set, otherwise fall back to mock server
|
|
116
|
+
# in development when no real GitHub credentials are configured.
|
|
117
|
+
def resolve_api_endpoint
|
|
118
|
+
return ENV["GITHUB_API_ENDPOINT"] if ENV["GITHUB_API_ENDPOINT"].present?
|
|
119
|
+
|
|
120
|
+
if Rails.env.development? && ENV["GITHUB_CLIENT_ID"].blank?
|
|
121
|
+
MOCK_SERVER_DEFAULT
|
|
122
|
+
end
|
|
123
|
+
end
|
|
109
124
|
end
|
|
110
125
|
end
|
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<meta name="csrf-token" content="<%= form_authenticity_token %>">
|
|
7
|
+
<title><%= t('collavre_github.integration.title') %></title>
|
|
8
|
+
<style>
|
|
9
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
10
|
+
body {
|
|
11
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
12
|
+
background: #f5f5f5;
|
|
13
|
+
color: #333;
|
|
14
|
+
padding: 2em;
|
|
15
|
+
line-height: 1.5;
|
|
16
|
+
}
|
|
17
|
+
.wizard-container {
|
|
18
|
+
max-width: 520px;
|
|
19
|
+
margin: 0 auto;
|
|
20
|
+
background: #fff;
|
|
21
|
+
border-radius: 12px;
|
|
22
|
+
box-shadow: 0 2px 12px rgba(0,0,0,0.1);
|
|
23
|
+
padding: 2em;
|
|
24
|
+
}
|
|
25
|
+
h2 { margin-bottom: 0.5em; font-size: 1.3em; }
|
|
26
|
+
.step { display: none; }
|
|
27
|
+
.step.active { display: block; }
|
|
28
|
+
.step-subtext { color: #666; margin-bottom: 1em; font-size: 0.95em; }
|
|
29
|
+
.list-box {
|
|
30
|
+
max-height: 280px;
|
|
31
|
+
overflow-y: auto;
|
|
32
|
+
border: 1px solid #ddd;
|
|
33
|
+
border-radius: 8px;
|
|
34
|
+
padding: 0.5em;
|
|
35
|
+
margin-bottom: 1em;
|
|
36
|
+
}
|
|
37
|
+
.list-item {
|
|
38
|
+
display: flex;
|
|
39
|
+
align-items: center;
|
|
40
|
+
gap: 0.5em;
|
|
41
|
+
padding: 0.5em 0.4em;
|
|
42
|
+
cursor: pointer;
|
|
43
|
+
border-radius: 4px;
|
|
44
|
+
}
|
|
45
|
+
.list-item:hover { background: #f0f0f0; }
|
|
46
|
+
.list-item input { flex-shrink: 0; }
|
|
47
|
+
.btn {
|
|
48
|
+
display: inline-flex;
|
|
49
|
+
align-items: center;
|
|
50
|
+
justify-content: center;
|
|
51
|
+
padding: 0.6em 1.2em;
|
|
52
|
+
border-radius: 6px;
|
|
53
|
+
border: 1px solid #ccc;
|
|
54
|
+
background: #fff;
|
|
55
|
+
cursor: pointer;
|
|
56
|
+
font-size: 0.95em;
|
|
57
|
+
transition: background 0.15s;
|
|
58
|
+
}
|
|
59
|
+
.btn:hover { background: #f0f0f0; }
|
|
60
|
+
.btn-primary { background: #2563eb; color: #fff; border-color: #2563eb; }
|
|
61
|
+
.btn-primary:hover { background: #1d4ed8; }
|
|
62
|
+
.btn-primary:disabled { background: #93c5fd; cursor: not-allowed; }
|
|
63
|
+
.footer { display: flex; justify-content: space-between; margin-top: 1.5em; }
|
|
64
|
+
.footer-right { display: flex; gap: 0.5em; margin-left: auto; }
|
|
65
|
+
.error-msg { color: #dc2626; font-weight: 600; margin: 0.5em 0; display: none; }
|
|
66
|
+
.success-msg { color: #16a34a; font-weight: 600; margin: 0.5em 0; text-align: center; }
|
|
67
|
+
.loading { text-align: center; padding: 2em; color: #999; }
|
|
68
|
+
.summary-list { padding-left: 1.2em; }
|
|
69
|
+
.summary-list li { margin-bottom: 0.3em; }
|
|
70
|
+
.webhook-detail { margin-top: 0.3em; font-size: 0.85em; }
|
|
71
|
+
.webhook-detail code { background: #f1f5f9; padding: 0.2em 0.4em; border-radius: 3px; word-break: break-all; }
|
|
72
|
+
.close-btn { margin-top: 1em; width: 100%; }
|
|
73
|
+
</style>
|
|
74
|
+
</head>
|
|
75
|
+
<body>
|
|
76
|
+
<div class="wizard-container" id="wizard"
|
|
77
|
+
data-creative-id="<%= @creative_id %>"
|
|
78
|
+
data-success-message="<%= t('collavre_github.integration.saved') %>"
|
|
79
|
+
data-webhook-url-label="<%= t('collavre_github.integration.webhook_url_label') %>"
|
|
80
|
+
data-webhook-secret-label="<%= t('collavre_github.integration.webhook_secret_label') %>"
|
|
81
|
+
data-no-orgs="<%= t('collavre_github.setup.no_orgs') %>"
|
|
82
|
+
data-no-repos="<%= t('collavre_github.setup.no_repos') %>"
|
|
83
|
+
data-load-orgs-error="<%= t('collavre_github.setup.load_orgs_error') %>"
|
|
84
|
+
data-load-repos-error="<%= t('collavre_github.setup.load_repos_error') %>"
|
|
85
|
+
data-save-error="<%= t('collavre_github.setup.save_error') %>">
|
|
86
|
+
<h2><%= t('collavre_github.integration.title') %></h2>
|
|
87
|
+
|
|
88
|
+
<!-- Step 1: Organization -->
|
|
89
|
+
<div class="step active" id="step-organization">
|
|
90
|
+
<p class="step-subtext"><%= t('collavre_github.integration.choose_org') %></p>
|
|
91
|
+
<div id="org-list" class="list-box">
|
|
92
|
+
<div class="loading"><%= t('collavre_github.setup.loading', default: 'Loading...') %></div>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
<!-- Step 2: Repositories -->
|
|
97
|
+
<div class="step" id="step-repositories">
|
|
98
|
+
<p class="step-subtext"><%= t('collavre_github.integration.choose_repo') %></p>
|
|
99
|
+
<div id="repo-list" class="list-box">
|
|
100
|
+
<div class="loading"><%= t('collavre_github.setup.loading', default: 'Loading...') %></div>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
<!-- Step 3: Summary -->
|
|
105
|
+
<div class="step" id="step-summary">
|
|
106
|
+
<p class="step-subtext"><%= t('collavre_github.integration.summary') %></p>
|
|
107
|
+
<p id="webhook-instructions" class="step-subtext" style="display:none;"><%= t('collavre_github.integration.webhook_instructions') %></p>
|
|
108
|
+
<ul id="summary-repos" class="summary-list"></ul>
|
|
109
|
+
<p id="summary-empty" style="display:none;color:#999;"><%= t('collavre_github.integration.summary_empty') %></p>
|
|
110
|
+
</div>
|
|
111
|
+
|
|
112
|
+
<!-- Step 4: Done -->
|
|
113
|
+
<div class="step" id="step-done">
|
|
114
|
+
<p class="success-msg"><%= t('collavre_github.integration.saved') %></p>
|
|
115
|
+
<button type="button" id="close-btn" class="btn btn-primary close-btn"><%= t('collavre_github.setup.close', default: 'Close') %></button>
|
|
116
|
+
</div>
|
|
117
|
+
|
|
118
|
+
<div id="wizard-error" class="error-msg"></div>
|
|
119
|
+
|
|
120
|
+
<div class="footer" id="wizard-footer">
|
|
121
|
+
<button type="button" id="prev-btn" class="btn" style="display:none;"><%= t('app.previous', default: 'Previous') %></button>
|
|
122
|
+
<div class="footer-right">
|
|
123
|
+
<button type="button" id="next-btn" class="btn btn-primary" disabled><%= t('app.next', default: 'Next') %></button>
|
|
124
|
+
<button type="button" id="finish-btn" class="btn btn-primary" style="display:none;"><%= t('app.finish', default: 'Finish') %></button>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
|
|
129
|
+
<script>
|
|
130
|
+
(function() {
|
|
131
|
+
var wizard = document.getElementById('wizard');
|
|
132
|
+
var creativeId = wizard.dataset.creativeId;
|
|
133
|
+
var successMessage = wizard.dataset.successMessage;
|
|
134
|
+
var webhookUrlLabel = wizard.dataset.webhookUrlLabel;
|
|
135
|
+
var webhookSecretLabel = wizard.dataset.webhookSecretLabel;
|
|
136
|
+
var noOrgsMsg = wizard.dataset.noOrgs;
|
|
137
|
+
var noReposMsg = wizard.dataset.noRepos;
|
|
138
|
+
var loadOrgsError = wizard.dataset.loadOrgsError;
|
|
139
|
+
var loadReposError = wizard.dataset.loadReposError;
|
|
140
|
+
var saveErrorMsg = wizard.dataset.saveError;
|
|
141
|
+
|
|
142
|
+
var steps = ['organization', 'repositories', 'summary', 'done'];
|
|
143
|
+
var currentStepIndex = 0;
|
|
144
|
+
var selectedOrg = null;
|
|
145
|
+
var selectedRepos = new Set();
|
|
146
|
+
var webhookDetails = {};
|
|
147
|
+
|
|
148
|
+
var prevBtn = document.getElementById('prev-btn');
|
|
149
|
+
var nextBtn = document.getElementById('next-btn');
|
|
150
|
+
var finishBtn = document.getElementById('finish-btn');
|
|
151
|
+
var errorEl = document.getElementById('wizard-error');
|
|
152
|
+
var footer = document.getElementById('wizard-footer');
|
|
153
|
+
|
|
154
|
+
function csrfToken() {
|
|
155
|
+
return document.querySelector('meta[name="csrf-token"]')?.content;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function showStep(index) {
|
|
159
|
+
currentStepIndex = index;
|
|
160
|
+
steps.forEach(function(s, i) {
|
|
161
|
+
var el = document.getElementById('step-' + s);
|
|
162
|
+
if (el) el.className = 'step' + (i === index ? ' active' : '');
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
if (steps[index] === 'done') {
|
|
166
|
+
footer.style.display = 'none';
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
footer.style.display = 'flex';
|
|
170
|
+
|
|
171
|
+
prevBtn.style.display = index > 0 ? 'inline-flex' : 'none';
|
|
172
|
+
|
|
173
|
+
if (steps[index] === 'summary') {
|
|
174
|
+
nextBtn.style.display = 'none';
|
|
175
|
+
finishBtn.style.display = 'inline-flex';
|
|
176
|
+
updateSummary();
|
|
177
|
+
} else {
|
|
178
|
+
nextBtn.style.display = 'inline-flex';
|
|
179
|
+
finishBtn.style.display = 'none';
|
|
180
|
+
nextBtn.disabled = (steps[index] === 'organization' && !selectedOrg);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function showError(msg) {
|
|
185
|
+
errorEl.textContent = msg;
|
|
186
|
+
errorEl.style.display = 'block';
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function clearError() {
|
|
190
|
+
errorEl.textContent = '';
|
|
191
|
+
errorEl.style.display = 'none';
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function loadOrganizations() {
|
|
195
|
+
var orgList = document.getElementById('org-list');
|
|
196
|
+
orgList.innerHTML = '<div class="loading">Loading...</div>';
|
|
197
|
+
|
|
198
|
+
fetch('/github/account/organizations', { headers: { Accept: 'application/json' } })
|
|
199
|
+
.then(function(r) { return r.json(); })
|
|
200
|
+
.then(function(data) {
|
|
201
|
+
var orgs = data.organizations || [];
|
|
202
|
+
orgList.innerHTML = '';
|
|
203
|
+
if (!orgs.length) {
|
|
204
|
+
orgList.innerHTML = '<p style="padding:0.5em;color:#999;">' + noOrgsMsg + '</p>';
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
orgs.forEach(function(org) {
|
|
208
|
+
var label = document.createElement('label');
|
|
209
|
+
label.className = 'list-item';
|
|
210
|
+
|
|
211
|
+
var input = document.createElement('input');
|
|
212
|
+
input.type = 'radio';
|
|
213
|
+
input.name = 'org';
|
|
214
|
+
input.value = org.login;
|
|
215
|
+
input.addEventListener('change', function() {
|
|
216
|
+
selectedOrg = org.login;
|
|
217
|
+
nextBtn.disabled = false;
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
var span = document.createElement('span');
|
|
221
|
+
span.textContent = org.name || org.login;
|
|
222
|
+
|
|
223
|
+
label.appendChild(input);
|
|
224
|
+
label.appendChild(span);
|
|
225
|
+
orgList.appendChild(label);
|
|
226
|
+
});
|
|
227
|
+
})
|
|
228
|
+
.catch(function() {
|
|
229
|
+
orgList.innerHTML = '';
|
|
230
|
+
showError(loadOrgsError);
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function loadRepositories() {
|
|
235
|
+
var repoList = document.getElementById('repo-list');
|
|
236
|
+
repoList.innerHTML = '<div class="loading">Loading...</div>';
|
|
237
|
+
|
|
238
|
+
var params = new URLSearchParams({ organization: selectedOrg });
|
|
239
|
+
if (creativeId) params.append('creative_id', creativeId);
|
|
240
|
+
|
|
241
|
+
fetch('/github/account/repositories?' + params.toString(), { headers: { Accept: 'application/json' } })
|
|
242
|
+
.then(function(r) { return r.json(); })
|
|
243
|
+
.then(function(data) {
|
|
244
|
+
var repos = data.repositories || [];
|
|
245
|
+
repoList.innerHTML = '';
|
|
246
|
+
if (!repos.length) {
|
|
247
|
+
repoList.innerHTML = '<p style="padding:0.5em;color:#999;">' + noReposMsg + '</p>';
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
repos.forEach(function(repo) {
|
|
251
|
+
var label = document.createElement('label');
|
|
252
|
+
label.className = 'list-item';
|
|
253
|
+
|
|
254
|
+
var input = document.createElement('input');
|
|
255
|
+
input.type = 'checkbox';
|
|
256
|
+
input.value = repo.full_name;
|
|
257
|
+
input.checked = selectedRepos.has(repo.full_name) || repo.selected;
|
|
258
|
+
if (input.checked) selectedRepos.add(repo.full_name);
|
|
259
|
+
input.addEventListener('change', function() {
|
|
260
|
+
if (input.checked) {
|
|
261
|
+
selectedRepos.add(repo.full_name);
|
|
262
|
+
} else {
|
|
263
|
+
selectedRepos.delete(repo.full_name);
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
var span = document.createElement('span');
|
|
268
|
+
span.textContent = repo.full_name;
|
|
269
|
+
|
|
270
|
+
label.appendChild(input);
|
|
271
|
+
label.appendChild(span);
|
|
272
|
+
repoList.appendChild(label);
|
|
273
|
+
});
|
|
274
|
+
})
|
|
275
|
+
.catch(function() {
|
|
276
|
+
repoList.innerHTML = '';
|
|
277
|
+
showError(loadReposError);
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function updateSummary() {
|
|
282
|
+
var summaryList = document.getElementById('summary-repos');
|
|
283
|
+
var summaryEmpty = document.getElementById('summary-empty');
|
|
284
|
+
var instructions = document.getElementById('webhook-instructions');
|
|
285
|
+
summaryList.innerHTML = '';
|
|
286
|
+
|
|
287
|
+
var repos = Array.from(selectedRepos);
|
|
288
|
+
if (!repos.length) {
|
|
289
|
+
summaryEmpty.style.display = 'block';
|
|
290
|
+
if (instructions) instructions.style.display = 'none';
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
summaryEmpty.style.display = 'none';
|
|
294
|
+
if (instructions) instructions.style.display = 'block';
|
|
295
|
+
|
|
296
|
+
repos.forEach(function(fullName) {
|
|
297
|
+
var li = document.createElement('li');
|
|
298
|
+
var strong = document.createElement('strong');
|
|
299
|
+
strong.textContent = fullName;
|
|
300
|
+
li.appendChild(strong);
|
|
301
|
+
|
|
302
|
+
var details = webhookDetails[fullName];
|
|
303
|
+
if (details) {
|
|
304
|
+
if (details.url) {
|
|
305
|
+
var urlDiv = document.createElement('div');
|
|
306
|
+
urlDiv.className = 'webhook-detail';
|
|
307
|
+
urlDiv.innerHTML = '<span style="font-weight:600;">' + webhookUrlLabel + ': </span><code>' + details.url + '</code>';
|
|
308
|
+
li.appendChild(urlDiv);
|
|
309
|
+
}
|
|
310
|
+
if (details.secret) {
|
|
311
|
+
var secretDiv = document.createElement('div');
|
|
312
|
+
secretDiv.className = 'webhook-detail';
|
|
313
|
+
secretDiv.innerHTML = '<span style="font-weight:600;">' + webhookSecretLabel + ': </span><code>' + details.secret + '</code>';
|
|
314
|
+
li.appendChild(secretDiv);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
summaryList.appendChild(li);
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function saveSelection() {
|
|
322
|
+
clearError();
|
|
323
|
+
var payload = { repositories: Array.from(selectedRepos) };
|
|
324
|
+
|
|
325
|
+
fetch('/github/creatives/' + creativeId + '/integration', {
|
|
326
|
+
method: 'PATCH',
|
|
327
|
+
headers: {
|
|
328
|
+
'Content-Type': 'application/json',
|
|
329
|
+
'X-CSRF-Token': csrfToken()
|
|
330
|
+
},
|
|
331
|
+
body: JSON.stringify(payload)
|
|
332
|
+
})
|
|
333
|
+
.then(function(r) { return r.json().then(function(body) { return { ok: r.ok, body: body }; }); })
|
|
334
|
+
.then(function(result) {
|
|
335
|
+
if (!result.ok) {
|
|
336
|
+
showError(result.body.error || saveErrorMsg);
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
webhookDetails = result.body.webhooks || {};
|
|
340
|
+
|
|
341
|
+
// Notify parent window and show done step
|
|
342
|
+
notifyParent();
|
|
343
|
+
showStep(steps.indexOf('done'));
|
|
344
|
+
})
|
|
345
|
+
.catch(function() {
|
|
346
|
+
showError(saveErrorMsg);
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function notifyParent() {
|
|
351
|
+
try {
|
|
352
|
+
if (window.opener) {
|
|
353
|
+
window.opener.postMessage({ type: 'githubConnected' }, window.location.origin);
|
|
354
|
+
}
|
|
355
|
+
} catch (e) {
|
|
356
|
+
// Ignore cross-origin errors
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Navigation
|
|
361
|
+
prevBtn.addEventListener('click', function() {
|
|
362
|
+
clearError();
|
|
363
|
+
if (currentStepIndex > 0) {
|
|
364
|
+
showStep(currentStepIndex - 1);
|
|
365
|
+
if (steps[currentStepIndex] === 'repositories') loadRepositories();
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
nextBtn.addEventListener('click', function() {
|
|
370
|
+
clearError();
|
|
371
|
+
var nextIndex = currentStepIndex + 1;
|
|
372
|
+
showStep(nextIndex);
|
|
373
|
+
if (steps[nextIndex] === 'repositories') loadRepositories();
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
finishBtn.addEventListener('click', function() {
|
|
377
|
+
saveSelection();
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
document.getElementById('close-btn').addEventListener('click', function() {
|
|
381
|
+
window.close();
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
// Initial load
|
|
385
|
+
loadOrganizations();
|
|
386
|
+
})();
|
|
387
|
+
</script>
|
|
388
|
+
</body>
|
|
389
|
+
</html>
|
data/config/locales/en.yml
CHANGED
|
@@ -23,6 +23,14 @@ en:
|
|
|
23
23
|
summary: "The following repositories will be linked to this Creative:"
|
|
24
24
|
webhook_instructions: "Configure each repository with the webhook details below."
|
|
25
25
|
summary_empty: "No repositories selected."
|
|
26
|
+
setup:
|
|
27
|
+
loading: "Loading..."
|
|
28
|
+
close: "Close"
|
|
29
|
+
no_orgs: "No organizations found."
|
|
30
|
+
no_repos: "No repositories found."
|
|
31
|
+
load_orgs_error: "Failed to load organizations."
|
|
32
|
+
load_repos_error: "Failed to load repositories."
|
|
33
|
+
save_error: "Failed to save integration."
|
|
26
34
|
auth:
|
|
27
35
|
login_first: "Please log in first to connect your GitHub account"
|
|
28
36
|
connected: "GitHub account connected successfully"
|
data/config/locales/ko.yml
CHANGED
|
@@ -23,6 +23,14 @@ ko:
|
|
|
23
23
|
summary: "다음 저장소가 이 Creative에 연동됩니다:"
|
|
24
24
|
webhook_instructions: "각 저장소에 아래 웹훅 정보를 설정하세요."
|
|
25
25
|
summary_empty: "선택된 저장소가 없습니다."
|
|
26
|
+
setup:
|
|
27
|
+
loading: "불러오는 중..."
|
|
28
|
+
close: "닫기"
|
|
29
|
+
no_orgs: "조직을 찾을 수 없습니다."
|
|
30
|
+
no_repos: "저장소를 찾을 수 없습니다."
|
|
31
|
+
load_orgs_error: "조직 목록을 불러오지 못했습니다."
|
|
32
|
+
load_repos_error: "저장소 목록을 불러오지 못했습니다."
|
|
33
|
+
save_error: "연동 저장에 실패했습니다."
|
|
26
34
|
auth:
|
|
27
35
|
login_first: "GitHub 계정을 연결하려면 먼저 로그인하세요"
|
|
28
36
|
connected: "GitHub 계정이 연결되었습니다"
|
data/config/routes.rb
CHANGED
|
@@ -4,6 +4,10 @@ CollavreGithub::Engine.routes.draw do
|
|
|
4
4
|
post "webhook", to: "webhooks#create", as: :webhook
|
|
5
5
|
post "webhooks", to: "webhooks#create"
|
|
6
6
|
|
|
7
|
+
# OAuth setup flow (popup wizard after callback)
|
|
8
|
+
get "auth/setup", to: "auth#setup", as: :setup_auth
|
|
9
|
+
post "auth/store_creative", to: "auth#store_creative", as: :store_creative_auth
|
|
10
|
+
|
|
7
11
|
# Account endpoints
|
|
8
12
|
resource :account, only: [ :show ] do
|
|
9
13
|
get :organizations
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: collavre_github
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Collavre
|
|
@@ -75,6 +75,7 @@ files:
|
|
|
75
75
|
- app/services/collavre_github/tools/github_pr_details_service.rb
|
|
76
76
|
- app/services/collavre_github/tools/github_pr_diff_service.rb
|
|
77
77
|
- app/services/collavre_github/webhook_provisioner.rb
|
|
78
|
+
- app/views/collavre_github/auth/setup.html.erb
|
|
78
79
|
- app/views/collavre_github/integrations/_modal.html.erb
|
|
79
80
|
- config/locales/en.yml
|
|
80
81
|
- config/locales/ko.yml
|
|
@@ -82,10 +83,12 @@ files:
|
|
|
82
83
|
- db/migrate/20250925000000_create_github_integrations.rb
|
|
83
84
|
- db/migrate/20250927000000_add_webhook_secret_to_github_repository_links.rb
|
|
84
85
|
- db/migrate/20250928105957_add_github_gemini_prompt_to_creatives.rb
|
|
86
|
+
- db/migrate/20260219095224_remove_github_gemini_prompt_from_creatives.rb
|
|
85
87
|
- db/seeds.rb
|
|
86
88
|
- lib/collavre_github.rb
|
|
87
89
|
- lib/collavre_github/engine.rb
|
|
88
90
|
- lib/collavre_github/version.rb
|
|
91
|
+
- lib/tasks/mock_server.rake
|
|
89
92
|
homepage: https://collavre.com
|
|
90
93
|
licenses:
|
|
91
94
|
- AGPL
|