landable 1.7.1.rc1 → 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/.rbenv-gemsets +1 -0
  4. data/.travis.yml +20 -2
  5. data/CHANGELOG.md +8 -1
  6. data/Gemfile +14 -13
  7. data/Rakefile +0 -1
  8. data/app/controllers/landable/api/assets_controller.rb +49 -31
  9. data/app/controllers/landable/api/pages_controller.rb +64 -42
  10. data/app/controllers/landable/api/templates_controller.rb +37 -13
  11. data/app/controllers/landable/api/themes_controller.rb +35 -14
  12. data/app/models/concerns/landable/librarian.rb +36 -0
  13. data/app/models/landable/asset.rb +1 -0
  14. data/app/models/landable/page.rb +9 -0
  15. data/app/models/landable/page_revision.rb +52 -9
  16. data/app/models/landable/search_engine.rb +1 -1
  17. data/app/models/landable/template.rb +10 -0
  18. data/app/models/landable/template_revision.rb +1 -0
  19. data/app/models/landable/theme.rb +1 -0
  20. data/app/responders/landable/api_responder.rb +1 -1
  21. data/app/serializers/landable/asset_serializer.rb +1 -0
  22. data/app/serializers/landable/page_revision_serializer.rb +1 -0
  23. data/app/serializers/landable/page_serializer.rb +4 -13
  24. data/app/serializers/landable/template_serializer.rb +3 -3
  25. data/app/serializers/landable/theme_serializer.rb +1 -1
  26. data/app/services/landable/screenshot_service.rb +32 -0
  27. data/app/views/templates/preview.liquid +13 -11
  28. data/config/routes.rb +6 -3
  29. data/db/migrate/20140501171345_add_deleted_at_to_pages.rb +5 -0
  30. data/db/migrate/20140501171352_add_deleted_at_to_themes.rb +5 -0
  31. data/db/migrate/20140501171359_add_deleted_at_to_assets.rb +5 -0
  32. data/db/migrate/20140501171406_add_deleted_at_to_templates.rb +5 -0
  33. data/db/migrate/20140515164543_add_screenshot_to_page_revisions.rb +5 -0
  34. data/db/test/landable.general.sql +9 -0
  35. data/db/test/landable.page_revisions.sql +5 -3
  36. data/db/test/landable.templates.sql +29 -2
  37. data/db/test/landable.themes.sql +5 -2
  38. data/doc/schema/asset.json +5 -0
  39. data/doc/schema/page.json +5 -0
  40. data/doc/schema/page_revision.json +6 -1
  41. data/doc/schema/template.json +5 -0
  42. data/doc/schema/template_revision.json +5 -0
  43. data/doc/schema/theme.json +5 -0
  44. data/landable.gemspec +31 -33
  45. data/lib/generators/templates/landable.rb +6 -0
  46. data/lib/landable/configuration.rb +39 -6
  47. data/lib/landable/version.rb +3 -3
  48. data/lib/tasks/landable/pgtap.rake +1 -2
  49. data/script/pgtap +10 -0
  50. data/script/redb +0 -2
  51. data/spec/concerns/landable/librarian.rb +45 -0
  52. data/spec/controllers/public/preview/pages_controller_spec.rb +1 -1
  53. data/spec/dummy/db/.keep +0 -0
  54. data/spec/models/landable/page_revision_spec.rb +60 -2
  55. data/spec/models/landable/page_spec.rb +3 -1
  56. data/spec/services/landable/authentication_service_spec.rb +1 -1
  57. data/spec/services/landable/screenshot_service_spec.rb +43 -0
  58. data/spec/services/landable/tidy_service_spec.rb +3 -2
  59. metadata +67 -82
  60. data/landable-1.7.0.gem +0 -0
  61. data/spec/dummy/db/structure.sql +0 -3837
@@ -1,28 +1,46 @@
1
- require_dependency "landable/api_controller"
1
+ require_dependency 'landable/api_controller'
2
2
 
3
3
  module Landable
4
4
  module Api
5
5
  class ThemesController < ApiController
6
- def index
7
- respond_with Theme.all
8
- end
6
+ # filters
7
+ before_filter :load_theme, except: [:create, :index, :preview]
9
8
 
9
+ # RESTful methods
10
10
  def create
11
11
  theme = Theme.new(theme_params)
12
12
  theme.save!
13
+
13
14
  respond_with theme, status: :created, location: theme_url(theme)
14
15
  end
15
16
 
17
+ def destroy
18
+ @theme.try(:deactivate)
19
+
20
+ respond_with @theme
21
+ end
22
+
23
+ def index
24
+ respond_with Theme.all
25
+ end
26
+
27
+ def reactivate
28
+ @theme.try(:reactivate)
29
+
30
+ respond_with @theme
31
+ end
32
+
16
33
  def show
