Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ gem 'faker'
gem 'faraday'
gem 'flipper', '~> 1.3'
gem 'flipper-active_record', '~> 1.3'
gem 'flipper-ui', '~> 1.4'
gem 'github_webhook', '~> 1.4'
gem 'globalid'
gem 'good_job', '~> 4.3'
Expand All @@ -37,6 +38,7 @@ gem 'paper_trail'
gem 'pg', '~> 1.6'
gem 'postmark-rails'
gem 'puma', '~> 7.2'
gem 'rack_content_type_default', '~> 1.1'
gem 'rack-cors'
gem 'rails', '~> 7.1'
gem 'sentry-rails'
Expand Down Expand Up @@ -79,5 +81,3 @@ group :test do
gem 'webdrivers'
gem 'webmock'
end

gem 'flipper-ui', '~> 1.4'
3 changes: 3 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,8 @@ GEM
rack (>= 3.0.0)
rack-test (2.2.0)
rack (>= 1.3)
rack_content_type_default (1.1.0)
rack
rackup (2.3.1)
rack (>= 3)
rails (7.2.3)
Expand Down Expand Up @@ -632,6 +634,7 @@ DEPENDENCIES
pry-byebug
puma (~> 7.2)
rack-cors
rack_content_type_default (~> 1.1)
rails (~> 7.1)
rails-erd
rspec
Expand Down
12 changes: 11 additions & 1 deletion app/controllers/api/scratch/assets_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,20 @@ class AssetsController < ScratchController
skip_before_action :check_scratch_feature, only: [:show]

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

def create
begin
filename_with_extension = "#{params[:id]}.#{params[:format]}"
ScratchAsset.find_or_create_by!(filename: filename_with_extension) do |a|
a.file.attach(io: request.body, filename: filename_with_extension)
end
rescue ActiveRecord::RecordNotUnique => e
logger.error(e)
Comment on lines +15 to +21
end

render json: { status: 'ok', 'content-name': params[:id] }, status: :created
end
end
Expand Down
7 changes: 7 additions & 0 deletions app/models/scratch_asset.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

class ScratchAsset < ApplicationRecord
validates :filename, presence: true

has_one_attached :file
end
7 changes: 7 additions & 0 deletions config/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,13 @@ class Application < Rails::Application
config.api_only = false

config.middleware.insert_before 0, CorpMiddleware

require 'rack/content_type_default'
config.middleware.insert_before(
Rack::Runtime,
Rack::ContentTypeDefault, :post, 'application/octet-stream', '/api/scratch/assets/.*'
)

config.generators.system_tests = nil

config.active_record.encryption.primary_key = ENV.fetch('ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY')
Expand Down
2 changes: 1 addition & 1 deletion config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
namespace :scratch do
resources :projects, only: %i[show update]
get '/assets/internalapi/asset/:id(.:format)/get/' => 'assets#show'
post '/assets/:id' => 'assets#create'
post '/assets/:id(.:format)' => 'assets#create'
end
Comment on lines 38 to 40

resource :default_project, only: %i[show] do
Expand Down
10 changes: 10 additions & 0 deletions db/migrate/20260310161646_create_scratch_assets.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
class CreateScratchAssets < ActiveRecord::Migration[7.2]
def change
create_table :scratch_assets, id: :uuid do |t|
t.string :filename, null: false
t.index :filename, unique: true

t.timestamps
end
end
end
8 changes: 7 additions & 1 deletion db/schema.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 18 additions & 0 deletions spec/factories/scratch_assets.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# frozen_string_literal: true

FactoryBot.define do
factory :scratch_asset do
sequence(:filename) { Random.hex }

trait :with_file do
transient { asset_path { file_fixture('test_image_1.png') } }

