Skip to content

Commit ef4870e

Browse files
committed
Use a student's remix for Scratch asset uploads
1 parent 10a403a commit ef4870e

File tree

7 files changed

+168
-42
lines changed

7 files changed

+168
-42
lines changed

app/controllers/api/scratch/assets_controller.rb

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ def show
1414
authorize! :show, @project_from_header
1515

1616
scratch_asset = ScratchAsset.find_visible_to_project(
17-
project: @project_from_header,
17+
project: readable_scratch_asset_project,
1818
filename: filename_with_extension
1919
)
2020
raise ActiveRecord::RecordNotFound, 'Not Found' unless scratch_asset
@@ -23,11 +23,12 @@ def show
2323
end
2424

2525
def create
26-
authorize! :update, @project_from_header
26+
project = writable_scratch_asset_project
27+
return if performed?
2728

2829
filename_with_extension = "#{params[:id]}.#{params[:format]}"
2930
scratch_asset = ScratchAsset.find_or_initialize_by(
30-
project: @project_from_header,
31+
project:,
3132
filename: filename_with_extension
3233
)
3334

@@ -37,7 +38,7 @@ def create
3738
scratch_asset.save!
3839
rescue ActiveRecord::RecordNotUnique
3940
logger.info("Scratch asset already created during concurrent upload: #{filename_with_extension}")
40-
ScratchAsset.find_by!(project: @project_from_header, filename: filename_with_extension)
41+
ScratchAsset.find_by!(project:, filename: filename_with_extension)
4142
end
4243
end
4344

app/controllers/api/scratch/projects_controller.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,17 @@ def create
2121
remix_params = create_params
2222
return render json: { error: I18n.t('errors.project.remixing.invalid_params') }, status: :bad_request if remix_params.dig(:scratch_component, :content).blank?
2323

24+
existing_remix = remix_for_user(original_project, current_user)
25+
if existing_remix
26+
authorize! :update, existing_remix
27+
28+
scratch_component = existing_remix.scratch_component || existing_remix.build_scratch_component
29+
scratch_component.content = scratch_content_params
30+
existing_remix.save!
31+
32+
return render json: { status: 'ok', 'content-name': existing_remix.identifier }, status: :ok
33+
end
34+
2435
remix_origin = request.origin || request.referer
2536

2637
result = Project::CreateRemix.call(

app/controllers/api/scratch/scratch_controller.rb

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
module Api
44
module Scratch
55
class ScratchController < ApiController
6+
include RemixSelection
7+
68
before_action :authorize_user
79
before_action :check_scratch_feature
810

@@ -31,6 +33,52 @@ def load_project_from_header
3133
project_type: Project::Types::CODE_EDITOR_SCRATCH
3234
)
3335
end
36+
37+
def readable_scratch_asset_project
38+
return @project_from_header if can?(:update, @project_from_header)
39+
40+
remix_for_user(@project_from_header, current_user) || @project_from_header
41+
end
42+
43+
def writable_scratch_asset_project
44+
return @project_from_header if can?(:update, @project_from_header)
45+
46+
authorize! :show, @project_from_header
47+
48+
remix = remix_for_user(@project_from_header, current_user)
49+
if remix
50+
authorize! :update, remix
51+
return remix
52+
end
53+
54+
create_scratch_remix_from(@project_from_header)
55+
end
56+
57+
def create_scratch_remix_from(project)
58+
result = Project::CreateRemix.call(
59+
params: {
60+
identifier: project.identifier,
61+
scratch_component: { content: scratch_component_content_for(project) }
62+
},
63+
user_id: current_user.id,
64+
original_project: project,
65+
remix_origin: request.origin || request.referer || request.base_url
66+
)
67+
68+
return result[:project] if result.success?
69+
70+
render json: { error: result[:error] }, status: :bad_request
71+
nil
72+
end
73+
74+
def scratch_component_content_for(project)
75+
project.scratch_component&.content&.deep_dup || {
76+
meta: {},
77+
targets: [],
78+
monitors: [],
79+
extensions: []
80+
}
81+
end
3482
end
3583
end
3684
end

