katalyst-koi 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +23 -0
- data/Upgrade.md +6 -0
- data/app/assets/builds/koi/admin.css +1 -0
- data/app/assets/builds/koi/nav_items.css +1 -0
- data/app/assets/config/koi.js +10 -0
- data/app/assets/images/koi/application/chevron-right.svg +10 -0
- data/app/assets/images/koi/application/glyphicons-halflings-white.png +0 -0
- data/app/assets/images/koi/application/glyphicons-halflings.png +0 -0
- data/app/assets/images/koi/application/icon-collapse-down.png +0 -0
- data/app/assets/images/koi/application/icon-collapse-up.png +0 -0
- data/app/assets/images/koi/application/icon-file-doc.png +0 -0
- data/app/assets/images/koi/application/icon-file-img.png +0 -0
- data/app/assets/images/koi/application/icon-file-pdf.png +0 -0
- data/app/assets/images/koi/application/icon-file-ppt.png +0 -0
- data/app/assets/images/koi/application/icon-file-unknown.png +0 -0
- data/app/assets/images/koi/application/icon-file-xls.png +0 -0
- data/app/assets/images/koi/application/icon-file-zip.png +0 -0
- data/app/assets/images/koi/application/icon-form-date-picker.png +0 -0
- data/app/assets/images/koi/application/icon-form-error.png +0 -0
- data/app/assets/images/koi/application/icon-index-sort-ascending.png +0 -0
- data/app/assets/images/koi/application/icon-index-sort-descending.png +0 -0
- data/app/assets/images/koi/application/icon-index-sort.png +0 -0
- data/app/assets/images/koi/application/icon-index-sortable.png +0 -0
- data/app/assets/images/koi/application/icon-menu-cursor.png +0 -0
- data/app/assets/images/koi/application/icon-overlay-add.png +0 -0
- data/app/assets/images/koi/application/icon-overlay-close.png +0 -0
- data/app/assets/images/koi/application/icon-sortable.png +0 -0
- data/app/assets/images/koi/application/jcrop.gif +0 -0
- data/app/assets/images/koi/application/loading.gif +0 -0
- data/app/assets/images/koi/application/select-arrow.svg +3 -0
- data/app/assets/images/koi/application/select_arrow.png +0 -0
- data/app/assets/images/koi/application/sort-ascending.png +0 -0
- data/app/assets/images/koi/application/sort-descending.png +0 -0
- data/app/assets/javascripts/koi/admin.js +4 -0
- data/app/assets/javascripts/koi/controllers/application.js +11 -0
- data/app/assets/javascripts/koi/controllers/document_field_controller.js +26 -0
- data/app/assets/javascripts/koi/controllers/file_field_controller.js +143 -0
- data/app/assets/javascripts/koi/controllers/flash_controller.js +12 -0
- data/app/assets/javascripts/koi/controllers/form_request_submit_controller.js +11 -0
- data/app/assets/javascripts/koi/controllers/image_field_controller.js +24 -0
- data/app/assets/javascripts/koi/controllers/index.js +6 -0
- data/app/assets/javascripts/koi/controllers/index_actions_controller.js +61 -0
- data/app/assets/javascripts/koi/controllers/keyboard_controller.js +149 -0
- data/app/assets/javascripts/koi/controllers/navigation_controller.js +84 -0
- data/app/assets/javascripts/koi/controllers/navigation_toggle_controller.js +7 -0
- data/app/assets/javascripts/koi/controllers/show_hide_controller.js +25 -0
- data/app/assets/javascripts/koi/controllers/sluggable_controller.js +30 -0
- data/app/assets/javascripts/koi/controllers/webauthn_authentication_controller.js +23 -0
- data/app/assets/javascripts/koi/controllers/webauthn_registration_controller.js +30 -0
- data/app/assets/javascripts/koi/utils/transition.js +220 -0
- data/app/assets/stylesheets/koi/admin.scss +27 -0
- data/app/assets/stylesheets/koi/base/_button.scss +122 -0
- data/app/assets/stylesheets/koi/base/_icon.scss +29 -0
- data/app/assets/stylesheets/koi/base/_index.scss +18 -0
- data/app/assets/stylesheets/koi/base/_input.scss +13 -0
- data/app/assets/stylesheets/koi/base/_link.scss +26 -0
- data/app/assets/stylesheets/koi/base/_list.scss +11 -0
- data/app/assets/stylesheets/koi/base/_typography.scss +160 -0
- data/app/assets/stylesheets/koi/components/_actions-group.scss +7 -0
- data/app/assets/stylesheets/koi/components/_image-field.scss +33 -0
- data/app/assets/stylesheets/koi/components/_index-actions.scss +69 -0
- data/app/assets/stylesheets/koi/components/_index-table.scss +91 -0
- data/app/assets/stylesheets/koi/components/_index.scss +6 -0
- data/app/assets/stylesheets/koi/components/_item-table.scss +33 -0
- data/app/assets/stylesheets/koi/components/_pagy.scss +41 -0
- data/app/assets/stylesheets/koi/layouts/_banner.scss +7 -0
- data/app/assets/stylesheets/koi/layouts/_content.scss +40 -0
- data/app/assets/stylesheets/koi/layouts/_flash.scss +41 -0
- data/app/assets/stylesheets/koi/layouts/_header.scss +62 -0
- data/app/assets/stylesheets/koi/layouts/_index.scss +48 -0
- data/app/assets/stylesheets/koi/layouts/_main.scss +23 -0
- data/app/assets/stylesheets/koi/layouts/_navigation.scss +156 -0
- data/app/assets/stylesheets/koi/layouts/_stack.scss +13 -0
- data/app/assets/stylesheets/koi/pages/_index.scss +1 -0
- data/app/assets/stylesheets/koi/pages/_login.scss +40 -0
- data/app/assets/stylesheets/koi/themes/_content.scss +5 -0
- data/app/assets/stylesheets/koi/themes/_govuk.scss +52 -0
- data/app/assets/stylesheets/koi/themes/_index.scss +5 -0
- data/app/assets/stylesheets/koi/themes/_kpop.scss +5 -0
- data/app/assets/stylesheets/koi/themes/_navigation.scss +5 -0
- data/app/assets/stylesheets/koi/themes/_trix.scss +32 -0
- data/app/assets/stylesheets/koi/utils/_breakpoints.scss +13 -0
- data/app/assets/stylesheets/koi/utils/_hide.scss +11 -0
- data/app/assets/stylesheets/koi/utils/_index.scss +2 -0
- data/app/assets/stylesheets/koi/utils/_typography.scss +24 -0
- data/app/components/koi/header/edit_component.rb +58 -0
- data/app/components/koi/header/index_component.rb +23 -0
- data/app/components/koi/header/new_component.rb +40 -0
- data/app/components/koi/header/show_component.rb +51 -0
- data/app/components/koi/header_component.html.erb +16 -0
- data/app/components/koi/header_component.rb +28 -0
- data/app/components/koi/index_table_component.rb +21 -0
- data/app/controllers/admin/admin_users_controller.rb +88 -0
- data/app/controllers/admin/application_controller.rb +9 -0
- data/app/controllers/admin/caches_controller.rb +11 -0
- data/app/controllers/admin/credentials_controller.rb +64 -0
- data/app/controllers/admin/dashboards_controller.rb +7 -0
- data/app/controllers/admin/sessions_controller.rb +78 -0
- data/app/controllers/admin/url_rewrites_controller.rb +87 -0
- data/app/controllers/concerns/koi/controller/has_admin_users.rb +49 -0
- data/app/controllers/concerns/koi/controller/has_webauthn.rb +45 -0
- data/app/controllers/concerns/koi/controller/is_admin_controller.rb +52 -0
- data/app/helpers/katalyst/content/editor/errors.rb +21 -0
- data/app/helpers/katalyst/navigation/editor/errors.rb +21 -0
- data/app/helpers/koi/application_helper.rb +7 -0
- data/app/helpers/koi/date_helper.rb +36 -0
- data/app/helpers/koi/definition_list_helper.rb +92 -0
- data/app/helpers/koi/index_actions_helper.rb +99 -0
- data/app/jobs/koi/application_job.rb +6 -0
- data/app/mailers/koi/application_mailer.rb +8 -0
- data/app/models/admin/credential.rb +14 -0
- data/app/models/admin/user.rb +51 -0
- data/app/models/application_record.rb +5 -0
- data/app/models/concerns/koi/model/archivable.rb +55 -0
- data/app/models/url_rewrite.rb +25 -0
- data/app/views/admin/admin_users/_admin.html+row.erb +4 -0
- data/app/views/admin/admin_users/_authentication.html.erb +15 -0
- data/app/views/admin/admin_users/_fields.html.erb +4 -0
- data/app/views/admin/admin_users/edit.html.erb +11 -0
- data/app/views/admin/admin_users/index.html.erb +9 -0
- data/app/views/admin/admin_users/new.html.erb +11 -0
- data/app/views/admin/admin_users/show.html.erb +22 -0
- data/app/views/admin/credentials/new.html.erb +14 -0
- data/app/views/admin/dashboards/show.html.erb +1 -0
- data/app/views/admin/sessions/new.html.erb +19 -0
- data/app/views/admin/shared/icons/_close.html.erb +8 -0
- data/app/views/admin/shared/icons/_cross.html.erb +3 -0
- data/app/views/admin/shared/icons/_menu.html.erb +3 -0
- data/app/views/admin/shared/icons/_refresh.html.erb +8 -0
- data/app/views/admin/url_rewrites/_form_fields.html.erb +3 -0
- data/app/views/admin/url_rewrites/_url_rewrite.html+row.erb +7 -0
- data/app/views/admin/url_rewrites/edit.html.erb +12 -0
- data/app/views/admin/url_rewrites/index.html.erb +10 -0
- data/app/views/admin/url_rewrites/new.html.erb +11 -0
- data/app/views/admin/url_rewrites/show.html.erb +16 -0
- data/app/views/katalyst/content/asides/_aside.html+form.erb +18 -0
- data/app/views/katalyst/content/columns/_column.html+form.erb +18 -0
- data/app/views/katalyst/content/contents/_content.html+form.erb +20 -0
- data/app/views/katalyst/content/figures/_figure.html+form.erb +17 -0
- data/app/views/katalyst/content/groups/_group.html+form.erb +18 -0
- data/app/views/katalyst/content/items/_item.html+form.erb +18 -0
- data/app/views/katalyst/content/sections/_section.html+form.erb +18 -0
- data/app/views/katalyst/navigation/items/_button.html.erb +15 -0
- data/app/views/katalyst/navigation/items/_heading.html.erb +11 -0
- data/app/views/katalyst/navigation/items/_link.html.erb +13 -0
- data/app/views/katalyst/navigation/menus/edit.html.erb +12 -0
- data/app/views/katalyst/navigation/menus/new.html.erb +9 -0
- data/app/views/katalyst/navigation/menus/show.html.erb +18 -0
- data/app/views/layouts/koi/_environment.html.erb +4 -0
- data/app/views/layouts/koi/_flash.html.erb +8 -0
- data/app/views/layouts/koi/_header.html.erb +11 -0
- data/app/views/layouts/koi/_navigation.html.erb +13 -0
- data/app/views/layouts/koi/_navigation_collapse.html.erb +3 -0
- data/app/views/layouts/koi/_navigation_header.html.erb +6 -0
- data/app/views/layouts/koi/_navigation_item.html.erb +12 -0
- data/app/views/layouts/koi/application.html.erb +59 -0
- data/app/views/layouts/koi/login.html.erb +29 -0
- data/config/importmap.rb +9 -0
- data/config/initializers/flipper.rb +13 -0
- data/config/initializers/pagy.rb +1 -0
- data/config/initializers/time_formats.rb +5 -0
- data/config/locales/koi.en.yml +18 -0
- data/config/locales/pagy.en.yml +6 -0
- data/config/routes.rb +25 -0
- data/db/migrate/20120220130849_devise_create_admins.rb +56 -0
- data/db/migrate/20130509235316_add_url_rewriter.rb +13 -0
- data/db/migrate/20230213053854_convert_devise_admins_to_rails.rb +7 -0
- data/db/migrate/20230412023411_create_admin_user_credentials.rb +20 -0
- data/db/migrate/20230531063707_update_admin_users.rb +37 -0
- data/db/migrate/20230602033610_add_archived_to_admin_users.rb +7 -0
- data/db/seeds.rb +9 -0
- data/lib/generators/koi/active_record/active_record_generator.rb +43 -0
- data/lib/generators/koi/admin/USAGE +8 -0
- data/lib/generators/koi/admin/admin_generator.rb +20 -0
- data/lib/generators/koi/admin_controller/USAGE +17 -0
- data/lib/generators/koi/admin_controller/admin_controller_generator.rb +51 -0
- data/lib/generators/koi/admin_controller/templates/controller.rb.tt +81 -0
- data/lib/generators/koi/admin_controller/templates/controller_spec.rb.tt +135 -0
- data/lib/generators/koi/admin_route/admin_route_generator.rb +62 -0
- data/lib/generators/koi/admin_views/USAGE +12 -0
- data/lib/generators/koi/admin_views/admin_views_generator.rb +54 -0
- data/lib/generators/koi/admin_views/templates/_fields.html.erb.tt +3 -0
- data/lib/generators/koi/admin_views/templates/_record.html+row.erb.tt +10 -0
- data/lib/generators/koi/admin_views/templates/edit.html.erb.tt +12 -0
- data/lib/generators/koi/admin_views/templates/index.html.erb.tt +7 -0
- data/lib/generators/koi/admin_views/templates/new.html.erb.tt +11 -0
- data/lib/generators/koi/admin_views/templates/show.html.erb.tt +18 -0
- data/lib/govuk_design_system_formbuilder/concerns/file_element.rb +115 -0
- data/lib/govuk_design_system_formbuilder/elements/document.rb +59 -0
- data/lib/govuk_design_system_formbuilder/elements/image.rb +86 -0
- data/lib/katalyst/koi.rb +3 -0
- data/lib/koi/caching.rb +15 -0
- data/lib/koi/config.rb +11 -0
- data/lib/koi/engine.rb +40 -0
- data/lib/koi/form_builder.rb +76 -0
- data/lib/koi/menu/builder.rb +68 -0
- data/lib/koi/menu.rb +46 -0
- data/lib/koi/middleware/url_redirect.rb +44 -0
- data/lib/koi/release.rb +52 -0
- data/lib/koi/version.rb +5 -0
- data/lib/koi.rb +37 -0
- data/spec/factories/admins.rb +9 -0
- data/spec/factories/url_rewrites.rb +9 -0
- metadata +430 -0
@@ -0,0 +1 @@
|
|
1
|
+
Pagy::I18n.load({ locale: "en", filepath: Koi::Engine.root.join("config/locales/pagy.en.yml") })
|
@@ -0,0 +1,18 @@
|
|
1
|
+
en:
|
2
|
+
time:
|
3
|
+
formats:
|
4
|
+
default: "%Y-%m-%dT%H:%M:%S.%L%z"
|
5
|
+
display: "%e %B %Y, %l:%M%P"
|
6
|
+
date:
|
7
|
+
formats:
|
8
|
+
default: "%Y-%m-%d"
|
9
|
+
display: "%e %B %Y"
|
10
|
+
koi:
|
11
|
+
labels:
|
12
|
+
new: New
|
13
|
+
search: Search
|
14
|
+
helpers:
|
15
|
+
hint:
|
16
|
+
default:
|
17
|
+
document: Must be less than %{max_size}
|
18
|
+
image: Must be less than %{max_size}
|
data/config/routes.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Rails.application.routes.draw do
|
4
|
+
namespace :admin do
|
5
|
+
resource :session, only: %i[new create destroy]
|
6
|
+
|
7
|
+
resources :url_rewrites
|
8
|
+
resources :admin_users do
|
9
|
+
resources :credentials, only: %i[new create destroy]
|
10
|
+
end
|
11
|
+
|
12
|
+
resource :cache, only: %i[destroy]
|
13
|
+
resource :dashboard, only: %i[show]
|
14
|
+
|
15
|
+
root to: redirect("admin/dashboard")
|
16
|
+
end
|
17
|
+
|
18
|
+
scope :admin do
|
19
|
+
constraints ->(req) { req.session[:admin_user_id].present? } do
|
20
|
+
mount Katalyst::Content::Engine, at: "content"
|
21
|
+
mount Katalyst::Navigation::Engine, at: "navigation"
|
22
|
+
mount Flipper::UI.app(Flipper) => "flipper" if Object.const_defined?("Flipper::UI")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class DeviseCreateAdmins < ActiveRecord::Migration[4.2]
|
4
|
+
# rubocop:disable Metrics/BlockLength
|
5
|
+
def change
|
6
|
+
create_table(:admins) do |t|
|
7
|
+
## Database authenticatable
|
8
|
+
t.string :email, null: false, default: ""
|
9
|
+
t.string :encrypted_password, null: false, default: ""
|
10
|
+
|
11
|
+
## Recoverable
|
12
|
+
t.string :reset_password_token
|
13
|
+
t.datetime :reset_password_sent_at
|
14
|
+
|
15
|
+
## Rememberable
|
16
|
+
t.datetime :remember_created_at
|
17
|
+
|
18
|
+
## Trackable
|
19
|
+
t.integer :sign_in_count, default: 0
|
20
|
+
t.datetime :current_sign_in_at
|
21
|
+
t.datetime :last_sign_in_at
|
22
|
+
t.string :current_sign_in_ip
|
23
|
+
t.string :last_sign_in_ip
|
24
|
+
|
25
|
+
## Encryptable
|
26
|
+
# t.string :password_salt
|
27
|
+
|
28
|
+
## Confirmable
|
29
|
+
# t.string :confirmation_token
|
30
|
+
# t.datetime :confirmed_at
|
31
|
+
# t.datetime :confirmation_sent_at
|
32
|
+
# t.string :unconfirmed_email # Only if using reconfirmable
|
33
|
+
|
34
|
+
## Lockable
|
35
|
+
# t.integer :failed_attempts, :default => 0 # Only if lock strategy is :failed_attempts
|
36
|
+
# t.string :unlock_token # Only if unlock strategy is :email or :both
|
37
|
+
t.datetime :locked_at
|
38
|
+
|
39
|
+
## Token authenticatable
|
40
|
+
# t.string :authentication_token
|
41
|
+
|
42
|
+
t.string :first_name
|
43
|
+
t.string :last_name
|
44
|
+
t.string :role
|
45
|
+
|
46
|
+
t.timestamps
|
47
|
+
end
|
48
|
+
|
49
|
+
add_index :admins, :email, unique: true
|
50
|
+
add_index :admins, :reset_password_token, unique: true
|
51
|
+
# add_index :koi_admins, :confirmation_token, :unique => true
|
52
|
+
# add_index :koi_admins, :unlock_token, :unique => true
|
53
|
+
# add_index :koi_admins, :authentication_token, :unique => true
|
54
|
+
end
|
55
|
+
# rubocop:enable Metrics/BlockLength
|
56
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class CreateAdminUserCredentials < ActiveRecord::Migration[7.0]
|
4
|
+
def change
|
5
|
+
create_table :admin_credentials do |t|
|
6
|
+
t.string "external_id", index: { unique: true }
|
7
|
+
t.references :admin, null: false, foreign_key: true
|
8
|
+
|
9
|
+
t.string "public_key"
|
10
|
+
t.string "nickname"
|
11
|
+
t.bigint "sign_count", default: 0, null: false
|
12
|
+
|
13
|
+
t.timestamps
|
14
|
+
end
|
15
|
+
|
16
|
+
change_table :admins do |t|
|
17
|
+
t.column :webauthn_id, :string
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
class UpdateAdminUsers < ActiveRecord::Migration[7.0]
|
2
|
+
class Admin < ActiveRecord::Base; end
|
3
|
+
|
4
|
+
def up
|
5
|
+
add_column :admins, :name, :string
|
6
|
+
|
7
|
+
Admin.all.each do |admin|
|
8
|
+
admin.name = "#{admin.first_name} #{admin.last_name}"
|
9
|
+
admin.save!
|
10
|
+
end
|
11
|
+
|
12
|
+
remove_column :admins, :first_name, :string
|
13
|
+
remove_column :admins, :last_name, :string
|
14
|
+
remove_column :admins, :reset_password_token, :string, index: { unique: true }
|
15
|
+
remove_column :admins, :reset_password_sent_at, :datetime
|
16
|
+
remove_column :admins, :remember_created_at, :datetime
|
17
|
+
remove_column :admins, :locked_at, :datetime
|
18
|
+
remove_column :admins, :role, :string
|
19
|
+
end
|
20
|
+
|
21
|
+
def down
|
22
|
+
add_column :admins, :first_name, :string
|
23
|
+
add_column :admins, :last_name, :string
|
24
|
+
add_column :admins, :reset_password_token, :string, index: { unique: true }
|
25
|
+
add_column :admins, :reset_password_sent_at, :datetime
|
26
|
+
add_column :admins, :remember_created_at, :datetime
|
27
|
+
add_column :admins, :locked_at, :datetime
|
28
|
+
add_column :admins, :role, :string
|
29
|
+
|
30
|
+
Admin.all.each do |admin|
|
31
|
+
admin.first_name, admin.last_name = admin.name.split(' ', 2)
|
32
|
+
admin.save!
|
33
|
+
end
|
34
|
+
|
35
|
+
remove_column :admins, :name, :string
|
36
|
+
end
|
37
|
+
end
|
data/db/seeds.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Create a default admin user (only in development)
|
4
|
+
if Rails.env.development? && !ENV["CI"]
|
5
|
+
Admin::User.create_with(
|
6
|
+
name: `id -F`.strip,
|
7
|
+
password: "password",
|
8
|
+
).find_or_create_by(email: "#{ENV['USER']}@katalyst.com.au")
|
9
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails/generators/active_record/model/model_generator"
|
4
|
+
|
5
|
+
module Koi
|
6
|
+
class ActiveRecordGenerator < ActiveRecord::Generators::ModelGenerator
|
7
|
+
source_root ActiveRecord::Generators::ModelGenerator.source_root
|
8
|
+
|
9
|
+
def admin_search
|
10
|
+
"PgSearch::Model".safe_constantize ? pg_search : sql_search
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def pg_search
|
16
|
+
insert_into_file "app/models/#{file_name}.rb", after: "class #{class_name} < ApplicationRecord\n" do
|
17
|
+
<<~RUBY
|
18
|
+
include PgSearch::Model
|
19
|
+
RUBY
|
20
|
+
end
|
21
|
+
|
22
|
+
insert_into_file "app/models/#{file_name}.rb", before: "end\n" do
|
23
|
+
<<~RUBY
|
24
|
+
pg_search :admin_search, against: %i[#{search_fields.join(' ')}], using: { tsearch: { prefix: true } }
|
25
|
+
RUBY
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def sql_search
|
30
|
+
insert_into_file "app/models/#{file_name}.rb", before: "end\n" do
|
31
|
+
<<~RUBY
|
32
|
+
scope :admin_search, ->(query) do
|
33
|
+
where("#{search_fields.map { |f| "#{f} LIKE :query" }.join(' OR ')}", query: "%\#{query}%")
|
34
|
+
end
|
35
|
+
RUBY
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def search_fields
|
40
|
+
attributes.select { |attr| attr.type == :string }.map(&:name)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails/generators/rails/scaffold/scaffold_generator"
|
4
|
+
|
5
|
+
module Koi
|
6
|
+
class AdminGenerator < Rails::Generators::ScaffoldGenerator
|
7
|
+
# Replace the default model generator with our own
|
8
|
+
remove_hook_for(:orm)
|
9
|
+
hook_for(:orm, in: :koi, as: :admin, default: true)
|
10
|
+
|
11
|
+
# Disable default controller generation as we do not want to generate public
|
12
|
+
# controllers by default
|
13
|
+
remove_hook_for(:scaffold_controller)
|
14
|
+
remove_hook_for(:resource_route)
|
15
|
+
|
16
|
+
hook_for :admin_controller, in: :koi, as: :admin, type: :boolean, default: true
|
17
|
+
|
18
|
+
Rails::Generators::ModelGenerator.hook_for :admin_search, type: :boolean, default: true
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
Description:
|
2
|
+
Generates a Koi admin crud for the given model
|
3
|
+
|
4
|
+
Example:
|
5
|
+
bin/rails generate koi:admin_controller Test
|
6
|
+
|
7
|
+
This will create:
|
8
|
+
app/controllers/admin/tests_controller.rb
|
9
|
+
app/views/admin/tests/_form_fields.html.erb
|
10
|
+
app/views/admin/tests/edit.html.erb
|
11
|
+
app/views/admin/tests/index.html.erb
|
12
|
+
app/views/admin/tests/new.html.erb
|
13
|
+
app/views/admin/tests/show.html.erb
|
14
|
+
|
15
|
+
This will update:
|
16
|
+
config/routes/admin.rb to include `resources :tests` to the :admin namespace
|
17
|
+
config/initializers/koi.rb to include "Tests => /admin/tests" in the Modules section
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails/generators/named_base"
|
4
|
+
require "rails/generators/resource_helpers"
|
5
|
+
|
6
|
+
module Koi
|
7
|
+
class AdminControllerGenerator < Rails::Generators::NamedBase
|
8
|
+
include Rails::Generators::ResourceHelpers
|
9
|
+
|
10
|
+
source_root File.expand_path("templates", __dir__)
|
11
|
+
|
12
|
+
check_class_collision prefix: "Admin::", suffix: "Controller"
|
13
|
+
|
14
|
+
argument :attributes, type: :array, default: [], banner: "field:type field:type"
|
15
|
+
|
16
|
+
def create_controller_files
|
17
|
+
template("controller.rb",
|
18
|
+
File.join("app/controllers/admin",
|
19
|
+
controller_class_path,
|
20
|
+
"#{controller_file_name}_controller.rb"))
|
21
|
+
end
|
22
|
+
|
23
|
+
def create_spec_files
|
24
|
+
template("controller_spec.rb",
|
25
|
+
File.join("spec/requests/admin",
|
26
|
+
controller_class_path,
|
27
|
+
"#{controller_file_name}_controller_spec.rb"))
|
28
|
+
end
|
29
|
+
|
30
|
+
hook_for(:admin_views, in: :koi, type: :boolean, default: true)
|
31
|
+
hook_for(:admin_route, in: :koi, type: :boolean, default: true)
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def permitted_params
|
36
|
+
attachments, others = attributes_names.partition { |name| attachments?(name) }
|
37
|
+
params = others.map { |name| ":#{name}" }
|
38
|
+
params += attachments.map { |name| "#{name}: []" }
|
39
|
+
params.join(", ")
|
40
|
+
end
|
41
|
+
|
42
|
+
def attachments?(name)
|
43
|
+
attribute = attributes.find { |attr| attr.name == name }
|
44
|
+
attribute&.attachments?
|
45
|
+
end
|
46
|
+
|
47
|
+
def search_attribute
|
48
|
+
attributes.find { |attr| attr.type == :string }&.name
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Admin
|
4
|
+
class <%= controller_class_name %>Controller < ApplicationController
|
5
|
+
|
6
|
+
before_action :set_<%= singular_name %>, only: %i[show edit update destroy]
|
7
|
+
|
8
|
+
def index
|
9
|
+
collection = Collection.new.with_params(params).apply(::<%= class_name %>.strict_loading.all)
|
10
|
+
table = Koi::IndexTableComponent.new(collection:)
|
11
|
+
|
12
|
+
respond_to do |format|
|
13
|
+
format.turbo_stream { render table } if self_referred?
|
14
|
+
format.html { render :index, locals: { table:, collection: } }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def show
|
19
|
+
render locals: { <%= singular_name %>: @<%= singular_name %> }
|
20
|
+
end
|
21
|
+
|
22
|
+
def new
|
23
|
+
render locals: { <%= singular_name %>: ::<%= class_name %>.new }
|
24
|
+
end
|
25
|
+
|
26
|
+
def edit
|
27
|
+
render locals: { <%= singular_name %>: @<%= singular_name %> }
|
28
|
+
end
|
29
|
+
|
30
|
+
def create
|
31
|
+
@<%= singular_name %> = ::<%= class_name %>.new(<%= singular_name %>_params)
|
32
|
+
|
33
|
+
if @<%= singular_name %>.save
|
34
|
+
redirect_to [:admin, @<%= singular_name %>]
|
35
|
+
else
|
36
|
+
render :new, locals: { <%= singular_name %>: @<%= singular_name %> }, status: :unprocessable_entity
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def update
|
41
|
+
if @<%= singular_name %>.update(<%= singular_name %>_params)
|
42
|
+
redirect_to action: :show
|
43
|
+
else
|
44
|
+
render :edit, locals: { <%= singular_name %>: @<%= singular_name %> }, status: :unprocessable_entity
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def destroy
|
49
|
+
@<%= singular_name %>.destroy
|
50
|
+
|
51
|
+
redirect_to action: :index
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
# Only allow a list of trusted parameters through.
|
57
|
+
def <%= "#{singular_table_name}_params" %>
|
58
|
+
<%- if attributes_names.empty? -%>
|
59
|
+
params.fetch(:<%= singular_table_name %>, {})
|
60
|
+
<%- else -%>
|
61
|
+
params.require(:<%= singular_table_name %>).permit(<%= permitted_params %>)
|
62
|
+
<%- end -%>
|
63
|
+
end
|
64
|
+
|
65
|
+
# Use callbacks to share common setup or constraints between actions.
|
66
|
+
def set_<%= singular_table_name %>
|
67
|
+
@<%= singular_table_name %> = ::<%= class_name %>.find(params[:id])
|
68
|
+
end
|
69
|
+
|
70
|
+
class Collection < Katalyst::Tables::Collection::Base
|
71
|
+
attribute :search, :string
|
72
|
+
|
73
|
+
config.sorting = :<%= search_attribute %>
|
74
|
+
config.paginate = true
|
75
|
+
|
76
|
+
def filter
|
77
|
+
self.items = items.admin_search(search) if search.present?
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails_helper"
|
4
|
+
|
5
|
+
RSpec.describe Admin::<%= controller_class_name %>Controller do
|
6
|
+
let(:admin) { create(:admin) }
|
7
|
+
let(:model) { create(:<%= singular_name %>) }
|
8
|
+
|
9
|
+
include_context "with admin session"
|
10
|
+
|
11
|
+
describe "GET /admin/<%= plural_name %>" do
|
12
|
+
let(:action) { get polymorphic_path([:admin, <%= class_name %>]) }
|
13
|
+
|
14
|
+
it_behaves_like "requires admin"
|
15
|
+
|
16
|
+
it "renders successfully" do
|
17
|
+
action
|
18
|
+
expect(response).to have_http_status(:success)
|
19
|
+
end
|
20
|
+
|
21
|
+
context "with a large collection" do
|
22
|
+
before { create_list(:<%= singular_name %>, 25) }
|
23
|
+
|
24
|
+
it "paginates the collection" do
|
25
|
+
action
|
26
|
+
expect(response.body).to have_selector("tbody tr", count: 20)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context "with sort parameter" do
|
31
|
+
let(:action) { get polymorphic_path([:admin, <%= class_name %>], sort: "<%= search_attribute %> desc") }
|
32
|
+
|
33
|
+
before do
|
34
|
+
create(:<%= singular_name %>, <%= search_attribute %>: "first")
|
35
|
+
create(:<%= singular_name %>, <%= search_attribute %>: "second")
|
36
|
+
end
|
37
|
+
|
38
|
+
it "finds first in second place" do
|
39
|
+
action
|
40
|
+
expect(response.body).to have_selector("tbody tr + tr td", text: "first")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context "with search parameter" do
|
45
|
+
let(:action) { get polymorphic_path([:admin, <%= class_name %>], search: "first") }
|
46
|
+
|
47
|
+
before do
|
48
|
+
create(:<%= singular_name %>, <%= search_attribute %>: "first")
|
49
|
+
create(:<%= singular_name %>, <%= search_attribute %>: "second")
|
50
|
+
end
|
51
|
+
|
52
|
+
it "finds the needle" do
|
53
|
+
action
|
54
|
+
expect(response.body).to have_selector("table td", text: "first")
|
55
|
+
end
|
56
|
+
|
57
|
+
it "removes the chaff" do
|
58
|
+
action
|
59
|
+
expect(response.body).not_to have_selector("table td", text: "second")
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe "GET /admin/<%= plural_name %>/new" do
|
65
|
+
let(:action) { get new_polymorphic_path([:admin, <%= class_name %>]) }
|
66
|
+
|
67
|
+
it_behaves_like "requires admin"
|
68
|
+
|
69
|
+
it "renders successfully" do
|
70
|
+
action
|
71
|
+
expect(response).to have_http_status(:success)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe "POST /admin/<%= plural_name %>" do
|
76
|
+
let(:action) { post polymorphic_path([:admin, <%= class_name %>]), params: { <%= singular_name %>: params } }
|
77
|
+
let(:params) { attributes_for(:<%= singular_name %>) }
|
78
|
+
|
79
|
+
it_behaves_like "requires admin"
|
80
|
+
|
81
|
+
it "renders successfully" do
|
82
|
+
action
|
83
|
+
expect(response).to redirect_to([:admin, assigns(:<%= singular_name %>)])
|
84
|
+
end
|
85
|
+
|
86
|
+
it "creates a <%= singular_name %>" do
|
87
|
+
expect { action }.to change(<%= class_name %>, :count).by(1)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe "GET /admin/<%= plural_name %>/:id" do
|
92
|
+
let(:action) { get polymorphic_path([:admin, model]) }
|
93
|
+
|
94
|
+
it_behaves_like "requires admin"
|
95
|
+
|
96
|
+
it "renders successfully" do
|
97
|
+
action
|
98
|
+
expect(response).to have_http_status(:success)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
describe "GET /admin/<%= plural_name %>/:id/edit" do
|
103
|
+
let(:action) { get edit_polymorphic_path([:admin, model]) }
|
104
|
+
|
105
|
+
it_behaves_like "requires admin"
|
106
|
+
|
107
|
+
it "renders successfully" do
|
108
|
+
action
|
109
|
+
expect(response).to have_http_status(:success)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
describe "PATCH /admin/<%= plural_name %>/:id" do
|
114
|
+
let(:action) { patch polymorphic_path([:admin, model]), params: { <%= singular_name %>: params } }
|
115
|
+
let(:params) { attributes_for(:<%= singular_name %>) }
|
116
|
+
|
117
|
+
it_behaves_like "requires admin"
|
118
|
+
|
119
|
+
it "renders successfully" do
|
120
|
+
action
|
121
|
+
expect(response).to redirect_to(polymorphic_path([:admin, model]))
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
describe "DELETE /admin/<%= plural_name %>/:id" do
|
126
|
+
let(:action) { delete polymorphic_path([:admin, model]) }
|
127
|
+
|
128
|
+
it_behaves_like "requires admin"
|
129
|
+
|
130
|
+
it "renders successfully" do
|
131
|
+
action
|
132
|
+
expect(response).to redirect_to(polymorphic_path([:admin, <%= class_name %>]))
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails/generators/named_base"
|
4
|
+
require "rails/generators/resource_helpers"
|
5
|
+
|
6
|
+
module Koi
|
7
|
+
class AdminRouteGenerator < Rails::Generators::NamedBase
|
8
|
+
include Rails::Generators::ResourceHelpers
|
9
|
+
|
10
|
+
def add_route
|
11
|
+
route "resources :#{file_name.pluralize}", namespace: regular_class_path
|
12
|
+
end
|
13
|
+
|
14
|
+
def add_navigation
|
15
|
+
insert_into_file("config/initializers/koi.rb",
|
16
|
+
" \"#{[*regular_class_path.map(&:humanize),
|
17
|
+
human_name.pluralize].join(' ')}\" => \"/admin#{route_url}\",\n",
|
18
|
+
after: "Koi::Menu.modules = {\n")
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
# See Rails::Generators::Actions
|
24
|
+
# Replaces hard-coded route with admin route file
|
25
|
+
def route(routing_code, namespace: nil)
|
26
|
+
namespace = Array(namespace)
|
27
|
+
namespace_pattern = route_namespace_pattern(namespace)
|
28
|
+
routing_code = namespace.reverse.reduce(routing_code) do |code, name|
|
29
|
+
"namespace :#{name} do\n#{rebase_indentation(code, 2)}end"
|
30
|
+
end
|
31
|
+
|
32
|
+
log :route, routing_code
|
33
|
+
|
34
|
+
in_root do
|
35
|
+
if (namespace_match = match_file(route_file, namespace_pattern))
|
36
|
+
base_indent, *, existing_block_indent = namespace_match.captures.compact.map(&:length)
|
37
|
+
existing_line_pattern = /^ {,#{existing_block_indent}}\S.+\n?/
|
38
|
+
routing_code = rebase_indentation(routing_code, base_indent + 2).gsub(existing_line_pattern, "")
|
39
|
+
namespace_pattern = /#{Regexp.escape namespace_match.to_s}/
|
40
|
+
end
|
41
|
+
|
42
|
+
inject_into_file route_file, routing_code, after: namespace_pattern, verbose: true, force: false
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# See Rails::Generators::Actions
|
47
|
+
# Replaces Routes.draw with namespace :admin as the search term
|
48
|
+
def route_namespace_pattern(namespace)
|
49
|
+
namespace.each_with_index.reverse_each.reduce(nil) do |pattern, (name, i)|
|
50
|
+
cummulative_margin = "\\#{i + 1}[ ]{2}"
|
51
|
+
blank_or_indented_line = "^[ ]*\n|^#{cummulative_margin}.*\n"
|
52
|
+
"(?:(?:#{blank_or_indented_line})*?^(#{cummulative_margin})namespace :#{name} do\n#{pattern})?"
|
53
|
+
end.then do |pattern|
|
54
|
+
/^( *)namespace :admin do\n#{pattern}/
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def route_file
|
59
|
+
"config/routes/admin.rb"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
Description:
|
2
|
+
Generates a Koi admin crud views for the given model
|
3
|
+
|
4
|
+
Example:
|
5
|
+
bin/rails generate koi:admin_views Test
|
6
|
+
|
7
|
+
This will create:
|
8
|
+
app/views/admin/tests/_fields.html.erb
|
9
|
+
app/views/admin/tests/edit.html.erb
|
10
|
+
app/views/admin/tests/index.html.erb
|
11
|
+
app/views/admin/tests/new.html.erb
|
12
|
+
app/views/admin/tests/show.html.erb
|