require "alephant/renderer/views/html" require "alephant/renderer/views/json" require "alephant/renderer/view_mapper" require "alephant/publisher/request/data_mapper_factory" require "alephant/publisher/request/data_mapper" require "alephant/publisher/request/error" require "alephant/support/parser" require "alephant/preview/fixture_loader" require "alephant/preview/template/base" require "sinatra/base" require "sinatra/reloader" require "faraday" require "json" require "uri" module Alephant module Preview class Server < Sinatra::Base set :bind, "0.0.0.0" register Sinatra::Reloader also_reload "components/*/models/*.rb" also_reload "components/*/mapper.rb" also_reload "components/shared/mappers/*.rb" BASE_LOCATION = "#{(ENV['BASE_LOCATION'] || Dir.pwd)}/components".freeze before do response["Access-Control-Allow-Origin"] = "*" end get "/preview/:id/:template/:region/?:fixture?" do response["X-Sequence"] = sequence_id render_preview end get "/component/:template/?:fixture?" do params["id"] = find_id_from_template params["template"] params["fixture"] = "responsive" unless params["fixture"] response["X-Sequence"] = sequence_id render_component end get "/component/:id/:template/?:fixture?" do response["X-Sequence"] = sequence_id render_component end get "/components/batch" do batch_components = [] get_batched_components.each do |component| component = component[1] options = component.fetch("options", {}) params["template"] = component.fetch("component") params["id"] = find_id_from_template params["template"] params["fixture"] = options.fetch("fixture", "responsive") || "responsive" batch_components << render_batch_component end { :components => batch_components }.to_json end post "/components/batch" do batch_components = [] post_batched_components.each do |component| options = symbolize component.fetch(:options, {}) params["template"] = component.fetch(:component) params["id"] = find_id_from_template params["template"] params["fixture"] = options.fetch(:fixture, "responsive") || "responsive" batch_components << render_batch_component end { :components => batch_components }.to_json end get "/status" do "ok" end not_found do "Not found" end def find_id_from_template(template) files = Dir.glob(BASE_LOCATION + "/**/models/*") file = files.select! { |file| file.include? "/#{template}.rb" }.pop halt(404) if file.nil? result = /#{BASE_LOCATION}\/(\w+)/.match(file) result[1] end def render_preview Template::Base.new( { region => render_component }, preview_template_location ).render end def render_component view_mapper.generate(fixture_data)[template].render.tap do |content| response["Content-Type"] = get_content_type(content) end end def render_batch_component content = render_component { :component => template, :options => {}, :status => 200, :body => content, :content_type => get_content_type(content), :sequence_id => sequence_id } end private def sequence_id Time.now.to_i end def get_content_type(content) return "application/json" if is_json?(content) "text/html" end def is_json?(content) JSON.parse(content) && true rescue Exception false end def request_body JSON.parse(request.body.read, :symbolize_names => true) || {} end def query_string Rack::Utils.parse_nested_query(request.query_string) end def post_batched_components request_body.fetch(:components, []) end def get_batched_components query_string.fetch("components", []) end def model require model_location Alephant::Renderer::Views.get_registered_class(template).new(fixture_data) end def base_path File.join(BASE_LOCATION, id) end def model_location File.join(base_path, "models", "#{template}.rb") end def template params["template"] end def region params["region"] end def id params["id"] end def fixture params["fixture"] || id end def fixture_data if File.exist? "#{base_path}/mapper.rb" loader = Alephant::Preview::FixtureLoader.new(base_path) data_mapper_factory = Alephant::Publisher::Request::DataMapperFactory.new(loader, BASE_LOCATION) begin data_mapper_factory.create(id, params).data rescue Alephant::Publisher::Request::InvalidApiResponse raise "The JSON passed to the data mapper isn't valid" rescue StandardError => e puts e.backtrace raise "There was an issue with the data mapper class: #{e.message}" end else msg = Struct.new(:body) .new(raw_fixture_data) parser.parse msg end end def raw_fixture_data File.open(fixture_location).read end def parser @parser ||= ::Alephant::Support::Parser.new end def fixture_location "#{base_path}/fixtures/#{fixture}.json" end def preview_template_location "#{Template.path}/templates/preview.mustache" end def view_mapper Alephant::Renderer::ViewMapper.new(id, BASE_LOCATION) end def symbolize(hash) Hash[hash.map { |k, v| [k.to_sym, v] }] end end end end