spec/features/scratch/creating_a_scratch_asset_spec.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@
1414
let(:project_headers) { auth_headers.merge('X-Project-ID' => project.identifier) }
1515

1616
before do
17-
Flipper.disable :cat_mode
18-
Flipper.disable_actor :cat_mode, school
17+
Flipper.enable_actor :cat_mode, school
1918
end
2019

2120
it 'responds 401 Unauthorized when no Authorization header is provided' do
@@ -26,6 +25,8 @@
2625

2726
it 'responds 404 Not Found when cat_mode is not enabled' do
2827
authenticated_in_hydra_as(teacher)
28+
Flipper.disable :cat_mode
29+
Flipper.disable_actor :cat_mode, school
2930

3031
post '/api/scratch/assets/example.svg', headers: project_headers
3132

@@ -34,7 +35,6 @@
3435

3536
it 'creates an asset when cat_mode is enabled and the required headers are provided' do
3637
authenticated_in_hydra_as(teacher)
37-
Flipper.enable_actor :cat_mode, school
3838

3939
post '/api/scratch/assets/example.svg', headers: project_headers
4040

spec/features/scratch/creating_a_scratch_project_spec.rb

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,7 @@
3636
mock_phrase_generation('new-project-id')
3737
create(:scratch_component, project: original_project)
3838

39-
Flipper.disable :cat_mode
40-
Flipper.disable_actor :cat_mode, school
39+
Flipper.enable_actor :cat_mode, school
4140
end
4241

4342
def make_request(query: request_query, request_headers: headers, request_params: scratch_project)
@@ -57,6 +56,8 @@ def make_request(query: request_query, request_headers: headers, request_params:
5756

5857
it 'responds 404 Not Found when cat_mode is not enabled' do
5958
authenticated_in_hydra_as(teacher)
59+
Flipper.disable :cat_mode
60+
Flipper.disable_actor :cat_mode, school
6061

6162
make_request
6263

@@ -66,7 +67,6 @@ def make_request(query: request_query, request_headers: headers, request_params:
6667
context 'when authenticated and cat_mode is enabled' do
6768
before do
6869
authenticated_in_hydra_as(teacher)
69-
Flipper.enable_actor :cat_mode, school
7070
end
7171

7272
it 'responds 403 Forbidden when not remixing' do
@@ -125,5 +125,31 @@ def make_request(query: request_query, request_headers: headers, request_params:
125125
expect(remixed_project.lesson_id).to be_nil
126126
expect(remixed_project.scratch_component.content.to_h).to eq(scratch_project.deep_stringify_keys)
127127
end
128+
129+
it 'updates the existing remix instead of creating a duplicate' do
130+
student = create(:student, school:)
131+
school_class = create(:school_class, school:, teacher_ids: [teacher.id])
132+
create(:class_student, school_class:, student_id: student.id)
133+
original_project.update!(lesson: create(:lesson, school:, school_class:, user_id: teacher.id, visibility: 'students'))
134+
existing_remix = create(
135+
:project,
136+
school:,
137+
user_id: student.id,
138+
remixed_from_id: original_project.id,
139+
project_type: Project::Types::CODE_EDITOR_SCRATCH,
140+
locale: nil
141+
)
142+
create(:scratch_component, project: existing_remix, content: { targets: ['old target'], monitors: [], extensions: [], meta: {} })
143+
authenticated_in_hydra_as(student)
144+
145+
expect { make_request }.not_to change(Project, :count)
146+
147+
expect(response).to have_http_status(:ok)
148+
expect(response.parsed_body).to eq(
149+
'status' => 'ok',
150+
'content-name' => existing_remix.identifier
151+
)
152+
expect(existing_remix.reload.scratch_component.content.to_h).to eq(scratch_project.deep_stringify_keys)
153+
end
128154
end
129155
end

0 commit comments

Comments
 (0)