xing-backend 0.0.19 → 0.0.20
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/models/role.rb +24 -0
- data/config/locales/json.yml +29 -0
- data/config/routes.rb +3 -0
- data/db/migrate/20140828011806_initial.rb +45 -0
- data/db/migrate/20140914030703_devise_token_auth_add_token_info_to_users.rb +20 -0
- data/db/migrate/20140929192921_remove_login_from_users.rb +5 -0
- data/lib/xing-backend.rb +1 -0
- data/lib/xing/engine.rb +4 -0
- data/lib/xing/nominal/database_config_validator.rb +31 -0
- data/lib/xing/nominal/dependency_utils.rb +26 -0
- data/lib/xing/nominal/secrets_validator.rb +50 -0
- data/lib/xing/nominal/yaml_config_validator.rb +44 -0
- data/lib/xing/services.rb +0 -2
- data/lib/xing/services/class_registry.rb +31 -0
- data/lib/xing/services/{page_wrapper.rb → paged_wrapper.rb} +2 -2
- data/lib/xing/snapshot.rb +4 -0
- data/lib/xing/snapshot/domain_helpers.rb +24 -0
- data/lib/xing/snapshot/fetcher.rb +35 -0
- data/lib/xing/snapshot/local_site_snapshot.rb +37 -0
- data/lib/xing/snapshot/remote_site_snapshot.rb +16 -0
- data/lib/xing/snapshot/site_page_set.rb +29 -0
- data/lib/xing/snapshot/site_snapshot.rb +41 -0
- data/lib/xing/snapshot/sitemap.rb +68 -0
- data/lib/xing/snapshot/writer.rb +15 -0
- data/lib/xing/spec_helpers.rb +7 -0
- data/lib/xing/spec_helpers/api_response_matchers.rb +26 -0
- data/lib/xing/spec_helpers/ci_support.rb +6 -0
- data/lib/xing/spec_helpers/dom_equiv.rb +26 -0
- data/lib/xing/spec_helpers/json_requests.rb +58 -0
- data/lib/xing/spec_helpers/routing_spec_patch.rb +28 -0
- data/lib/xing/spec_helpers/split_servers.rb +15 -0
- data/lib/xing/spec_helpers/test_url_helpers.rb +5 -0
- data/lib/xing/static.rb +1 -0
- data/lib/xing/static/backend_url_cookie.rb +16 -0
- data/lib/xing/static/goto_param.rb +30 -0
- data/lib/xing/static/logger.rb +11 -0
- data/lib/xing/static/rack_app.rb +40 -0
- data/lib/xing/tasks/all.rake +4 -0
- data/lib/xing/tasks/db_recycle.rake +4 -0
- data/lib/xing/tasks/dependencies_common.rake +49 -0
- data/lib/xing/tasks/sample_data.rake +49 -0
- data/lib/xing/tasks/take_snapshot.rake +4 -0
- data/spec/xing/builders/list_builder_spec.rb +0 -2
- data/spec/xing/builders/ordered_list_builder_spec.rb +0 -2
- data/spec/xing/nominal/database_config_validator_spec.rb +98 -0
- data/spec/xing/nominal/secrets_validator_spec.rb +78 -0
- data/spec/xing/serializers/list_spec.rb +116 -0
- data/spec/xing/serializers/paged_index_spec.rb +2 -2
- data/spec/xing/serializers/paged_list_spec.rb +1 -1
- data/spec/xing/services/error_converter_spec.rb +1 -3
- data/spec/xing/services/paged_wrapper_spec.rb +30 -0
- data/spec/xing/snapshot/remote_snapshot_fetcher_spec.rb +81 -0
- data/spec/xing/{services → snapshot}/snapshot_fetcher_spec.rb +6 -4
- data/spec_help/dummy/db/test.sqlite3 +0 -0
- data/spec_help/dummy/log/test.log +143 -0
- data/spec_help/file-sandbox.rb +164 -0
- data/spec_help/spec_helper.rb +0 -2
- metadata +152 -12
- data/lib/xing/services/snapshot_fetcher.rb +0 -33
- data/lib/xing/services/snapshot_writer.rb +0 -19
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'selenium-webdriver'
|
2
|
+
require 'xing/snapshot/site_snapshot'
|
3
|
+
require 'xing/snapshot/writer'
|
4
|
+
|
5
|
+
module Xing
|
6
|
+
module Snapshot
|
7
|
+
class LocalSiteSnapshot < SiteSnapshot
|
8
|
+
include Writer
|
9
|
+
|
10
|
+
def initialize(url)
|
11
|
+
super(url)
|
12
|
+
@wait = Selenium::WebDriver::Wait.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def setup
|
16
|
+
@driver = Selenium::WebDriver.for :chrome
|
17
|
+
end
|
18
|
+
attr_accessor :driver, :wait
|
19
|
+
|
20
|
+
def teardown
|
21
|
+
@driver.close
|
22
|
+
end
|
23
|
+
|
24
|
+
def fetch(url, path)
|
25
|
+
@driver.navigate.to(url+path)
|
26
|
+
# better way to wait till javascript complete?
|
27
|
+
@wait.until do
|
28
|
+
@driver.execute_script("return window.frontendContentLoaded == true;")
|
29
|
+
end
|
30
|
+
element = @driver.find_element(:tag_name, "html")
|
31
|
+
html = element.attribute("outerHTML")
|
32
|
+
write(path, html)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'xing/snapshot/site_snapshot'
|
2
|
+
require 'xing/snapshot/fetcher'
|
3
|
+
|
4
|
+
module Xing
|
5
|
+
module Snapshot
|
6
|
+
class RemoteSiteSnapshot < SiteSnapshot
|
7
|
+
def setup
|
8
|
+
@fetcher = Xing::Snapshot::Fetcher.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def fetch(url, path)
|
12
|
+
@fetcher.perform(url, path)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'xing/snapshot/domain_helpers'
|
2
|
+
|
3
|
+
module Xing
|
4
|
+
module Snapshot
|
5
|
+
class SitePageSet
|
6
|
+
include DomainHelpers
|
7
|
+
|
8
|
+
def initialize(url)
|
9
|
+
@url = domain(url)
|
10
|
+
end
|
11
|
+
|
12
|
+
def pages_to_visit
|
13
|
+
@pages_to_visit ||= Page.published.where.not(type: "Page::Homepage")
|
14
|
+
end
|
15
|
+
|
16
|
+
def visit_pages(&block)
|
17
|
+
STATIC_PATHS_FOR_SITEMAP.each do |path|
|
18
|
+
yield(@url,path,Time.now)
|
19
|
+
end
|
20
|
+
|
21
|
+
pages_to_visit.each do |page|
|
22
|
+
path = page_frontend_url(page.url_slug)
|
23
|
+
yield(@url, path, page.updated_at)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require "xing/snapshot/site_page_set"
|
2
|
+
|
3
|
+
module Xing
|
4
|
+
module Snapshot
|
5
|
+
class SiteSnapshot
|
6
|
+
class << self
|
7
|
+
def create!(url)
|
8
|
+
self.new(url).create!
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(url)
|
13
|
+
@url = url
|
14
|
+
end
|
15
|
+
attr_accessor :url
|
16
|
+
|
17
|
+
def create!
|
18
|
+
@sitemap_page_set = SitePageSet.new(url)
|
19
|
+
setup
|
20
|
+
generate_snapshot
|
21
|
+
teardown
|
22
|
+
end
|
23
|
+
|
24
|
+
def setup
|
25
|
+
end
|
26
|
+
|
27
|
+
def generate_snapshot
|
28
|
+
@sitemap_page_set.visit_pages do |url, path, updated_at|
|
29
|
+
fetch(url, path)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def fetch(url, path)
|
34
|
+
end
|
35
|
+
|
36
|
+
def teardown
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'builder'
|
2
|
+
require 'xing/snapshot/site_page_set'
|
3
|
+
require 'xing/snapshot/domain_helpers'
|
4
|
+
|
5
|
+
module Xing
|
6
|
+
module Snapshot
|
7
|
+
class Sitemap
|
8
|
+
class << self
|
9
|
+
include DomainHelpers
|
10
|
+
|
11
|
+
def create!(url = nil)
|
12
|
+
@bad_pages = []
|
13
|
+
|
14
|
+
@sitemap_page_set = SitePageSet.new(url)
|
15
|
+
|
16
|
+
generate_sitemap
|
17
|
+
update_search_engines if Rails.env.production?
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
def generate_sitemap
|
22
|
+
xml_str = ""
|
23
|
+
|
24
|
+
xml = Builder::XmlMarkup.new(:target => xml_str, :indent => 2)
|
25
|
+
|
26
|
+
xml.instruct!
|
27
|
+
xml.urlset(:xmlns=>'http://www.sitemaps.org/schemas/sitemap/0.9') {
|
28
|
+
@sitemap_page_set.visit_pages do |url, path, update_time|
|
29
|
+
xml.url {
|
30
|
+
xml.loc(url+path)
|
31
|
+
xml.lastmod(update_time.utc.strftime('%Y-%m-%dT%H:%M:%S+00:00'))
|
32
|
+
}
|
33
|
+
end
|
34
|
+
}
|
35
|
+
|
36
|
+
save_file(xml_str)
|
37
|
+
end
|
38
|
+
|
39
|
+
def save_file(xml)
|
40
|
+
File.open("#{ Rails.root }/public/sitemap.xml", "w+") do |f|
|
41
|
+
f.write(xml)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def update_search_engine(host, path, sitemap_uri)
|
46
|
+
Rails.logger.info{"Notifying #{host}"}
|
47
|
+
begin
|
48
|
+
res = Net::HTTP.get_response(host, path + sitemap_uri)
|
49
|
+
rescue Object => ex
|
50
|
+
Rails.logger.info "Error while notifying #{host}: #{ex.inspect}"
|
51
|
+
end
|
52
|
+
Rails.logger.info res.class
|
53
|
+
end
|
54
|
+
|
55
|
+
# Notify popular search engines of the updated sitemap.xml
|
56
|
+
def update_search_engines
|
57
|
+
sitemap_uri = domain + 'sitemap.xml'
|
58
|
+
escaped_sitemap_uri = CGI.escape(sitemap_uri)
|
59
|
+
|
60
|
+
update_search_engine('www.google.com', '/webmasters/tools/ping?sitemap=', escaped_sitemap_uri)
|
61
|
+
update_search_engine('search.yahooapis.com', '/SiteExplorerService/V1/updateNotification?appid=SitemapWriter&url=', escaped_sitemap_uri)
|
62
|
+
update_search_engine('www.bing.com', '/webmaster/ping.aspx?siteMap=', escaped_sitemap_uri)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Xing
|
2
|
+
module Snapshot
|
3
|
+
module Writer
|
4
|
+
def write(path, html)
|
5
|
+
snapshot_file = "#{ Rails.root }/public/frontend_snapshots/#{path.present? ? path : 'index'}.html"
|
6
|
+
dirname = File.dirname(snapshot_file)
|
7
|
+
FileUtils.mkdir_p(dirname)
|
8
|
+
|
9
|
+
File.open(snapshot_file, "w+:ASCII-8BIT:UTF-8") do |f|
|
10
|
+
f.write(html)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,7 @@
|
|
1
|
+
require 'xing/spec_helpers/api_response_matchers'
|
2
|
+
require 'xing/spec_helpers/ci_support'
|
3
|
+
require 'xing/spec_helpers/dom_equiv'
|
4
|
+
require 'xing/spec_helpers/json_requests'
|
5
|
+
require 'xing/spec_helpers/routing_spec_patch'
|
6
|
+
require 'xing/spec_helpers/split_servers'
|
7
|
+
require 'xing/spec_helpers/test_url_helpers'
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module APIResponseMatchers
|
2
|
+
extend RSpec::Matchers::DSL
|
3
|
+
|
4
|
+
matcher :reject_as_unprocessable do
|
5
|
+
match_unless_raises ActiveSupport::TestCase::Assertion do |response|
|
6
|
+
expect(response.status).to eq(422)
|
7
|
+
end
|
8
|
+
|
9
|
+
failure_message do |response|
|
10
|
+
"Expected response to be a 422: Unprocessable Entity"
|
11
|
+
end
|
12
|
+
|
13
|
+
failure_message_when_negated do |source|
|
14
|
+
"Expected response not to be a 422: Unprocessable Entity"
|
15
|
+
end
|
16
|
+
|
17
|
+
description do
|
18
|
+
"should send a 422: Unprocessable Entity status"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
class RSpec::Core::ExampleGroup
|
25
|
+
include APIResponseMatchers
|
26
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module DomEquivalence
|
2
|
+
extend RSpec::Matchers::DSL
|
3
|
+
|
4
|
+
matcher :match_dom_of do |target|
|
5
|
+
match_unless_raises ActiveSupport::TestCase::Assertion do |source|
|
6
|
+
assert_dom_equal source, target
|
7
|
+
end
|
8
|
+
|
9
|
+
failure_message do |source|
|
10
|
+
"Expected #{source} to have equivalent DOM to #{target}"
|
11
|
+
end
|
12
|
+
|
13
|
+
failure_message_when_negated do |source|
|
14
|
+
"Expected #{source} not to have equivalent DOM to #{target}"
|
15
|
+
end
|
16
|
+
|
17
|
+
description do
|
18
|
+
"should be DOM equivalent to #{target}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
class RSpec::Core::ExampleGroup
|
25
|
+
include DomEquivalence
|
26
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module JSONRequests
|
2
|
+
|
3
|
+
def json_get(url, arg2 = nil)
|
4
|
+
get rootify(url), arg2, { 'HTTP_ACCEPT' => 'application/json' }
|
5
|
+
end
|
6
|
+
|
7
|
+
def json_post(url, arg2 = nil)
|
8
|
+
post rootify(url), arg2, { 'HTTP_ACCEPT' => 'application/json' }
|
9
|
+
end
|
10
|
+
|
11
|
+
def json_put(url, arg2 = nil)
|
12
|
+
put rootify(url), arg2, { 'HTTP_ACCEPT' => 'application/json' }
|
13
|
+
end
|
14
|
+
|
15
|
+
def json_delete(url, arg2 = nil)
|
16
|
+
delete rootify(url), arg2, { 'HTTP_ACCEPT' => 'application/json' }
|
17
|
+
end
|
18
|
+
|
19
|
+
def authenticated_json_get(user, url, arg2 = nil)
|
20
|
+
auth_header = user.create_new_auth_token
|
21
|
+
get rootify(url), arg2, auth_header.merge({ 'HTTP_ACCEPT' => 'application/json' })
|
22
|
+
end
|
23
|
+
|
24
|
+
def authenticated_json_post(user, url, arg2 = nil)
|
25
|
+
auth_header = user.create_new_auth_token
|
26
|
+
post rootify(url), arg2, auth_header.merge({ 'HTTP_ACCEPT' => 'application/json' })
|
27
|
+
end
|
28
|
+
|
29
|
+
def authenticated_json_put(user, url, arg2 = nil)
|
30
|
+
auth_header = user.create_new_auth_token
|
31
|
+
put rootify(url), arg2, auth_header.merge({ 'HTTP_ACCEPT' => 'application/json' })
|
32
|
+
end
|
33
|
+
|
34
|
+
def authenticated_json_delete(user, url, arg2 = nil)
|
35
|
+
auth_header = user.create_new_auth_token
|
36
|
+
delete rootify(url), arg2, auth_header.merge({ 'HTTP_ACCEPT' => 'application/json' })
|
37
|
+
end
|
38
|
+
|
39
|
+
def rootify(url)
|
40
|
+
if url[0] == '/'
|
41
|
+
url
|
42
|
+
else
|
43
|
+
"/#{url}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
module RSpec::Rails::RequestExampleGroup
|
51
|
+
include JSONRequests
|
52
|
+
include JsonSpec::Matchers
|
53
|
+
include JsonSpec::Helpers
|
54
|
+
end
|
55
|
+
|
56
|
+
RSpec.configure do |config|
|
57
|
+
config.include JsonSpec::Helpers, :type => :serializer
|
58
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module RSpec
|
2
|
+
module Rails
|
3
|
+
module Matchers
|
4
|
+
# Matchers to help with specs for routing code.
|
5
|
+
module RoutingMatchers
|
6
|
+
class RouteToMatcher
|
7
|
+
def matches_with_patches?(verb_to_path_map)
|
8
|
+
path = verb_to_path_map.values.first
|
9
|
+
verb_to_path_map[verb_to_path_map.keys.first] = "http://#{BACKEND_SUBDOMAIN}.example.com#{path[0] == "/" ? "" : "/"}#{path}";
|
10
|
+
matches_without_patches?(verb_to_path_map)
|
11
|
+
end
|
12
|
+
alias_method_chain :matches?, :patches
|
13
|
+
end
|
14
|
+
|
15
|
+
class FrontendRouteToMatcher < RouteToMatcher
|
16
|
+
def matches?(verb_to_path_map)
|
17
|
+
matches_without_patches?(verb_to_path_map)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def frontend_route_to(*expected)
|
22
|
+
FrontendRouteToMatcher.new(self, *expected)
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
begin
|
2
|
+
require 'static-app'
|
3
|
+
|
4
|
+
Capybara.configure do |capy|
|
5
|
+
backend_server = Capybara::Server.new(Capybara.app)
|
6
|
+
backend_server.boot
|
7
|
+
puts "Rails API server started on #{backend_server.host}:#{backend_server.port}"
|
8
|
+
ActionMailer::Base.default_url_options[:host] = "#{backend_server.host}:#{backend_server.port}"
|
9
|
+
Capybara.app = APP_MODULE::StaticApp.build("../frontend/bin", backend_server.port)
|
10
|
+
RSpec.configure do |config|
|
11
|
+
config.waterpig_clearable_logs << 'test_static'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
rescue LoadError
|
15
|
+
end
|
data/lib/xing/static.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'xing/static/rack_app'
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Xing
|
2
|
+
module Static
|
3
|
+
class BackendUrlCookie
|
4
|
+
def initialize(app, backend_url)
|
5
|
+
@backend_url = backend_url
|
6
|
+
@app = app
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(env)
|
10
|
+
status, headers, body = @app.call(env)
|
11
|
+
headers["Set-Cookie"] = [(headers["Set-Cookie"]), "lrdBackendUrl=#@backend_url"].compact.join(";") unless @backend_url.nil?
|
12
|
+
[ status, headers, body ]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'pp'
|
2
|
+
module Xing
|
3
|
+
module Static
|
4
|
+
class GotoParam
|
5
|
+
def initialize(app)
|
6
|
+
@app = app
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(env)
|
10
|
+
status, headers, body = @app.call(env)
|
11
|
+
default = [ status, headers, body ]
|
12
|
+
request_path = env["SCRIPT_NAME"] + env["PATH_INFO"]
|
13
|
+
if env["QUERY_STRING"]
|
14
|
+
request_path += "&#{env["QUERY_STRING"]}"
|
15
|
+
end
|
16
|
+
|
17
|
+
redirect = [ 301, headers.merge("Location" => "/?goto=#{request_path}", "Content-Length" => "0"), [] ]
|
18
|
+
|
19
|
+
return default unless status == 404
|
20
|
+
return default if /\A(assets|fonts|system)/ =~ request_path
|
21
|
+
return default if /\.(xml|html|ico|txt)\z/ =~ request_path
|
22
|
+
return default if /goto=/ =~ env["QUERY_STRING"]
|
23
|
+
|
24
|
+
return redirect
|
25
|
+
rescue => ex
|
26
|
+
pp ex
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|