Skip to content

Commit 7215427

Browse files
committed
Add asset save/loading
1 parent 3c12216 commit 7215427

11 files changed

Lines changed: 216 additions & 17 deletions

File tree

Gemfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ gem 'faker'
1818
gem 'faraday'
1919
gem 'flipper', '~> 1.3'
2020
gem 'flipper-active_record', '~> 1.3'
21+
gem 'flipper-ui', '~> 1.4'
2122
gem 'github_webhook', '~> 1.4'
2223
gem 'globalid'
2324
gem 'good_job', '~> 4.3'
@@ -37,6 +38,7 @@ gem 'paper_trail'
3738
gem 'pg', '~> 1.6'
3839
gem 'postmark-rails'
3940
gem 'puma', '~> 7.2'
41+
gem 'rack_content_type_default', '~> 1.1'
4042
gem 'rack-cors'
4143
gem 'rails', '~> 7.1'
4244
gem 'sentry-rails'
@@ -79,5 +81,3 @@ group :test do
7981
gem 'webdrivers'
8082
gem 'webmock'
8183
end
82-
83-
gem 'flipper-ui', '~> 1.4'

Gemfile.lock

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,8 @@ GEM
377377
rack (>= 3.0.0)
378378
rack-test (2.2.0)
379379
rack (>= 1.3)
380+
rack_content_type_default (1.1.0)
381+
rack
380382
rackup (2.3.1)
381383
rack (>= 3)
382384
rails (7.2.3)
@@ -632,6 +634,7 @@ DEPENDENCIES
632634
pry-byebug
633635
puma (~> 7.2)
634636
rack-cors
637+
rack_content_type_default (~> 1.1)
635638
rails (~> 7.1)
636639
rails-erd
637640
rspec

app/controllers/api/scratch/assets_controller.rb

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,20 @@ class AssetsController < ScratchController
77
skip_before_action :check_scratch_feature, only: [:show]
88

99
def show
10-
render :show, formats: [:svg]
10+
filename_with_extension = "#{params[:id]}.#{params[:format]}"
11+
redirect_to url_for(ScratchAsset.find_by!(filename: filename_with_extension).file)
1112
end
1213

1314
def create
15+
begin
16+
filename_with_extension = "#{params[:id]}.#{params[:format]}"
17+
ScratchAsset.find_or_create_by!(filename: filename_with_extension) do |a|
18+
a.file.attach(io: request.body, filename: filename_with_extension)
19+
end
20+
rescue ActiveRecord::RecordNotUnique => e
21+
logger.error(e)
22+
end
23+
1424
render json: { status: 'ok', 'content-name': params[:id] }, status: :created
1525
end
1626
end

app/models/scratch_asset.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# frozen_string_literal: true
2+
3+
class ScratchAsset < ApplicationRecord
4+
validates :filename, presence: true
5+
6+
has_one_attached :file
7+
end

config/application.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,13 @@ class Application < Rails::Application
5959
config.api_only = false
6060

6161
config.middleware.insert_before 0, CorpMiddleware
62+
63+
require 'rack/content_type_default'
64+
config.middleware.insert_before(
65+
Rack::Runtime,
66+
Rack::ContentTypeDefault, :post, 'application/octet-stream', '/api/scratch/assets/.*'
67+
)
68+
6269
config.generators.system_tests = nil
6370

6471
config.active_record.encryption.primary_key = ENV.fetch('ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY')

config/routes.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
namespace :scratch do
3737
resources :projects, only: %i[show update]
3838
get '/assets/internalapi/asset/:id(.:format)/get/' => 'assets#show'
39-
post '/assets/:id' => 'assets#create'
39+
post '/assets/:id(.:format)' => 'assets#create'
4040
end
4141

4242
resource :default_project, only: %i[show] do
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
class CreateScratchAssets < ActiveRecord::Migration[7.2]
2+
def change
3+
create_table :scratch_assets, id: :uuid do |t|
4+
t.string :filename
5+
6+
t.timestamps
7+
end
8+
end
9+
end

db/schema.rb

Lines changed: 7 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