17
- respond_with Theme.find(params[:id])
34
+ respond_with @theme
18
35
  end
19
36
 
20
37
  def update
21
- theme = Theme.find(params[:id])
22
- theme.update_attributes! theme_params
23
- respond_with theme
38
+ @theme.update_attributes!(theme_params)
39
+
40
+ respond_with @theme
24
41
  end
25
42
 
43
+ # custom methods
26
44
  def preview
27
45
  theme = Theme.new(theme_params)
28
46
  page = Page.example(theme: theme)
@@ -30,17 +48,17 @@ module Landable
30
48
  params[:theme][:asset_ids].try(:each) do |asset_id|
31
49
  theme.attachments.add Asset.find(asset_id)
32
50
  end
33
-
51
+
34
52
  content = render_to_string(
35
53
  text: RenderService.call(page),
36
54
  layout: page.theme.file || false
37
55
  )
38
-
56
+
39
57
  respond_to do |format|
40
58
  format.html do
41
59
  render text: content, layout: false, content_type: 'text/html'
42
60
  end
43
-
61
+
44
62
  format.json do
45
63
  render json: {theme: {preview: content}}
46
64
  end
@@ -48,10 +66,13 @@ module Landable
48
66
  end
49
67
 
50
68
  private
69
+ def load_theme
70
+ @theme = Theme.find(params[:id])
71
+ end
51
72
 
52
- def theme_params
53
- params.require(:theme).permit(:id, :name, :body, :description, :thumbnail_url)
54
- end
73
+ def theme_params
74
+ params.require(:theme).permit(:id, :name, :body, :description, :thumbnail_url)
75
+ end
55
76
  end
56
77
  end
57
78
  end
@@ -0,0 +1,36 @@
1
+ module Landable
2
+ # modules
3
+ module Librarian
4
+ # The original name for this module was Landable::JamesCole, but Isaac said 'no.'
5
+ # if this name confuses you, go watch "12 Monkeys" you class-less savage.
6
+ # Wait, where were you in 95? Prison? The womb? Ohmygawd, that was a great film.
7
+
8
+ # extensions
9
+ extend ActiveSupport::Concern
10
+
11
+ # includes
12
+ included do
13
+ end
14
+
15
+ # validations
16
+
17
+ # standard methods
18
+ def deactivate
19
+ self.update_attribute(:deleted_at, Time.now)
20
+ end
21
+
22
+ # custom methods
23
+ def nuke!
24
+ self.destroy
25
+ end
26
+
27
+ def reactivate
28
+ self.update_attribute(:deleted_at, nil)
29
+ end
30
+
31
+ # end
32
+ end
33
+
34
+ # Live Long the Army of the 12 Monkeys!
35
+ # end
36
+ end
@@ -6,6 +6,7 @@ require 'digest/md5'
6
6
  module Landable
7
7
  class Asset < ActiveRecord::Base
8
8
  include Landable::TableName
9
+ include Landable::Librarian
9
10
 
10
11
  mount_uploader :data, Landable::AssetUploader
11
12
  alias :file :data
@@ -10,6 +10,7 @@ module Landable
10
10
  include Landable::HasAssets
11
11
  include Landable::Engine.routes.url_helpers
12
12
  include Landable::TableName
13
+ include Landable::Librarian
13
14
 
14
15
  validates_presence_of :path, :status_code
15
16
  validates_presence_of :redirect_url, if: -> page { page.redirect? }
@@ -124,6 +125,14 @@ module Landable
124
125
  end
125
126
  end
126
127
 
128
+ def deactivate
129
+ self.update_attribute(:status_code, 410)
130
+
131
+ publish!(author_id: updated_by_author.id, notes: "This page has been trashed")
132
+
133
+ super
134
+ end
135
+
127
136
  def html?
128
137
  content_type == 'text/html'
129
138
  end
@@ -10,6 +10,7 @@ module Landable
10
10
  'page_id',
11
11
  'imported_at',
12
12
  'created_at',
13
+ 'deleted_at',
13
14
  'updated_at',
14
15
  'published_revision_id',
15
16
  'is_publishable',
@@ -21,7 +22,8 @@ module Landable
21
22
 
22
23
  belongs_to :author
23
24
  belongs_to :page, inverse_of: :revisions
24
- has_many :screenshots, class_name: 'Landable::Screenshot', as: :screenshotable
25
+
26
+ after_commit :add_screenshot!, on: :create
25
27
 
26
28
  def page_id=(id)
27
29
  # set the value
@@ -42,14 +44,14 @@ module Landable
42
44
  end
43
45
 
44
46
  def snapshot
