collavre 0.16.0 → 0.20.1
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/assets/stylesheets/collavre/comments_popup.css +65 -5
- data/app/assets/stylesheets/collavre/creatives.css +5 -2
- data/app/controllers/collavre/admin/settings_controller.rb +9 -0
- data/app/controllers/collavre/attachments_controller.rb +1 -1
- data/app/controllers/collavre/comment_read_pointers_controller.rb +2 -2
- data/app/controllers/collavre/comments/snapshots_controller.rb +2 -7
- data/app/controllers/collavre/comments_controller.rb +23 -25
- data/app/controllers/collavre/creatives_controller.rb +22 -11
- data/app/controllers/collavre/emails_controller.rb +2 -0
- data/app/controllers/collavre/google_auth_controller.rb +1 -1
- data/app/controllers/collavre/inbox_items_controller.rb +1 -1
- data/app/controllers/collavre/invites_controller.rb +3 -0
- data/app/controllers/collavre/tasks_controller.rb +1 -1
- data/app/controllers/collavre/topics_controller.rb +27 -85
- data/app/controllers/concerns/collavre/comments/comment_scoping.rb +3 -0
- data/app/controllers/concerns/collavre/creative_permission_guard.rb +32 -0
- data/app/controllers/concerns/collavre/integration_setup.rb +17 -0
- data/app/helpers/collavre/application_helper.rb +30 -2
- data/app/javascript/components/InlineLexicalEditor.jsx +7 -3
- data/app/javascript/components/creative_tree_row.js +21 -1
- data/app/javascript/components/plugins/markdown_shortcuts_plugin.jsx +34 -0
- data/app/javascript/controllers/comment_controller.js +17 -0
- data/app/javascript/controllers/comments/form_controller.js +7 -4
- data/app/javascript/controllers/comments/list_controller.js +43 -4
- data/app/javascript/controllers/comments/popup_controller.js +45 -12
- data/app/javascript/controllers/comments/presence_controller.js +8 -0
- data/app/javascript/controllers/comments/topics_controller.js +50 -31
- data/app/javascript/creatives/tree_renderer.js +1 -0
- data/app/javascript/lib/__tests__/chat_history.test.js +31 -0
- data/app/javascript/lib/chat_history.js +12 -2
- data/app/javascript/modules/command_args_form.js +8 -0
- data/app/javascript/modules/creative_row_editor.js +12 -17
- data/app/javascript/modules/integration_wizard.js +162 -0
- data/app/jobs/collavre/compress_job.rb +1 -0
- data/app/jobs/collavre/creative_broadcast_job.rb +4 -1
- data/app/jobs/collavre/merge_comments_job.rb +1 -0
- data/app/jobs/collavre/trigger_loop_check_job.rb +1 -0
- data/app/jobs/collavre/trigger_loop_verify_job.rb +1 -0
- data/app/models/collavre/calendar_event.rb +0 -4
- data/app/models/collavre/comment/broadcastable.rb +1 -1
- data/app/models/collavre/comment.rb +17 -2
- data/app/models/collavre/comment_snapshot.rb +0 -1
- data/app/models/collavre/creative/describable.rb +10 -1
- data/app/models/collavre/creative/realtime_broadcastable.rb +17 -5
- data/app/models/collavre/creative.rb +43 -1
- data/app/models/collavre/current.rb +1 -1
- data/app/models/collavre/inbox_item.rb +0 -4
- data/app/models/collavre/system_setting.rb +10 -1
- data/app/models/collavre/task.rb +17 -8
- data/app/models/collavre/user.rb +11 -1
- data/app/models/concerns/collavre/ai_agent_resolvable.rb +0 -8
- data/app/services/collavre/ai_agent/message_builder.rb +32 -15
- data/app/services/collavre/ai_agent/response_finalizer.rb +2 -1
- data/app/services/collavre/ai_agent/session_context_resolver.rb +50 -0
- data/app/services/collavre/ai_agent_service.rb +14 -2
- data/app/services/collavre/ai_client.rb +13 -0
- data/app/services/collavre/command_menu_service.rb +23 -3
- data/app/services/collavre/comments/command_processor.rb +1 -1
- data/app/services/collavre/comments/mcp_command.rb +27 -8
- data/app/services/collavre/comments/mcp_command_builder.rb +4 -3
- data/app/services/collavre/creatives/tree_builder.rb +3 -0
- data/app/services/collavre/google_calendar_service.rb +32 -6
- data/app/services/collavre/markdown_converter.rb +15 -20
- data/app/services/collavre/mcp_service.rb +4 -4
- data/app/services/collavre/orchestration/agent_orchestrator.rb +2 -2
- data/app/services/collavre/tools/creative_batch_service.rb +5 -1
- data/app/services/collavre/tools/cron_create_service.rb +9 -6
- data/app/services/collavre/topic_branch_service.rb +2 -2
- data/app/views/collavre/admin/settings/_system_tab.html.erb +11 -0
- data/app/views/collavre/comments/_comment.html.erb +7 -1
- data/app/views/collavre/comments/_comments_popup.html.erb +4 -1
- data/config/locales/admin.en.yml +3 -0
- data/config/locales/admin.ko.yml +3 -0
- data/config/locales/comments.en.yml +6 -1
- data/config/locales/comments.ko.yml +6 -1
- data/db/migrate/20251126040752_add_description_to_creatives.rb +1 -1
- data/db/migrate/20260415000000_create_main_topics_for_existing_creatives.rb +69 -0
- data/db/migrate/20260415094811_add_task_id_to_comments.rb +6 -0
- data/lib/collavre/version.rb +1 -1
- metadata +22 -4
- data/app/jobs/collavre/permission_cache_cleanup_job.rb +0 -36
- data/app/models/collavre/variation.rb +0 -5
- data/app/services/collavre/creatives/path_exporter.rb +0 -131
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 85ccc8b054af6b7b505ec32e773367299bc72d4cc3dd0d13fc345048b358470b
|
|
4
|
+
data.tar.gz: 1ebb968d3a1db4f5fe72bb67afca30154d705a0c0e682cbc2aea1a830e092c4f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a6a9b9f7068b3f9f14eb680ee758e35031fef2e406994b462ce0ca22609bb59a6f96d956d51c0e28041f9ee46205a4b8360145fba434942103b819d50e63e9f2
|
|
7
|
+
data.tar.gz: 6354dc440494f9f7c1753de7909ce7d04ec24dc7d2d9557f0180dfdd09deaa6171af0c1c96e9d5abb21a4164c55ab7e3efa0293e3a4d6a280c54e479b6ed8cdd
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
display: none;
|
|
3
3
|
position: fixed;
|
|
4
4
|
right: 2em;
|
|
5
|
-
|
|
5
|
+
top: 10em;
|
|
6
6
|
z-index: var(--layer-modal);
|
|
7
7
|
width: 420px;
|
|
8
8
|
height: 640px;
|
|
@@ -19,12 +19,20 @@
|
|
|
19
19
|
flex-direction: column;
|
|
20
20
|
transition: top 0.25s ease, left 0.25s ease, width 0.25s ease, height 0.25s ease,
|
|
21
21
|
right 0.25s ease, bottom 0.25s ease, border-radius 0.25s ease,
|
|
22
|
-
box-shadow 0.25s ease, padding 0.25s ease;
|
|
22
|
+
box-shadow 0.25s ease, padding 0.25s ease, opacity 0.3s ease;
|
|
23
23
|
max-width: calc(100vw - 0.5em) !important;
|
|
24
24
|
overscroll-behavior: contain;
|
|
25
25
|
overflow: hidden;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
#comments-popup.editor-behind {
|
|
29
|
+
opacity: 0.15;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
#comments-popup.editor-behind:hover {
|
|
33
|
+
opacity: 1;
|
|
34
|
+
}
|
|
35
|
+
|
|
28
36
|
body.chat-fullscreen {
|
|
29
37
|
overflow: hidden;
|
|
30
38
|
}
|
|
@@ -918,6 +926,32 @@ body.chat-fullscreen {
|
|
|
918
926
|
display: none;
|
|
919
927
|
}
|
|
920
928
|
|
|
929
|
+
.comment-stop-btn {
|
|
930
|
+
background: var(--surface-btn);
|
|
931
|
+
border: 1px solid var(--border-color);
|
|
932
|
+
border-radius: var(--radius-2);
|
|
933
|
+
cursor: pointer;
|
|
934
|
+
padding: var(--space-px-1) var(--space-px-2);
|
|
935
|
+
font-size: var(--text-00);
|
|
936
|
+
color: var(--color-danger);
|
|
937
|
+
line-height: 1;
|
|
938
|
+
white-space: nowrap;
|
|
939
|
+
display: inline-flex;
|
|
940
|
+
align-items: center;
|
|
941
|
+
gap: var(--space-px-1);
|
|
942
|
+
transition: background 0.15s, color 0.15s;
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
.comment-stop-btn:hover {
|
|
946
|
+
background: var(--color-danger);
|
|
947
|
+
color: var(--text-on-btn);
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
.comment-stop-btn:disabled {
|
|
951
|
+
opacity: 0.5;
|
|
952
|
+
cursor: not-allowed;
|
|
953
|
+
}
|
|
954
|
+
|
|
921
955
|
.comment-copy-notice {
|
|
922
956
|
position: absolute;
|
|
923
957
|
right: 0.2em;
|
|
@@ -1069,10 +1103,36 @@ body.chat-fullscreen {
|
|
|
1069
1103
|
color: var(--color-text);
|
|
1070
1104
|
}
|
|
1071
1105
|
|
|
1106
|
+
#typing-indicator-row {
|
|
1107
|
+
display: flex;
|
|
1108
|
+
align-items: center;
|
|
1109
|
+
min-height: var(--space-px-5);
|
|
1110
|
+
margin: 0.3em 0;
|
|
1111
|
+
gap: var(--space-1);
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
.scroll-prev-msg-btn {
|
|
1115
|
+
background: none;
|
|
1116
|
+
border: 1px solid var(--border-color);
|
|
1117
|
+
border-radius: var(--radius-2);
|
|
1118
|
+
color: var(--text-muted);
|
|
1119
|
+
cursor: pointer;
|
|
1120
|
+
font-size: var(--text-1);
|
|
1121
|
+
line-height: 1;
|
|
1122
|
+
padding: var(--space-px-1) var(--space-2);
|
|
1123
|
+
flex-shrink: 0;
|
|
1124
|
+
transition: background 0.15s, color 0.15s;
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
.scroll-prev-msg-btn:hover {
|
|
1128
|
+
background: var(--color-section-bg);
|
|
1129
|
+
color: var(--color-text);
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1072
1132
|
#typing-indicator {
|
|
1073
1133
|
font-style: italic;
|
|
1074
|
-
|
|
1075
|
-
min-height:
|
|
1134
|
+
flex: 1;
|
|
1135
|
+
min-height: var(--space-px-5);
|
|
1076
1136
|
display: flex;
|
|
1077
1137
|
align-items: center;
|
|
1078
1138
|
}
|
|
@@ -1101,7 +1161,7 @@ body.chat-fullscreen {
|
|
|
1101
1161
|
background: var(--surface-btn);
|
|
1102
1162
|
border: 1px solid var(--border-color);
|
|
1103
1163
|
border-radius: var(--radius-2);
|
|
1104
|
-
color: var(--
|
|
1164
|
+
color: var(--color-danger);
|
|
1105
1165
|
cursor: pointer;
|
|
1106
1166
|
font-size: var(--text-00);
|
|
1107
1167
|
padding: var(--space-px-1) var(--space-px-2);
|
|
@@ -364,13 +364,15 @@ creative-tree-row.show-edit .creative-row {
|
|
|
364
364
|
|
|
365
365
|
.creative-row:hover .progress-toggle-wrap .creative-progress-complete,
|
|
366
366
|
.creative-row:hover .progress-toggle-wrap .creative-progress-incomplete {
|
|
367
|
-
|
|
367
|
+
visibility: hidden;
|
|
368
368
|
}
|
|
369
369
|
|
|
370
370
|
.creative-row:hover .progress-toggle-checkbox {
|
|
371
371
|
visibility: visible;
|
|
372
372
|
opacity: 1;
|
|
373
|
-
position:
|
|
373
|
+
position: absolute;
|
|
374
|
+
inset: 0;
|
|
375
|
+
margin: auto;
|
|
374
376
|
pointer-events: auto;
|
|
375
377
|
accent-color: var(--color-brand);
|
|
376
378
|
}
|
|
@@ -1040,3 +1042,4 @@ creative-tree-row.is-being-edited .creative-tree {
|
|
|
1040
1042
|
justify-content: center;
|
|
1041
1043
|
box-shadow: var(--shadow-2);
|
|
1042
1044
|
}
|
|
1045
|
+
|
|
@@ -21,6 +21,9 @@ module Collavre
|
|
|
21
21
|
# Session timeout settings
|
|
22
22
|
@session_timeout_minutes = SystemSetting.session_timeout_minutes
|
|
23
23
|
|
|
24
|
+
# LLM settings
|
|
25
|
+
@llm_request_timeout_seconds = SystemSetting.llm_request_timeout_seconds
|
|
26
|
+
|
|
24
27
|
# Rate limiting settings
|
|
25
28
|
@password_reset_rate_limit = SystemSetting.password_reset_rate_limit
|
|
26
29
|
@password_reset_rate_period_minutes = SystemSetting.password_reset_rate_period_minutes
|
|
@@ -143,6 +146,11 @@ module Collavre
|
|
|
143
146
|
api_period = SystemSetting::DEFAULT_API_RATE_PERIOD_MINUTES if api_period < 1
|
|
144
147
|
SystemSetting.find_or_initialize_by(key: "api_rate_period_minutes").tap { |s| s.value = api_period.to_s; s.save! }
|
|
145
148
|
|
|
149
|
+
# LLM Settings
|
|
150
|
+
llm_timeout = params[:llm_request_timeout_seconds].to_i
|
|
151
|
+
llm_timeout = SystemSetting::DEFAULT_LLM_REQUEST_TIMEOUT_SECONDS if llm_timeout < 30
|
|
152
|
+
SystemSetting.find_or_initialize_by(key: "llm_request_timeout_seconds").tap { |s| s.value = llm_timeout.to_s; s.save! }
|
|
153
|
+
|
|
146
154
|
# Auth Providers
|
|
147
155
|
auth_providers = Array(params[:auth_providers]).reject(&:blank?)
|
|
148
156
|
if auth_providers.empty?
|
|
@@ -171,6 +179,7 @@ module Collavre
|
|
|
171
179
|
@password_reset_rate_period_minutes = params[:password_reset_rate_period_minutes].to_i.positive? ? params[:password_reset_rate_period_minutes].to_i : SystemSetting::DEFAULT_PASSWORD_RESET_RATE_PERIOD_MINUTES
|
|
172
180
|
@api_rate_limit = params[:api_rate_limit].to_i.positive? ? params[:api_rate_limit].to_i : SystemSetting::DEFAULT_API_RATE_LIMIT
|
|
173
181
|
@api_rate_period_minutes = params[:api_rate_period_minutes].to_i.positive? ? params[:api_rate_period_minutes].to_i : SystemSetting::DEFAULT_API_RATE_PERIOD_MINUTES
|
|
182
|
+
@llm_request_timeout_seconds = params[:llm_request_timeout_seconds].to_i.positive? ? params[:llm_request_timeout_seconds].to_i : SystemSetting::DEFAULT_LLM_REQUEST_TIMEOUT_SECONDS
|
|
174
183
|
@enabled_auth_providers = params[:auth_providers] || []
|
|
175
184
|
render :index, status: :unprocessable_entity
|
|
176
185
|
end
|
|
@@ -44,11 +44,11 @@ module Collavre
|
|
|
44
44
|
end
|
|
45
45
|
|
|
46
46
|
def find_nearest_public_comment_id(creative, comment_id)
|
|
47
|
-
creative.comments.
|
|
47
|
+
creative.comments.public_only.where("id <= ?", comment_id).maximum(:id)
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
def fetch_users_on_effective_id(creative, effective_id)
|
|
51
|
-
next_public_id = creative.comments.
|
|
51
|
+
next_public_id = creative.comments.public_only.where("id > ?", effective_id).minimum(:id)
|
|
52
52
|
|
|
53
53
|
query = CommentReadPointer.where(creative: creative)
|
|
54
54
|
.where("last_read_comment_id >= ?", effective_id)
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
module Collavre
|
|
4
4
|
module Comments
|
|
5
5
|
class SnapshotsController < ApplicationController
|
|
6
|
+
include Collavre::Comments::CommentScoping
|
|
7
|
+
|
|
6
8
|
before_action :set_creative
|
|
7
9
|
before_action :set_snapshot, only: [ :restore ]
|
|
8
10
|
|
|
@@ -29,13 +31,6 @@ module Collavre
|
|
|
29
31
|
|
|
30
32
|
private
|
|
31
33
|
|
|
32
|
-
def set_creative
|
|
33
|
-
@creative = Creative.find(params[:creative_id]).effective_origin
|
|
34
|
-
unless @creative.has_permission?(Current.user, :read)
|
|
35
|
-
render json: { error: I18n.t("collavre.creatives.errors.no_permission") }, status: :forbidden
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
|
|
39
34
|
def set_snapshot
|
|
40
35
|
@snapshot = @creative.comment_snapshots.find(params[:id])
|
|
41
36
|
end
|
|
@@ -28,7 +28,7 @@ module Collavre
|
|
|
28
28
|
limit = 20
|
|
29
29
|
|
|
30
30
|
visible_scope = @creative.comments.visible_to(Current.user)
|
|
31
|
-
scope = visible_scope.with_attached_images.includes(:topic, :comment_reactions, :comment_versions, :snapshot_as_result)
|
|
31
|
+
scope = visible_scope.with_attached_images.includes(:task, :topic, :comment_reactions, :comment_versions, :snapshot_as_result)
|
|
32
32
|
|
|
33
33
|
if params[:search].present?
|
|
34
34
|
words = params[:search].to_s.strip.downcase.split(/\s+/)
|
|
@@ -128,7 +128,7 @@ module Collavre
|
|
|
128
128
|
# Fetch all visible IDs for correct read-receipt placement transparency
|
|
129
129
|
# Only map read receipts to PUBLIC comments.
|
|
130
130
|
# Users who read private comments will appear on the nearest preceding public comment.
|
|
131
|
-
public_ids = @creative.comments.
|
|
131
|
+
public_ids = @creative.comments.public_only.order(id: :asc).pluck(:id)
|
|
132
132
|
|
|
133
133
|
pointers.each do |pointer|
|
|
134
134
|
effective_id = pointer.effective_comment_id(public_ids)
|
|
@@ -169,9 +169,7 @@ module Collavre
|
|
|
169
169
|
|
|
170
170
|
@comment = @creative.comments.build(comment_attributes)
|
|
171
171
|
|
|
172
|
-
|
|
173
|
-
render json: { error: I18n.t("collavre.comments.invalid_topic") }, status: :unprocessable_entity and return
|
|
174
|
-
end
|
|
172
|
+
validate_topic_id!(@comment.topic_id) or return
|
|
175
173
|
|
|
176
174
|
@comment.user = Current.user
|
|
177
175
|
@comment.images.attach(image_attachments) if image_attachments.present?
|
|
@@ -193,11 +191,13 @@ module Collavre
|
|
|
193
191
|
end
|
|
194
192
|
|
|
195
193
|
def update
|
|
194
|
+
if github_synced_content_comment?(@comment)
|
|
195
|
+
render json: { error: I18n.t("collavre.comments.github_synced_readonly") }, status: :forbidden and return
|
|
196
|
+
end
|
|
197
|
+
|
|
196
198
|
if @comment.user == Current.user
|
|
197
199
|
safe_params = comment_params.except(:quoted_comment_id, :quoted_text)
|
|
198
|
-
|
|
199
|
-
render json: { error: I18n.t("collavre.comments.invalid_topic") }, status: :unprocessable_entity and return
|
|
200
|
-
end
|
|
200
|
+
validate_topic_id!(safe_params[:topic_id]) or return
|
|
201
201
|
|
|
202
202
|
if @comment.update(safe_params)
|
|
203
203
|
@comment = Comment.with_attached_images.includes(:comment_reactions, :comment_versions, :selected_version).find(@comment.id)
|
|
@@ -211,6 +211,10 @@ module Collavre
|
|
|
211
211
|
end
|
|
212
212
|
|
|
213
213
|
def destroy
|
|
214
|
+
if github_synced_content_comment?(@comment)
|
|
215
|
+
render json: { error: I18n.t("collavre.comments.github_synced_readonly") }, status: :forbidden and return
|
|
216
|
+
end
|
|
217
|
+
|
|
214
218
|
# @comment is set by before_action
|
|
215
219
|
is_owner = @comment.user == Current.user
|
|
216
220
|
is_admin = @creative.has_permission?(Current.user, :admin)
|
|
@@ -258,17 +262,7 @@ module Collavre
|
|
|
258
262
|
def participants
|
|
259
263
|
users = [ @creative.user ].compact + @creative.all_shared_users(:feedback).map(&:user)
|
|
260
264
|
users = users.uniq
|
|
261
|
-
user_data = users.map
|
|
262
|
-
{
|
|
263
|
-
id: u.id,
|
|
264
|
-
email: u.email,
|
|
265
|
-
name: u.display_name,
|
|
266
|
-
avatar_url: view_context.user_avatar_url(u, size: 20),
|
|
267
|
-
default_avatar: !u.avatar.attached? && u.avatar_url.blank?,
|
|
268
|
-
initial: u.display_name[0].upcase,
|
|
269
|
-
ai_user: u.ai_user?
|
|
270
|
-
}
|
|
271
|
-
end
|
|
265
|
+
user_data = users.map { |u| view_context.user_json(u, email: true, ai_user: true) }
|
|
272
266
|
response.headers["Cache-Control"] = "no-store"
|
|
273
267
|
response.headers["Pragma"] = "no-cache"
|
|
274
268
|
response.headers["Expires"] = "0"
|
|
@@ -286,7 +280,7 @@ module Collavre
|
|
|
286
280
|
head :forbidden and return
|
|
287
281
|
end
|
|
288
282
|
|
|
289
|
-
render json: CommandMenuService.new(user: Current.user).items
|
|
283
|
+
render json: CommandMenuService.new(user: Current.user, creative: @creative).items
|
|
290
284
|
end
|
|
291
285
|
|
|
292
286
|
def download_images
|
|
@@ -345,11 +339,9 @@ module Collavre
|
|
|
345
339
|
|
|
346
340
|
private
|
|
347
341
|
|
|
348
|
-
def
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
render json: { error: I18n.t("collavre.creatives.errors.no_permission") }, status: :forbidden
|
|
352
|
-
end
|
|
342
|
+
def github_synced_content_comment?(comment)
|
|
343
|
+
return false unless comment.topic&.name == Collavre::Creative::CONTENT_TOPIC_NAME
|
|
344
|
+
comment.creative&.github_markdown?
|
|
353
345
|
end
|
|
354
346
|
|
|
355
347
|
def comment_params
|
|
@@ -359,5 +351,11 @@ module Collavre
|
|
|
359
351
|
def current_topic_context
|
|
360
352
|
params[:topic_id].presence || params.dig(:comment, :topic_id).presence
|
|
361
353
|
end
|
|
354
|
+
|
|
355
|
+
def validate_topic_id!(topic_id)
|
|
356
|
+
return true if topic_id.blank? || @creative.topics.where(id: topic_id).exists?
|
|
357
|
+
render json: { error: I18n.t("collavre.comments.invalid_topic") }, status: :unprocessable_entity
|
|
358
|
+
false
|
|
359
|
+
end
|
|
362
360
|
end
|
|
363
361
|
end
|
|
@@ -4,12 +4,14 @@ module Collavre
|
|
|
4
4
|
include Collavre::Concerns::Exportable
|
|
5
5
|
include Collavre::Concerns::TreeManageable
|
|
6
6
|
include Collavre::Concerns::Shareable
|
|
7
|
+
include Collavre::CreativePermissionGuard
|
|
7
8
|
|
|
8
9
|
# TODO: for not for security reasons for this Collavre app, we don't expose to public, later it should be controlled by roles for each Creatives
|
|
9
10
|
# Removed unauthenticated access to index and show actions
|
|
10
11
|
allow_unauthenticated_access only: %i[ index children export_markdown show slide_view ]
|
|
11
12
|
before_action :enforce_creatives_login_policy, only: %i[ index children export_markdown show slide_view ]
|
|
12
13
|
before_action :set_creative, only: %i[ show edit update destroy parent_suggestions slide_view request_permission unconvert contexts update_contexts update_metadata archive unarchive trigger_action ]
|
|
14
|
+
before_action :require_creative_write!, only: %i[archive unarchive]
|
|
13
15
|
|
|
14
16
|
def index
|
|
15
17
|
respond_to do |format|
|
|
@@ -195,6 +197,10 @@ module Collavre
|
|
|
195
197
|
end
|
|
196
198
|
|
|
197
199
|
def parent_suggestions
|
|
200
|
+
unless @creative.has_permission?(Current.user, :read)
|
|
201
|
+
render json: { error: t("collavre.creatives.errors.no_permission") }, status: :forbidden and return
|
|
202
|
+
end
|
|
203
|
+
|
|
198
204
|
suggestions = ::GeminiParentRecommender.new.recommend(@creative)
|
|
199
205
|
render json: suggestions
|
|
200
206
|
end
|
|
@@ -276,13 +282,17 @@ module Collavre
|
|
|
276
282
|
end
|
|
277
283
|
|
|
278
284
|
def contexts
|
|
285
|
+
unless @creative.has_permission?(Current.user, :read)
|
|
286
|
+
render json: { error: t("collavre.creatives.errors.no_permission") }, status: :forbidden and return
|
|
287
|
+
end
|
|
288
|
+
|
|
279
289
|
creative = @creative.effective_origin(Set.new)
|
|
280
290
|
own_ids = creative.context_ids - [ creative.id ]
|
|
281
291
|
inherited_ids = (creative.effective_context_ids - own_ids - [ creative.id ]).uniq
|
|
282
292
|
own_creatives = Creative.where(id: own_ids).index_by(&:id)
|
|
283
293
|
inherited_creatives = Creative.where(id: inherited_ids).index_by(&:id)
|
|
284
294
|
|
|
285
|
-
disabled_ids =
|
|
295
|
+
disabled_ids = creative.effective_disabled_context_ids
|
|
286
296
|
|
|
287
297
|
own = own_ids.filter_map do |cid|
|
|
288
298
|
c = own_creatives[cid]
|
|
@@ -314,8 +324,13 @@ module Collavre
|
|
|
314
324
|
|
|
315
325
|
current_data = (creative.data || {}).dup
|
|
316
326
|
current_data["context_ids"] = Array(params[:context_ids]).map(&:to_i) if params.key?(:context_ids)
|
|
317
|
-
|
|
318
|
-
|
|
327
|
+
if params.key?(:disabled_context_ids)
|
|
328
|
+
requested_disabled = Array(params[:disabled_context_ids]).map(&:to_i)
|
|
329
|
+
parent_disabled = creative.parent&.effective_disabled_context_ids || []
|
|
330
|
+
inherited_only = parent_disabled - creative.disabled_context_ids
|
|
331
|
+
current_data["disabled_context_ids"] = requested_disabled - inherited_only
|
|
332
|
+
current_data.delete("disabled_context_ids") if current_data["disabled_context_ids"].empty?
|
|
333
|
+
end
|
|
319
334
|
if params.key?(:disabled_self_context)
|
|
320
335
|
if ActiveModel::Type::Boolean.new.cast(params[:disabled_self_context])
|
|
321
336
|
current_data["disabled_self_context"] = true
|
|
@@ -440,19 +455,11 @@ module Collavre
|
|
|
440
455
|
end
|
|
441
456
|
|
|
442
457
|
def archive
|
|
443
|
-
unless @creative.has_permission?(Current.user, :write) || @creative.user == Current.user
|
|
444
|
-
render json: { error: t("collavre.creatives.errors.no_permission") }, status: :forbidden and return
|
|
445
|
-
end
|
|
446
|
-
|
|
447
458
|
@creative.archive!
|
|
448
459
|
head :ok
|
|
449
460
|
end
|
|
450
461
|
|
|
451
462
|
def unarchive
|
|
452
|
-
unless @creative.has_permission?(Current.user, :write) || @creative.user == Current.user
|
|
453
|
-
render json: { error: t("collavre.creatives.errors.no_permission") }, status: :forbidden and return
|
|
454
|
-
end
|
|
455
|
-
|
|
456
463
|
@creative.unarchive!
|
|
457
464
|
head :ok
|
|
458
465
|
end
|
|
@@ -487,6 +494,10 @@ module Collavre
|
|
|
487
494
|
@creative = Creative.find(params[:id])
|
|
488
495
|
end
|
|
489
496
|
|
|
497
|
+
def creative_permission_denied_message
|
|
498
|
+
t("collavre.creatives.errors.no_permission")
|
|
499
|
+
end
|
|
500
|
+
|
|
490
501
|
def creative_params
|
|
491
502
|
params.require(:creative).permit(:description, :progress, :parent_id, :sequence, :origin_id)
|
|
492
503
|
end
|
|
@@ -33,7 +33,7 @@ module Collavre
|
|
|
33
33
|
# Ensure app calendar exists if the granted scope allows creating an app calendar
|
|
34
34
|
begin
|
|
35
35
|
::GoogleCalendarService.new(user: user).ensure_app_calendar!
|
|
36
|
-
rescue => e
|
|
36
|
+
rescue StandardError => e
|
|
37
37
|
Rails.logger.error("Post-login calendar setup failed: #{e.message}")
|
|
38
38
|
end
|
|
39
39
|
|
|
@@ -5,6 +5,9 @@ module Collavre
|
|
|
5
5
|
|
|
6
6
|
def create
|
|
7
7
|
creative = Creative.find(params[:creative_id]).effective_origin
|
|
8
|
+
unless creative.has_permission?(Current.user, :admin)
|
|
9
|
+
return head :forbidden
|
|
10
|
+
end
|
|
8
11
|
permission = params[:permission] || :read
|
|
9
12
|
invitation = Invitation.create!(inviter: Current.user,
|
|
10
13
|
creative: creative,
|