diff --git a/app/controllers/concerns/warp_engine/api_error_rendering.rb b/app/controllers/concerns/warp_engine/api_error_rendering.rb new file mode 100644 index 0000000..0d61591 --- /dev/null +++ b/app/controllers/concerns/warp_engine/api_error_rendering.rb @@ -0,0 +1,28 @@ +module WarpEngine + module ApiErrorRendering + extend ActiveSupport::Concern + + included do + rescue_from StandardError do |e| + Rails.logger.error("[#{self.class.name}] #{e.class}: #{e.message}") + render json: { error: "Internal server error" }, status: :internal_server_error + end + + rescue_from ActiveRecord::RecordNotFound do + render json: { error: "Not found" }, status: :not_found + end + + rescue_from Errno::ENOENT do + render json: { error: "Not found" }, status: :not_found + end + + rescue_from ArgumentError do |e| + render json: { error: e.message }, status: :bad_request + end + end + + private + + def resolve_mime(path) = WarpEngine::Storage.mime_for(path) + end +end diff --git a/app/controllers/warp_engine/api_controller.rb b/app/controllers/warp_engine/api_controller.rb index 76eb3b6..5f05267 100644 --- a/app/controllers/warp_engine/api_controller.rb +++ b/app/controllers/warp_engine/api_controller.rb @@ -7,25 +7,9 @@ module WarpEngine before_action :set_version_header + include WarpEngine::ApiErrorRendering include WarpEngine::SubjectAuthentication - rescue_from StandardError do |e| - Rails.logger.error("[#{self.class.name}] #{e.class}: #{e.message}") - render json: { error: "Internal server error" }, status: :internal_server_error - end - - rescue_from ActiveRecord::RecordNotFound do |e| - render json: { error: "Not found" }, status: :not_found - end - - rescue_from Errno::ENOENT do |e| - render json: { error: "Not found" }, status: :not_found - end - - rescue_from ArgumentError do |e| - render json: { error: e.message }, status: :bad_request - end - rescue_from WarpEngine::DownloadService::Denied do render json: { error: "Forbidden" }, status: :forbidden end @@ -33,13 +17,7 @@ module WarpEngine private def set_version_header - response.headers["WarpEngine-Version"] = WarpEngine::VERSION end - - def resolve_mime(path) - ext = File.extname(path.to_s).delete_prefix(".") - Mime::Type.lookup_by_extension(ext) || "application/octet-stream" - end end end diff --git a/app/models/warp_engine/release_asset.rb b/app/models/warp_engine/release_asset.rb index c197510..847b645 100644 --- a/app/models/warp_engine/release_asset.rb +++ b/app/models/warp_engine/release_asset.rb @@ -13,6 +13,11 @@ module WarpEngine default_scope { where(deleted_at: nil) } + def self.for_relative_path(relative) + absolute = File.join(WarpEngine.config.file_container_path, relative.to_s) + find_by(path: absolute) || where("path LIKE ?", "%/#{sanitize_sql_like(relative.to_s)}").first + end + def self.ransackable_attributes(auth_object = nil) %w[created_at deleted_at id kind path release_id updated_at] end diff --git a/app/models/warp_engine/software_image.rb b/app/models/warp_engine/software_image.rb index 3a396fc..0f61851 100644 --- a/app/models/warp_engine/software_image.rb +++ b/app/models/warp_engine/software_image.rb @@ -13,7 +13,7 @@ module WarpEngine before_save :unset_other_defaults, if: -> { is_default? && is_default_changed? } - default_scope { order(:position) } + scope :ordered, -> { order(:position) } def self.ransackable_attributes(auth_object = nil) %w[created_at id image_id is_default position software_id updated_at] diff --git a/app/services/warp_engine/download_service.rb b/app/services/warp_engine/download_service.rb index c2170bc..ca2c122 100644 --- a/app/services/warp_engine/download_service.rb +++ b/app/services/warp_engine/download_service.rb @@ -3,14 +3,6 @@ module WarpEngine class Denied < StandardError; end - def self.container_base - WarpEngine.config.file_container_path - end - - def self.base_path - Pathname.new(container_base).realpath - end - def locate(path:, ip:, user_agent:, referer:, subject: nil, request: nil) relative = path.to_s return nil unless storage.file?(relative) @@ -52,9 +44,7 @@ module WarpEngine end def find_asset(relative) - escaped = relative.gsub("%", "\\%").gsub("_", "\\_") - WarpEngine::ReleaseAsset.find_by(path: File.join(self.class.container_base, relative)) || - WarpEngine::ReleaseAsset.where("path LIKE ?", "%#{escaped}%").first + WarpEngine::ReleaseAsset.for_relative_path(relative) end def log_download(relative, asset:, ip:, user_agent:, referer:, subject:) diff --git a/app/services/warp_engine/file_service.rb b/app/services/warp_engine/file_service.rb index d418d4a..b2cee17 100644 --- a/app/services/warp_engine/file_service.rb +++ b/app/services/warp_engine/file_service.rb @@ -1,10 +1,6 @@ module WarpEngine class FileService - def self.base_path - Pathname.new(WarpEngine.config.file_container_path).realpath - end - def show(input, subject: nil) relative = input.path.to_s @@ -28,7 +24,7 @@ module WarpEngine return WarpEngine::Access::Grant::OPEN if WarpEngine::AccessPolicy.open? - asset = WarpEngine::ReleaseAsset.where("path LIKE ?", "%#{relative.gsub('%', '\\%').gsub('_', '\\_')}%").first + asset = WarpEngine::ReleaseAsset.for_relative_path(relative) grant = WarpEngine.access_policy.authorize_download(asset: asset, subject: subject, request: nil) raise WarpEngine::DownloadService::Denied if grant.nil? diff --git a/lib/warp_engine/storage.rb b/lib/warp_engine/storage.rb index 0e77b9b..d7222eb 100644 --- a/lib/warp_engine/storage.rb +++ b/lib/warp_engine/storage.rb @@ -43,6 +43,11 @@ module WarpEngine end class << self + def mime_for(path) + ext = File.extname(path.to_s).delete_prefix(".") + Mime::Type.lookup_by_extension(ext) || "application/octet-stream" + end + def adapter configured = WarpEngine.config.storage_adapter diff --git a/spec/models/platform_link_spec.rb b/spec/models/platform_link_spec.rb index b04ef90..6e57f8a 100644 --- a/spec/models/platform_link_spec.rb +++ b/spec/models/platform_link_spec.rb @@ -28,11 +28,21 @@ RSpec.describe WarpEngine::PlatformLink, type: :model do expect(WarpEngine::PlatformLink.all).to eq([active]) end - it "orders by position" do + it "does not order: ordering is asked for, not inherited" do second = create(:platform_link, position: 2) first = create(:platform_link, position: 1) - expect(WarpEngine::PlatformLink.all).to eq([first, second]) + expect(WarpEngine::PlatformLink.ordered).to eq([ first, second ]) + expect(WarpEngine::PlatformLink.all.to_sql).not_to include("ORDER BY") + end + end + + describe ".ordered" do + it "is what .for_platform uses, so the API keeps its link order" do + second = create(:platform_link, platform: "tic80", position: 2) + first = create(:platform_link, platform: "tic80", position: 1) + + expect(WarpEngine::PlatformLink.for_platform("tic80")).to eq([ first, second ]) end end