45
- Page.new(title: self.title,
46
- meta_tags: page.meta_tags,
47
+ Page.new(title: self.title,
48
+ meta_tags: page.meta_tags,
47
49
  head_content: page.head_content,
48
- body: self.body,
49
- path: self.path,
50
- redirect_url: self.redirect_url,
51
- status_code: self.status_code,
52
- theme_id: self.theme_id,
50
+ body: self.body,
51
+ path: self.path,
52
+ redirect_url: self.redirect_url,
53
+ status_code: self.status_code,
54
+ theme_id: self.theme_id,
53
55
  category_id: self.category_id,
54
56
  abstract: self.abstract,
55
57
  hero_asset_id: self.hero_asset_id)
@@ -64,11 +66,52 @@ module Landable
64
66
  end
65
67
 
66
68
  def preview_url
67
- public_preview_page_revision_url(self)
69
+ begin
70
+ public_preview_page_revision_url(self, host: Landable.configuration.public_host)
71
+ rescue ArgumentError
72
+ Rails.logger.warn "Failed to generate preview url for page revision #{id} - missing Landable.configuration.public_host"
73
+ nil
74
+ end
68
75
  end
69
76
 
70
77
  def preview_path
71
78
  public_preview_page_revision_path(self)
72
79
  end
80
+
81
+ mount_uploader :screenshot, Landable::AssetUploader
82
+
83
+ def screenshot_url
84
+ screenshot.try(:url)
85
+ end
86
+
87
+ def add_screenshot!
88
+ return nil if preview_url.blank?
89
+
90
+ unless Landable.configuration.screenshots_enabled
91
+ Rails.logger.info "Screenshots disabled; skipping for #{path}"
92
+ return
93
+ end
94
+
95
+ attempts_left = 3
96
+
97
+ begin
98
+ attempts_left -= 1
99
+
100
+ self.screenshot = ScreenshotService.capture(preview_url)
101
+
102
+ # we've got a trigger preventing updates to other columns, so! muck
103
+ # about under the hood to commit the asset, and explicitly only update
104
+ # this column.
105
+ store_screenshot!
106
+ write_screenshot_identifier
107
+ update_column :screenshot, self[:screenshot]
108
+
109
+ rescue ScreenshotService::Error => error
110
+ Rails.logger.warn "Failed to generate screenshot (#{attempts_left} attempt(s) left) for #{path}: #{error.inspect}"
111
+
112
+ retry if attempts_left > 0
113
+ end
114
+ end
73
115
  end
116
+
74
117
  end
@@ -14,7 +14,7 @@ module Landable
14
14
  end
15
15
 
16
16
  def meta
17
- { search: { total_results: @scope.count } }
17
+ { search: { total_results: @scope.count(:all) } }
18
18
  end
19
19
 
20
20
  def filter_by!(filters)
@@ -1,6 +1,10 @@
1
1
  module Landable
2
2
  class Template < ActiveRecord::Base
3
3
  include Landable::TableName
4
+ include Landable::Librarian
5
+
6
+ # attributes
7
+ attr_accessor :temp_author
4
8
 
5
9
  validates_presence_of :name, :slug, :description
6
10
  validates_uniqueness_of :name, case_sensitive: false
@@ -14,6 +18,12 @@ module Landable
14
18
  template.is_publishable = true unless template.published_revision_id_changed?
15
19
  }
16
20
 
21
+ def deactivate
22
+ publish!(author_id: temp_author.id, notes: "This template has been trashed")
23
+
24
+ super
25
+ end
26
+
17
27
  def name= val
18
28
  self[:name] = val
19
29
  self[:slug] ||= (val && val.underscore.gsub(/[^\w_]/, '_').gsub(/_{2,}/, '_'))
@@ -11,6 +11,7 @@ module Landable
11
11
  'thumbnail_url',
12
12
  'is_layout',
13
13
  'is_publishable',
14
+ 'deleted_at'
14
15
  ]
15
16
 
16
17
  cattr_accessor :ignored_template_attributes
@@ -4,6 +4,7 @@ module Landable
4
4
  class Theme < ActiveRecord::Base
5
5
  include Landable::TableName
6
6
  include Landable::HasAssets
7
+ include Landable::Librarian
7
8
 
8
9
  validates_presence_of :name, :description
9
10
  validates_uniqueness_of :name, case_sensitive: false
@@ -18,7 +18,7 @@ module Landable
18
18
  # For updates, rails defaults to returning 204 No Content;
19
19
  # we would actually prefer that the updated record be returned,
20
20
  # in case an update to one key necessitates an automatic update to another.
21
- if patch? || put?
21
+ if patch? || put? || delete?
22
22
  display resource
23
23
  else
24
24
  super
