trek 2.1.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.slim-lint.yml +0 -3
- data/AGENTS.md +8 -1
- data/CHANGELOG.md +75 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +20 -3
- data/Makefile +11 -9
- data/README.md +1 -1
- data/app/assets/icons/trek/audit.svg +1 -0
- data/app/assets/icons/trek/chevron-down.svg +1 -0
- data/app/assets/icons/trek/chevron-up.svg +1 -0
- data/app/assets/icons/trek/external.svg +1 -0
- data/app/assets/icons/trek/print.svg +1 -0
- data/app/assets/icons/trek/theme-auto.svg +1 -0
- data/app/assets/icons/trek/theme-dark.svg +1 -0
- data/app/assets/icons/trek/theme-light.svg +1 -0
- data/app/assets/stylesheets/trek/base.css +13 -0
- data/app/components/trek/button_component/button_component.css +9 -0
- data/app/components/trek/button_component.rb +13 -1
- data/app/components/trek/dashboard/analytics_component/analytics_component.html.slim +2 -0
- data/app/components/trek/dashboard/analytics_component.rb +1 -0
- data/app/components/trek/dashboard/chart_component/chart_component.css +192 -0
- data/app/components/trek/dashboard/chart_component/chart_component.html.slim +6 -0
- data/app/components/trek/dashboard/chart_component/chart_component.js +1 -0
- data/app/components/trek/dashboard/chart_component.rb +27 -0
- data/app/components/trek/dashboard/tile_component/tile_component.css +40 -0
- data/app/components/trek/dashboard/tile_component/tile_component.html.slim +14 -4
- data/app/components/trek/dashboard/tile_component.rb +20 -2
- data/app/components/trek/dialog_component/dialog_component.html.slim +6 -2
- data/app/components/trek/dialog_component/dialog_component.js +62 -2
- data/app/components/trek/dropdown_component/dropdown_component.css +66 -0
- data/app/components/trek/dropdown_component/dropdown_component.html.slim +31 -0
- data/app/components/trek/dropdown_component/dropdown_component.js +114 -0
- data/app/components/trek/dropdown_component.rb +29 -0
- data/app/components/trek/form/actions_component/actions_component.css +6 -7
- data/app/components/trek/form/content_editor_component/content_editor_component.html.slim +10 -12
- data/app/components/trek/form/content_editor_component/content_editor_component.js +4 -0
- data/app/components/trek/form/group_component/group_component.css +34 -0
- data/app/components/trek/header_component/header_component.css +10 -4
- data/app/components/trek/header_component/header_component.html.slim +17 -10
- data/app/components/trek/header_component/header_component.js +29 -5
- data/app/components/trek/header_component.rb +20 -3
- data/app/components/trek/layout_component/layout_component.css +20 -0
- data/app/components/trek/layout_component/layout_component.html.slim +16 -0
- data/app/components/trek/layout_component/layout_component.js +35 -6
- data/app/components/trek/menu_component/menu_component.css +6 -0
- data/app/components/trek/menu_component.rb +29 -4
- data/app/components/trek/toaster/toast_component.rb +1 -1
- data/app/controllers/concerns/trek/authorization.rb +12 -0
- data/app/formatters/prosemirror_to_html/global_id_to_links_formatter.rb +3 -1
- data/app/helpers/trek/page_path_helper.rb +4 -3
- data/app/helpers/trek/theme_helper.rb +16 -1
- data/app/models/concerns/trek/phonable.rb +15 -3
- data/app/models/concerns/trek/sectionable.rb +5 -0
- data/app/models/concerns/trek/users/validations.rb +1 -1
- data/app/models/prosemirror.rb +5 -2
- data/app/uploaders/audio_uploader.rb +11 -0
- data/app/uploaders/record_image_uploader.rb +3 -0
- data/app/views/trek/panels/images/_form.html.slim +1 -1
- data/app/views/trek/panels/images/create.turbo_stream.slim +3 -3
- data/app/views/trek/panels/images/new.html.slim +1 -1
- data/app/views/trek/panels/links/_form.html.slim +1 -1
- data/app/views/trek/panels/links/create.turbo_stream.slim +3 -3
- data/app/views/trek/panels/links/new.html.slim +1 -1
- data/app/views/trek/panels/prompts/_form.html.slim +1 -1
- data/app/views/trek/panels/prompts/create.turbo_stream.slim +3 -3
- data/app/views/trek/panels/prompts/new.html.slim +1 -1
- data/config/locales/trek.en.yml +3 -2
- data/config/locales/trek.fr.yml +1 -0
- data/docs/customize.md +18 -2
- data/docs/deploy.md +7 -2
- data/docs/getting-started.md +1 -1
- data/docs/index.md +1 -1
- data/docs/reference/components.md +15 -0
- data/docs/reference/concerns/phonable.md +27 -1
- data/docs/reference/environment-variables.md +2 -0
- data/docs/reference/forms.md +2 -0
- data/docs/reference/generators/install.md +4 -1
- data/docs/reference/icons.md +1 -1
- data/docs/reference/models/tag.md +1 -1
- data/docs/reference/models/user.md +4 -2
- data/docs/reference/uploaders.md +31 -6
- data/docs/what-is-trek.md +1 -1
- data/lib/generators/trek/install/admin/routes_generator.rb +7 -1
- data/lib/generators/trek/install/appsignal_generator.rb +102 -0
- data/lib/generators/trek/install/model_specs_generator.rb +64 -0
- data/lib/generators/trek/install/postmark_generator.rb +3 -0
- data/lib/generators/trek/install/ruby_generator.rb +1 -1
- data/lib/generators/trek/install/scalingo_generator.rb +1 -1
- data/lib/generators/trek/install/uploaders_generator.rb +2 -0
- data/lib/generators/trek/install_generator.rb +5 -0
- data/lib/generators/trek/taxonomies_generator.rb +7 -0
- data/lib/generators/trek/templates/.gitlab-ci.yml +1 -1
- data/lib/generators/trek/templates/config/appsignal.rb.tt +25 -0
- data/lib/generators/trek/templates/controllers/admin/uploads_controller.rb +4 -0
- data/lib/generators/trek/templates/controllers/admin/users_controller.rb +4 -1
- data/lib/generators/trek/templates/controllers/concerns/appsignal_context.rb +37 -0
- data/lib/generators/trek/templates/db/seeds/setup/fragments.rb +3 -0
- data/lib/generators/trek/templates/db/seeds/setup/users.rb +6 -0
- data/lib/generators/trek/templates/db/seeds/test/cases/page_tree.rb +5 -0
- data/lib/generators/trek/templates/initializers/appsignal_context.rb +5 -0
- data/lib/generators/trek/templates/locales/scaffold/model.en.yml.tt +1 -1
- data/lib/generators/trek/templates/migrations/fragments.rb.erb +1 -1
- data/lib/generators/trek/templates/migrations/menu_nodes.rb.erb +0 -1
- data/lib/generators/trek/templates/migrations/tag_categories.rb.erb +1 -1
- data/lib/generators/trek/templates/migrations/taggings.rb.erb +1 -1
- data/lib/generators/trek/templates/migrations/users.rb.erb +2 -1
- data/lib/generators/trek/templates/models/tag.rb +6 -1
- data/lib/generators/trek/templates/policies/admin/tag_policy.rb +1 -1
- data/lib/generators/trek/templates/policies/external_link_policy.rb +1 -1
- data/lib/generators/trek/templates/spec/fixtures/files/image.png +0 -0
- data/lib/generators/trek/templates/spec/fixtures/files/sound.mp3 +0 -0
- data/lib/generators/trek/templates/spec/models/page_spec.rb +24 -0
- data/lib/generators/trek/templates/spec/models/user_spec.rb +76 -0
- data/lib/generators/trek/templates/spec/policies/admin/external_link_policy_spec.rb +17 -0
- data/lib/generators/trek/templates/spec/policies/admin/fragment_policy_spec.rb +33 -0
- data/lib/generators/trek/templates/spec/policies/admin/page_policy_spec.rb +39 -0
- data/lib/generators/trek/templates/spec/policies/admin/preview_policy_spec.rb +9 -0
- data/lib/generators/trek/templates/spec/policies/admin/tag_category_policy_spec.rb +31 -0
- data/lib/generators/trek/templates/spec/policies/admin/tag_policy_spec.rb +31 -0
- data/lib/generators/trek/templates/spec/policies/admin/user_policy_spec.rb +58 -0
- data/lib/generators/trek/templates/spec/requests/admin/fragments_spec.rb +50 -0
- data/lib/generators/trek/templates/spec/requests/admin/pages_spec.rb +47 -0
- data/lib/generators/trek/templates/spec/requests/admin/uploads_spec.rb +22 -0
- data/lib/generators/trek/templates/spec/requests/admin/user_password_resets_spec.rb +71 -0
- data/lib/generators/trek/templates/spec/requests/admin/user_sessions_spec.rb +45 -0
- data/lib/generators/trek/templates/spec/requests/admin/users_spec.rb +99 -0
- data/lib/generators/trek/templates/views/admin/user_password_resets/edit.html.slim +1 -1
- data/lib/generators/trek/templates/views/admin/user_password_resets/new.html.slim +1 -1
- data/lib/generators/trek/templates/views/admin/user_sessions/new.html.slim +2 -2
- data/lib/generators/trek/templates/views/admin/users/theme.turbo_stream.slim +2 -0
- data/lib/generators/trek/upgrade/v2_generator.rb +3 -0
- data/lib/tasks/trek/attachments.rake +18 -0
- data/lib/trek/cached_attachments.rb +53 -0
- data/lib/trek/saved_attachments_reload.rb +44 -0
- data/lib/trek/version.rb +1 -1
- data/lib/trek.rb +3 -0
- data/package.json +1 -1
- data/postcss.config.js +36 -0
- data/trek.gemspec +5 -0
- metadata +76 -3
- data/app/assets/icons/trek/theme.svg +0 -11
|
@@ -58,6 +58,9 @@ module Trek
|
|
|
58
58
|
|
|
59
59
|
def log_and_abort(error)
|
|
60
60
|
Rails.logger.info(error)
|
|
61
|
+
# Reported as handled (the delivery is deliberately abandoned, not a failure) and
|
|
62
|
+
# through Rails.error, so the app code stays independent of the monitoring vendor.
|
|
63
|
+
Rails.error.report(error, handled: true)
|
|
61
64
|
end
|
|
62
65
|
RUBY
|
|
63
66
|
end
|
|
@@ -5,7 +5,7 @@ module Trek
|
|
|
5
5
|
module Generators
|
|
6
6
|
module Install
|
|
7
7
|
class ScalingoGenerator < Rails::Generators::Base
|
|
8
|
-
BUILDPACK_URL = "https://github.com/etaminstudio/trek-buildpack
|
|
8
|
+
BUILDPACK_URL = "https://github.com/etaminstudio/trek-buildpack/archive/refs/tags/v0.1.0.tar.gz".freeze
|
|
9
9
|
|
|
10
10
|
include Trek::Generators::Helpers
|
|
11
11
|
|
|
@@ -76,6 +76,10 @@ module Trek
|
|
|
76
76
|
generate "trek:install:mailer"
|
|
77
77
|
end
|
|
78
78
|
|
|
79
|
+
def install_monitoring
|
|
80
|
+
generate "trek:install:appsignal"
|
|
81
|
+
end
|
|
82
|
+
|
|
79
83
|
def install_user
|
|
80
84
|
generate "trek:install:user"
|
|
81
85
|
end
|
|
@@ -111,6 +115,7 @@ module Trek
|
|
|
111
115
|
generate "trek:install:fragment"
|
|
112
116
|
generate "trek:install:menu_node"
|
|
113
117
|
generate "trek:install:external_link"
|
|
118
|
+
generate "trek:install:model_specs"
|
|
114
119
|
end
|
|
115
120
|
|
|
116
121
|
def install_ci
|
|
@@ -44,6 +44,13 @@ module Trek
|
|
|
44
44
|
"app/policies/admin/tag_category_policy.rb"
|
|
45
45
|
end
|
|
46
46
|
|
|
47
|
+
def copy_policy_specs
|
|
48
|
+
template "spec/policies/admin/tag_policy_spec.rb",
|
|
49
|
+
"spec/policies/admin/tag_policy_spec.rb"
|
|
50
|
+
template "spec/policies/admin/tag_category_policy_spec.rb",
|
|
51
|
+
"spec/policies/admin/tag_category_policy_spec.rb"
|
|
52
|
+
end
|
|
53
|
+
|
|
47
54
|
def generate_crud
|
|
48
55
|
generate("trek:scaffold", "TagCategory name:string key:string")
|
|
49
56
|
generate("trek:scaffold", "Tag name:string key:string category:references")
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# AppSignal's Ruby config file, loaded by the gem's Railtie (not a Rails initializer).
|
|
2
|
+
#
|
|
3
|
+
# The push API key lives in the Rails credentials (the installer injects it from
|
|
4
|
+
# APPSIGNAL_PUSH_API_KEY). APPSIGNAL_* environment variables are read before this
|
|
5
|
+
# block runs, so they keep precedence: the credentials only fill in what is unset,
|
|
6
|
+
# which leaves per-environment overrides (e.g. APPSIGNAL_APP_ENV) possible.
|
|
7
|
+
Appsignal.configure do |config|
|
|
8
|
+
config.name = "<%= application_name.dasherize %>"
|
|
9
|
+
|
|
10
|
+
config.push_api_key = Rails.application.credentials.dig(:appsignal, :push_api_key) if config.push_api_key.blank?
|
|
11
|
+
|
|
12
|
+
# The agent activates itself only when APPSIGNAL_PUSH_API_KEY is set in the
|
|
13
|
+
# environment; with the key in the credentials, activation has to be explicit.
|
|
14
|
+
# Production is monitored as soon as a key exists, the other environments never are.
|
|
15
|
+
config.active = config.push_api_key.present? if Rails.env.production?
|
|
16
|
+
|
|
17
|
+
# The slug is read-only on Scalingo, as on any host with a read-only filesystem, so
|
|
18
|
+
# the agent cannot write its own diagnostic log to log/: it falls back to /tmp on its
|
|
19
|
+
# own and announces it on every boot and every one-off container. Say it upfront.
|
|
20
|
+
config.log_path = "/tmp" if Rails.env.production?
|
|
21
|
+
|
|
22
|
+
# Trek mounts rails-healthcheck at /healthcheck — the platform polls it constantly
|
|
23
|
+
# and it would dominate the throughput graphs. (Rails' own /up is ignored by default.)
|
|
24
|
+
config.ignore_actions += ["Healthcheck::HealthchecksController#check"]
|
|
25
|
+
end
|
|
@@ -15,7 +15,10 @@ module Admin
|
|
|
15
15
|
def theme
|
|
16
16
|
value = params[:theme].to_s
|
|
17
17
|
Current.user.update!(theme: value) if User.themes.key?(value)
|
|
18
|
-
|
|
18
|
+
respond_to do |format|
|
|
19
|
+
format.turbo_stream
|
|
20
|
+
format.html { head :no_content }
|
|
21
|
+
end
|
|
19
22
|
end
|
|
20
23
|
|
|
21
24
|
private
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Tags every AppSignal transaction with the area it belongs to and the acting user,
|
|
2
|
+
# so errors and slow requests can be filtered per namespace and per actor.
|
|
3
|
+
#
|
|
4
|
+
# Included in ApplicationController (front-office and Admin::BaseController through
|
|
5
|
+
# it) and, from config/initializers/appsignal_context.rb, in Trek::BaseController.
|
|
6
|
+
#
|
|
7
|
+
# Free of charge when AppSignal is inactive (no push API key, i.e. every environment
|
|
8
|
+
# but production): each Appsignal call below returns early.
|
|
9
|
+
module AppsignalContext
|
|
10
|
+
extend ActiveSupport::Concern
|
|
11
|
+
|
|
12
|
+
ADMIN_CONTROLLER_PATHS = ["admin/", "trek/"].freeze
|
|
13
|
+
|
|
14
|
+
included do
|
|
15
|
+
before_action :set_appsignal_context
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def set_appsignal_context
|
|
21
|
+
Appsignal.set_namespace(appsignal_namespace)
|
|
22
|
+
Appsignal.add_tags(user_id: appsignal_user_id) if appsignal_user_id
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def appsignal_namespace
|
|
26
|
+
controller_path.start_with?(*ADMIN_CONTROLLER_PATHS) ? "admin" : "web"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def appsignal_user_id
|
|
30
|
+
# Trek::Authentication resolves the session in its own before_action, which may
|
|
31
|
+
# run after this one, so resume it explicitly. Front-office controllers don't
|
|
32
|
+
# include the concern and have no session to resume.
|
|
33
|
+
resume_session if respond_to?(:resume_session, true) && Current.session.nil?
|
|
34
|
+
|
|
35
|
+
Current.user&.id
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Defaults for `users.create` and `users.new` (see https://github.com/kaspth/oaken): a unique email and
|
|
2
|
+
# a password passing Trek's validations, so a seed or a spec only sets what it cares about. Specs
|
|
3
|
+
# sign in with `users.password`.
|
|
4
|
+
def users.password = "correct horse battery staple"
|
|
5
|
+
|
|
6
|
+
users.defaults email: -> { "user-#{SecureRandom.hex(4)}@example.com" }, password: users.password
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# A small page tree for specs, loaded with `seed "cases/page_tree"`: the home page (the tree's root,
|
|
2
|
+
# which has no slug), a section, and a page inside it.
|
|
3
|
+
home = pages.create :home, key: "root", title: "Home"
|
|
4
|
+
company = pages.create :company, title: "Company", parent: home
|
|
5
|
+
pages.create :team, title: "Our team", parent: company
|
|
@@ -4,7 +4,8 @@ class TrekUsers < <%= migration_class_name %>
|
|
|
4
4
|
add_column :<%= tableized_model_class %>, :theme, :integer, default: 0, null: false
|
|
5
5
|
add_column :<%= tableized_model_class %>, :locale, :string
|
|
6
6
|
add_column :<%= tableized_model_class %>, :role, :integer, default: 1, null: false
|
|
7
|
-
|
|
7
|
+
# The table is created empty by the trek_create_users migration, so no default is needed.
|
|
8
|
+
add_column :<%= tableized_model_class %>, :password_digest, :string, null: false # rubocop:disable Rails/NotNullColumn
|
|
8
9
|
add_column :<%= tableized_model_class %>, :last_activity_at, :datetime
|
|
9
10
|
end
|
|
10
11
|
end
|
|
@@ -6,7 +6,6 @@ class Tag < ApplicationRecord
|
|
|
6
6
|
optional: true
|
|
7
7
|
|
|
8
8
|
has_many :taggings, dependent: :destroy
|
|
9
|
-
has_many :taggables, through: :taggings
|
|
10
9
|
|
|
11
10
|
validates :name, :key, presence: true
|
|
12
11
|
validates :key, uniqueness: { scope: :category }
|
|
@@ -19,6 +18,12 @@ class Tag < ApplicationRecord
|
|
|
19
18
|
scope :in_categories, ->(keys) { TagCategory.where(key: keys).ordered.map(&:tags).reduce(:+) }
|
|
20
19
|
scope :in_category, ->(key) { TagCategory.find_by(key:)&.tags }
|
|
21
20
|
|
|
21
|
+
# The tagged records, of any model. Not a `has_many :through`: Rails can't go through the
|
|
22
|
+
# polymorphic `Tagging#taggable`.
|
|
23
|
+
def taggables
|
|
24
|
+
taggings.includes(:taggable).map(&:taggable)
|
|
25
|
+
end
|
|
26
|
+
|
|
22
27
|
def to_s
|
|
23
28
|
name
|
|
24
29
|
end
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
require "rails_helper"
|
|
2
|
+
|
|
3
|
+
RSpec.describe Page do
|
|
4
|
+
before { seed "cases/page_tree" }
|
|
5
|
+
|
|
6
|
+
it "slugs the title" do
|
|
7
|
+
expect(pages.team.slug).to eq("our-team")
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
it "builds the full path from the slugs of its ancestors, root excluded" do
|
|
11
|
+
expect(pages.team.full_path).to eq("company/our-team")
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it "is found at its full path" do
|
|
15
|
+
expect(described_class.find_at_full_path("company/our-team")).to eq(pages.team)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it "keeps its former path when moved, so old URLs can be redirected" do
|
|
19
|
+
team = pages.team
|
|
20
|
+
team.update!(parent: pages.home)
|
|
21
|
+
|
|
22
|
+
expect(team.reload.paths.map(&:path)).to eq(["our-team", "company/our-team"])
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
require "rails_helper"
|
|
2
|
+
|
|
3
|
+
RSpec.describe User do
|
|
4
|
+
subject(:user) { users.new }
|
|
5
|
+
|
|
6
|
+
it "is valid with an email and a password" do
|
|
7
|
+
expect(user).to be_valid
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
describe "validations" do
|
|
11
|
+
it "requires an email" do
|
|
12
|
+
user.email = nil
|
|
13
|
+
|
|
14
|
+
expect(user).to be_invalid
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it "requires a well-formed email" do
|
|
18
|
+
user.email = "not an email"
|
|
19
|
+
|
|
20
|
+
expect(user).to be_invalid
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it "requires a unique email" do
|
|
24
|
+
users.create(email: user.email)
|
|
25
|
+
|
|
26
|
+
expect(user).to be_invalid
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it "requires a password of at least 8 characters on creation" do
|
|
30
|
+
user.password = "short"
|
|
31
|
+
|
|
32
|
+
expect(user).to be_invalid
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it "requires a password of at least 8 characters when changing it" do
|
|
36
|
+
user = users.create
|
|
37
|
+
user.password = "short"
|
|
38
|
+
|
|
39
|
+
expect(user).to be_invalid
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it "does not require the password when changing something else" do
|
|
43
|
+
user = described_class.find(users.create.id)
|
|
44
|
+
user.name = "Ada"
|
|
45
|
+
|
|
46
|
+
expect(user).to be_valid
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
describe "roles" do
|
|
51
|
+
it "defaults to guest" do
|
|
52
|
+
expect(user.role).to eq("guest")
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it "treats admins and editors as privileged" do
|
|
56
|
+
expect(%w[admin editor guest].map { |role| users.new(role:).privileged? }).to eq([true, true, false])
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it "scopes privileged users" do
|
|
60
|
+
admin, editor = users.create(role: :admin), users.create(role: :editor)
|
|
61
|
+
users.create(role: :reader)
|
|
62
|
+
|
|
63
|
+
expect(described_class.privileged).to contain_exactly(admin, editor)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
describe "#to_s" do
|
|
68
|
+
it "reads the name" do
|
|
69
|
+
expect(users.new(name: "Ada").to_s).to eq("Ada")
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
it "falls back to the email" do
|
|
73
|
+
expect(users.new(email: "ada@example.com").to_s).to eq("ada@example.com")
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
require "rails_helper"
|
|
2
|
+
|
|
3
|
+
RSpec.describe Admin::ExternalLinkPolicy do
|
|
4
|
+
before { ExternalLink.create!(url: "https://example.com") }
|
|
5
|
+
|
|
6
|
+
it "shows privileged users every external link" do
|
|
7
|
+
policy = described_class.new(user: users.new(role: :editor))
|
|
8
|
+
|
|
9
|
+
expect(policy.apply_scope(ExternalLink.all, type: :active_record_relation)).to match_array(ExternalLink.all)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it "shows other roles no external link" do
|
|
13
|
+
policy = described_class.new(user: users.new(role: :reader))
|
|
14
|
+
|
|
15
|
+
expect(policy.apply_scope(ExternalLink.all, type: :active_record_relation)).to be_empty
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
require "rails_helper"
|
|
2
|
+
|
|
3
|
+
RSpec.describe Admin::FragmentPolicy do
|
|
4
|
+
def policy(role, record = Fragment)
|
|
5
|
+
described_class.new(record, user: users.new(role:))
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
%w[admin editor].each do |role|
|
|
9
|
+
it "lets #{role}s list, create, edit and delete fragments" do
|
|
10
|
+
expect(%i[index? create? update? destroy?].map { |rule| policy(role).apply(rule) }).to all(be(true))
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
%w[user reader guest].each do |role|
|
|
15
|
+
it "keeps #{role}s away from fragments" do
|
|
16
|
+
expect(%i[index? create? update? destroy?].map { |rule| policy(role).apply(rule) }).to all(be(false))
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it "lets privileged users edit a fragment" do
|
|
21
|
+
expect(policy("editor").permitted_attributes).to contain_exactly(:content, :key, :namespace, :title)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it "lets other roles edit nothing" do
|
|
25
|
+
expect(policy("reader").permitted_attributes).to be_empty
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "shows other roles no fragment" do
|
|
29
|
+
Fragment.create!(key: "footer", namespace: "layout", title: "Footer")
|
|
30
|
+
|
|
31
|
+
expect(policy("reader").apply_scope(Fragment.all, type: :active_record_relation)).to be_empty
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
require "rails_helper"
|
|
2
|
+
|
|
3
|
+
RSpec.describe Admin::PagePolicy do
|
|
4
|
+
def policy(role, record = Page)
|
|
5
|
+
described_class.new(record, user: users.new(role:))
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
%w[admin editor].each do |role|
|
|
9
|
+
it "lets #{role}s list, create, edit and delete pages" do
|
|
10
|
+
expect(%i[index? create? update? destroy?].map { |rule| policy(role).apply(rule) }).to all(be(true))
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
%w[user reader guest].each do |role|
|
|
15
|
+
it "keeps #{role}s away from pages" do
|
|
16
|
+
expect(%i[index? create? update? destroy?].map { |rule| policy(role).apply(rule) }).to all(be(false))
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it "lets privileged users edit a page's content" do
|
|
21
|
+
expect(policy("editor").permitted_attributes).to include(:title, :slug, :description, :content)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it "lets other roles edit nothing" do
|
|
25
|
+
expect(policy("reader").permitted_attributes).to be_empty
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "shows privileged users every page" do
|
|
29
|
+
seed "cases/page_tree"
|
|
30
|
+
|
|
31
|
+
expect(policy("editor").apply_scope(Page.all, type: :active_record_relation)).to match_array(Page.all)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it "shows other roles no page" do
|
|
35
|
+
seed "cases/page_tree"
|
|
36
|
+
|
|
37
|
+
expect(policy("reader").apply_scope(Page.all, type: :active_record_relation)).to be_empty
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
require "rails_helper"
|
|
2
|
+
|
|
3
|
+
RSpec.describe Admin::TagCategoryPolicy do
|
|
4
|
+
let(:category) { TagCategory.create!(name: "Topics", key: "topics") }
|
|
5
|
+
|
|
6
|
+
def policy(role, record = TagCategory)
|
|
7
|
+
described_class.new(record, user: users.new(role:))
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
%w[admin editor].each do |role|
|
|
11
|
+
it "lets #{role}s list, create and edit tag categories" do
|
|
12
|
+
expect(%i[index? create? update?].map { |rule| policy(role).apply(rule) }).to all(be(true))
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
%w[user reader guest].each do |role|
|
|
17
|
+
it "keeps #{role}s away from tag categories" do
|
|
18
|
+
expect(%i[index? create? update?].map { |rule| policy(role).apply(rule) }).to all(be(false))
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it "lets privileged users delete an empty category" do
|
|
23
|
+
expect(policy("editor", category).apply(:destroy?)).to be(true)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it "keeps a category with tags from being deleted" do
|
|
27
|
+
Tag.create!(name: "News", key: "news", category:)
|
|
28
|
+
|
|
29
|
+
expect(policy("admin", category.reload).apply(:destroy?)).to be(false)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
require "rails_helper"
|
|
2
|
+
|
|
3
|
+
RSpec.describe Admin::TagPolicy do
|
|
4
|
+
let(:tag) { Tag.create!(name: "News", key: "news") }
|
|
5
|
+
|
|
6
|
+
def policy(role, record = Tag)
|
|
7
|
+
described_class.new(record, user: users.new(role:))
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
%w[admin editor].each do |role|
|
|
11
|
+
it "lets #{role}s list, create and edit tags" do
|
|
12
|
+
expect(%i[index? create? update?].map { |rule| policy(role).apply(rule) }).to all(be(true))
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
%w[user reader guest].each do |role|
|
|
17
|
+
it "keeps #{role}s away from tags" do
|
|
18
|
+
expect(%i[index? create? update?].map { |rule| policy(role).apply(rule) }).to all(be(false))
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it "lets privileged users delete an unused tag" do
|
|
23
|
+
expect(policy("editor", tag).apply(:destroy?)).to be(true)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it "keeps a tag in use from being deleted" do
|
|
27
|
+
allow(tag).to receive(:taggings).and_return([Tagging.new])
|
|
28
|
+
|
|
29
|
+
expect(policy("admin", tag).apply(:destroy?)).to be(false)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
require "rails_helper"
|
|
2
|
+
|
|
3
|
+
RSpec.describe Admin::UserPolicy do
|
|
4
|
+
let(:admin) { users.create(role: :admin) }
|
|
5
|
+
let(:editor) { users.create(role: :editor) }
|
|
6
|
+
|
|
7
|
+
def policy(user, record = User)
|
|
8
|
+
described_class.new(record, user:)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it "lets admins list, create and delete users" do
|
|
12
|
+
expect(%i[index? create? destroy?].map { |rule| policy(admin).apply(rule) }).to all(be(true))
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it "lets admins edit any user" do
|
|
16
|
+
expect(policy(admin, editor).apply(:update?)).to be(true)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "keeps editors away from the users list" do
|
|
20
|
+
expect(%i[index? create? destroy?].map { |rule| policy(editor).apply(rule) }).to all(be(false))
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it "lets editors see and edit themselves" do
|
|
24
|
+
expect(%i[show? update?].map { |rule| policy(editor, editor).apply(rule) }).to all(be(true))
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it "keeps editors away from other users" do
|
|
28
|
+
expect(%i[show? update?].map { |rule| policy(editor, admin).apply(rule) }).to all(be(false))
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it "lets admins change a user's role" do
|
|
32
|
+
expect(policy(admin, editor).permitted_attributes).to include(:role)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it "lets admins invite the users they create" do
|
|
36
|
+
expect(policy(admin, User).permitted_attributes).to include(:send_invite)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it "lets editors change their own details but not their role" do
|
|
40
|
+
expect(policy(editor, editor).permitted_attributes).to contain_exactly(:name, :email, :locale, :theme)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it "lets editors change nothing on another user" do
|
|
44
|
+
expect(policy(editor, admin).permitted_attributes).to be_empty
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it "shows admins the privileged users only" do
|
|
48
|
+
reader = users.create(role: :reader)
|
|
49
|
+
|
|
50
|
+
expect(described_class.new(user: admin).apply_scope(User.all, type: :active_record_relation)).not_to include(reader)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it "shows editors no user" do
|
|
54
|
+
admin
|
|
55
|
+
|
|
56
|
+
expect(described_class.new(user: editor).apply_scope(User.all, type: :active_record_relation)).to be_empty
|
|
57
|
+
end
|
|
58
|
+
end
|