after(:build) do |asset, evaluator|
io = Rails.root.join(evaluator.asset_path).open
filename = File.basename(evaluator.asset_path)
content_type = Mime::Type.lookup_by_extension(filename)
asset.file.attach(io:, filename:, content_type:)
end
end
end
end
153 changes: 153 additions & 0 deletions spec/features/scratch/creating_and_showing_a_scratch_asset_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe 'Creating a Scratch asset', type: :request do
let(:basename) { 'test_image_1' }
let(:format) { 'png' }
let(:filename) { "#{basename}.#{format}" }
let(:school) { create(:school) }
let(:teacher) { create(:teacher, school:) }
let(:cookie_headers) { { 'Cookie' => "scratch_auth=#{UserProfileMock::TOKEN}" } }

describe 'GET #show' do
let(:make_request) { get '/api/scratch/assets/internalapi/asset/test_image_1.png/get/' }

context 'when the asset exists' do
let!(:scratch_asset) { create(:scratch_asset, :with_file, filename:, asset_path: file_fixture(filename)) }

it 'redirects to the asset file URL' do
make_request

expect(response).to redirect_to(rails_blob_url(scratch_asset.file, only_path: true))
end
end
end

describe 'POST #create' do
let(:upload) { File.binread(file_fixture(filename)) }
let(:make_request) do
post '/api/scratch/assets/test_image_1.png', headers: { 'Content-Type' => 'application/octet-stream' }.merge(cookie_headers), params: upload
end

context 'when user is logged in and cat_mode is enabled' do
before do
authenticated_in_hydra_as(teacher)
Flipper.disable :cat_mode
Flipper.disable_actor :cat_mode, school
end

it 'creates a new asset' do
# Arrange
Flipper.enable_actor :cat_mode, school

# Act & Assert
expect { make_request }.to change(ScratchAsset, :count).by(1)
end

it 'sets the filename on the asset' do
# Arrange
Flipper.enable_actor :cat_mode, school

# Act & Assert
make_request
expect(ScratchAsset.last.filename).to eq(filename)
end

it 'attaches the uploaded file to the asset' do
# Arrange
Flipper.enable_actor :cat_mode, school

# Act & Assert
make_request
expect(ScratchAsset.last.file).to be_attached
end

it 'stores the content of the file in the attachment' do
# Arrange
Flipper.enable_actor :cat_mode, school

# Act & Assert
make_request
expect(ScratchAsset.last.file.download).to eq(upload)
end

it 'responds with 201 Created' do
# Arrange
Flipper.enable_actor :cat_mode, school

# Act & Assert
make_request
expect(response).to have_http_status(:created)
end

it 'includes the status and filename (without extension) in the response' do
# Arrange
Flipper.enable_actor :cat_mode, school

# Act & Assert
make_request
expect(response.parsed_body).to include(
'status' => 'ok',
'content-name' => basename
)
end

context 'when the asset already exists' do
let(:another_upload_path) { file_fixture('test_image_2.jpeg') }
let(:upload) { File.binread(another_upload_path) }
let(:original_upload) { File.binread(file_fixture(filename)) }

before do
create(:scratch_asset, :with_file, filename:, asset_path: file_fixture(filename))
end

it 'does not update the content of the file in the attachment' do
# Arrange
Flipper.enable_actor :cat_mode, school

# Act & Assert
make_request
expect(ScratchAsset.last.file.download).to eq(original_upload)
end

it 'responds with 201 Created' do
# Arrange
Flipper.enable_actor :cat_mode, school

# Act & Assert
make_request
expect(response).to have_http_status(:created)
end

it 'includes the status and filename (without extension) in the response' do
# Arrange
Flipper.enable_actor :cat_mode, school

# Act & Assert
make_request
expect(response.parsed_body).to include(
'status' => 'ok',
'content-name' => basename
)
end
end
end

context 'when user is logged in and cat_mode is disabled' do
before do
authenticated_in_hydra_as(teacher)
Flipper.disable :cat_mode
Flipper.disable_actor :cat_mode, school
end

it 'responds 404 Not Found when cat_mode is not enabled' do
# Act
post '/api/scratch/assets/example.svg', headers: cookie_headers

# Assert
expect(response).to have_http_status(:not_found)
end
end
end
end
12 changes: 0 additions & 12 deletions spec/features/scratch/showing_a_scratch_asset_spec.rb

This file was deleted.

Loading