@@ -4,6 +4,7 @@ module Landable
4
4
  attributes :file_size, :mime_type, :md5sum
5
5
  attributes :created_at, :updated_at
6
6
  attributes :public_url
7
+ attributes :deleted_at
7
8
 
8
9
  embed :ids
9
10
 
@@ -6,6 +6,7 @@ module Landable
6
6
  attributes :id, :ordinal, :notes, :is_minor, :is_published
7
7
  attributes :created_at, :updated_at
8
8
  attributes :preview_path
9
+ attributes :screenshot_url
9
10
 
10
11
  embed :ids
11
12
  has_one :page
@@ -1,18 +1,9 @@
1
1
  module Landable
2
2
  class PageSerializer < ActiveModel::Serializer
3
- attributes :id
4
- attributes :path
5
- attributes :title
6
- attributes :body
7
- attributes :head_content
8
- attributes :redirect_url
9
- attributes :meta_tags
10
- attributes :is_publishable
11
- attributes :preview_path
12
- attributes :lock_version
13
- attributes :status_code
14
- attributes :abstract
15
- attributes :hero_asset_name
3
+
4
+ attributes :abstract, :body, :deleted_at, :head_content, :hero_asset_name,
5
+ :id, :is_publishable, :lock_version, :meta_tags, :path,
6
+ :preview_path, :redirect_url, :status_code, :title
16
7
 
17
8
  embed :ids
18
9
  has_one :theme
@@ -1,8 +1,8 @@
1
1
  module Landable
2
2
  class TemplateSerializer < ActiveModel::Serializer
3
- attributes :id, :name, :body, :description
4
- attributes :thumbnail_url, :slug, :is_layout, :editable
5
- attributes :file, :is_publishable
3
+
4
+ attributes :body, :deleted_at, :description, :editable, :file, :id,
5
+ :is_layout, :is_publishable, :name, :slug, :thumbnail_url
6
6
 
7
7
  embed :ids
8
8
  has_one :published_revision
@@ -1,6 +1,6 @@
1
1
  module Landable
2
2
  class ThemeSerializer < ActiveModel::Serializer
3
- attributes :id, :name, :editable, :body, :description, :thumbnail_url
3
+ attributes :id, :name, :editable, :body, :description, :thumbnail_url, :deleted_at
4
4
 
5
5
  embed :ids
6
6
  end
@@ -0,0 +1,32 @@
1
+ require 'tempfile'
2
+
3
+ module Landable
4
+ class ScreenshotService
5
+ class Error < StandardError; end
6
+
7
+ class << self
8
+ def capture url
9
+ if not Landable.configuration.publicist_url
10
+ Rails.logger.warn "Couldn't generate screenshot for #{url}; no Landable.configuration.publicist_url configured"
11
+ else
12
+ screenshots_uri = URI(Landable.configuration.publicist_url)
13
+ screenshots_uri.path = '/api/services/screenshots'
14
+
15
+ response = Net::HTTP.post_form screenshots_uri, 'screenshot[url]' => url
16
+
17
+ if response.code == '200'
18
+ file = Tempfile.new ['screenshot-', '.png']
19
+ file.binmode
20
+ file.write response.body
21
+ file.rewind
22
+
23
+ file
24
+ else
25
+ raise Error, "Received #{response.code} back from #{screenshots_uri.to_s}"
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ end
32
+ end
@@ -85,25 +85,27 @@
85
85
  <!-- pretty invalid, this is -->
86
86
  <style type="text/css">
87
87
  body {
88
- padding-top: 40px;
88
+ padding-top: 30px;
89
89
  }
90
90
 
91
91
  body:before {
92
- text-align: center;
93
- font-weight: bold;
94
- background-color: #FFDA73;
95
- font-family: Roboto;
96
92
  width: 100%;
97
93
  position: absolute;
98
94
  top: 0;
99
95
  left: 0;
100
- font-size: 20px;
101
- padding: 5px 0;
102
- line-height: 30px;
103
- border-bottom: 1px #e2bd56 solid;
104
- content: "Preview Mode (Test your unpublished changes with this page. Share this URL!)";
105
96
  box-sizing: border-box;
106
- height: 40px;
97
+ box-shadow: inset 0 -10px 5px -10px rgba(0, 0, 0, 0.5);
98
+ height: 30px;
99
+ padding: 5px 0;
100
+ background-color: #FFDA73;
101
+ line-height: 20px;
102
+ font-size: 14px;
103
+ font-family: sans-serif;
104
+ font-weight: normal;
105
+ text-align: center;
106
+ color: #7e592d;
107
+ content: "Preview Mode";
108
+ text-transform: uppercase;
107
109
  }
108
110
 
109
111
  body.publicist-preview {