spec/factories/scratch_assets.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
FactoryBot.define do
2+
factory :scratch_asset do
3+
sequence(:filename) { Random.hex }
4+
5+
trait :with_file do
6+
transient { asset_path { file_fixture('test_image_1.png') } }
7+
8+
after(:build) do |asset, evaluator|
9+
io = Rails.root.join(evaluator.asset_path).open
10+
filename = File.basename(evaluator.asset_path)
11+
content_type = Mime::Type.lookup_by_extension(filename)
12+
asset.file.attach(io:, filename:, content_type:)
13+
end
14+
end
15+
end
16+
end
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
# frozen_string_literal: true
2+
3+
require 'rails_helper'
4+
5+
RSpec.describe 'Creating a Scratch asset', type: :request do
6+
let(:basename) { 'test_image_1' }
7+
let(:format) { 'png' }
8+
let(:filename) { "#{basename}.#{format}" }
9+
let(:school) { create(:school) }
10+
let(:teacher) { create(:teacher, school:) }
11+
let(:cookie_headers) { { 'Cookie' => "scratch_auth=#{UserProfileMock::TOKEN}" } }
12+
13+
describe 'GET #show' do
14+
let(:make_request) { get '/api/scratch/assets/internalapi/asset/test_image_1.png/get/' }
15+
16+
context 'when the asset exists' do
17+
let!(:scratch_asset) { create(:scratch_asset, :with_file, filename:, asset_path: file_fixture(filename)) }
18+
19+
it 'redirects to the asset file URL' do
20+
make_request
21+
22+
expect(response).to redirect_to(rails_blob_url(scratch_asset.file, only_path: true))
23+
end
24+
end
25+
end
26+
27+
describe 'POST #create' do
28+
let(:upload) { File.binread(file_fixture(filename)) }
29+
let(:make_request) do
30+
post '/api/scratch/assets/test_image_1.png', headers: { 'Content-Type' => 'application/octet-stream' }.merge(cookie_headers), params: upload
31+
end
32+
33+
context 'when user is logged in and cat_mode is enabled' do
34+
before do
35+
authenticated_in_hydra_as(teacher)
36+
Flipper.disable :cat_mode
37+
Flipper.disable_actor :cat_mode, school
38+
end
39+
40+
it 'creates a new asset' do
41+
# Arrange
42+
Flipper.enable_actor :cat_mode, school
43+
44+
# Act & Assert
45+
expect { make_request }.to change(ScratchAsset, :count).by(1)
46+
end
47+
48+
it 'sets the filename on the asset' do
49+
# Arrange
50+
Flipper.enable_actor :cat_mode, school
51+
52+
# Act & Assert
53+
make_request
54+
expect(ScratchAsset.last.filename).to eq(filename)
55+
end
56+
57+
it 'attaches the uploaded file to the asset' do
58+
# Arrange
59+
Flipper.enable_actor :cat_mode, school
60+
61+
# Act & Assert
62+
make_request
63+
expect(ScratchAsset.last.file).to be_attached
64+
end
65+
66+
it 'stores the content of the file in the attachment' do
67+
# Arrange
68+
Flipper.enable_actor :cat_mode, school
69+
70+
# Act & Assert
71+
make_request
72+
expect(ScratchAsset.last.file.download).to eq(upload)
73+
end
74+
75+
it 'responds with 201 Created' do
76+
# Arrange
77+
Flipper.enable_actor :cat_mode, school
78+
79+
# Act & Assert
80+
make_request
81+
expect(response).to have_http_status(:created)
82+
end
83+
84+
it 'includes the status and filename (without extension) in the response' do
85+
# Arrange
86+
Flipper.enable_actor :cat_mode, school
87+
88+
# Act & Assert
89+
make_request
90+
expect(response.parsed_body).to include(
91+
'status' => 'ok',
92+
'content-name' => basename
93+
)
94+
end
95+
96+
context 'when the asset already exists' do
97+
let(:another_upload_path) { file_fixture('test_image_2.jpeg') }
98+
let(:upload) { File.binread(another_upload_path) }
99+
let(:original_upload) { File.binread(file_fixture(filename)) }
100+
101+
before do
102+
create(:scratch_asset, :with_file, filename:, asset_path: file_fixture(filename))
103+
end
104+
105+
it 'does not update the content of the file in the attachment' do
106+
# Arrange
107+
Flipper.enable_actor :cat_mode, school
108+
109+
# Act & Assert
110+
make_request
111+
expect(ScratchAsset.last.file.download).to eq(original_upload)
112+
end
113+
114+
it 'responds with 201 Created' do
115+
# Arrange
116+
Flipper.enable_actor :cat_mode, school
117+
118+
# Act & Assert
119+
make_request
120+
expect(response).to have_http_status(:created)
121+
end
122+
123+
it 'includes the status and filename (without extension) in the response' do
124+
# Arrange
125+
Flipper.enable_actor :cat_mode, school
126+
127+
# Act & Assert
128+
make_request
129+
expect(response.parsed_body).to include(
130+
'status' => 'ok',
131+
'content-name' => basename
132+
)
133+
end
134+
end
135+
end
136+
137+
context 'when user is logged in a and cat_mode is disabled' do
138+
before do
139+
authenticated_in_hydra_as(teacher)
140+
Flipper.disable :cat_mode
141+
Flipper.disable_actor :cat_mode, school
142+
end
143+
144+
it 'responds 404 Not Found when cat_mode is not enabled' do
145+
# Act
146+
post '/api/scratch/assets/example.svg', headers: cookie_headers
147+
148+
# Assert
149+
expect(response).to have_http_status(:not_found)
150+
end
151+
end
152+
end
153+
end

0 commit comments

Comments
 (0)