administration-one 1.0.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.
Files changed (84) hide show
  1. checksums.yaml +7 -0
  2. data/.dockerignore +51 -0
  3. data/.gitattributes +9 -0
  4. data/.gitignore +37 -0
  5. data/.rubocop.yml +8 -0
  6. data/.ruby-version +1 -0
  7. data/CHANGELOG.md +2 -0
  8. data/Gemfile +8 -0
  9. data/Gemfile.lock +21 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +52 -0
  12. data/Rakefile +4 -0
  13. data/administration_one.gemspec +22 -0
  14. data/lib/administration-one.rb +1 -0
  15. data/lib/administration_one/version.rb +3 -0
  16. data/lib/administration_one.rb +4 -0
  17. data/lib/generators/admin/install/USAGE +5 -0
  18. data/lib/generators/admin/install/install_generator.rb +142 -0
  19. data/lib/generators/admin/install/templates/controllers/admin/base_controller.rb +20 -0
  20. data/lib/generators/admin/install/templates/controllers/admin/home_controller.rb +4 -0
  21. data/lib/generators/admin/install/templates/controllers/admin/passwords_controller.rb +35 -0
  22. data/lib/generators/admin/install/templates/controllers/admin/profile_controller.rb +23 -0
  23. data/lib/generators/admin/install/templates/controllers/admin/sessions_controller.rb +24 -0
  24. data/lib/generators/admin/install/templates/controllers/admin/users_controller.rb +54 -0
  25. data/lib/generators/admin/install/templates/css/custom.css +85 -0
  26. data/lib/generators/admin/install/templates/erb/admin/base/_ejs_tags.html.erb +8 -0
  27. data/lib/generators/admin/install/templates/erb/admin/base/_flash_messages.html.erb +9 -0
  28. data/lib/generators/admin/install/templates/erb/admin/base/_footer.html.erb +34 -0
  29. data/lib/generators/admin/install/templates/erb/admin/base/_form_errors_messages.html.erb +10 -0
  30. data/lib/generators/admin/install/templates/erb/admin/base/_javascript_tags.html.erb +5 -0
  31. data/lib/generators/admin/install/templates/erb/admin/base/_page_header.html.erb +7 -0
  32. data/lib/generators/admin/install/templates/erb/admin/base/_page_header_actions.html.erb +5 -0
  33. data/lib/generators/admin/install/templates/erb/admin/base/_page_header_breadcrumb.html.erb +12 -0
  34. data/lib/generators/admin/install/templates/erb/admin/base/_primary_navbar.html.erb +63 -0
  35. data/lib/generators/admin/install/templates/erb/admin/base/_secondary_navbar.html.erb +12 -0
  36. data/lib/generators/admin/install/templates/erb/admin/base/_secondary_navbar_links.html.erb +20 -0
  37. data/lib/generators/admin/install/templates/erb/admin/base/_stylesheet_link_tags.html.erb +5 -0
  38. data/lib/generators/admin/install/templates/erb/admin/home/index.html.erb +8 -0
  39. data/lib/generators/admin/install/templates/erb/admin/passwords/edit.html.erb +16 -0
  40. data/lib/generators/admin/install/templates/erb/admin/passwords/new.html.erb +11 -0
  41. data/lib/generators/admin/install/templates/erb/admin/profile/edit.html.erb +48 -0
  42. data/lib/generators/admin/install/templates/erb/admin/profile/show.html.erb +22 -0
  43. data/lib/generators/admin/install/templates/erb/admin/sessions/new.html.erb +21 -0
  44. data/lib/generators/admin/install/templates/erb/admin/users/_form.html.erb +48 -0
  45. data/lib/generators/admin/install/templates/erb/admin/users/edit.html.erb +17 -0
  46. data/lib/generators/admin/install/templates/erb/admin/users/index.html.erb +78 -0
  47. data/lib/generators/admin/install/templates/erb/admin/users/new.html.erb +16 -0
  48. data/lib/generators/admin/install/templates/erb/admin/users/show.html.erb +33 -0
  49. data/lib/generators/admin/install/templates/erb/layouts/admin/authentication.html.erb +32 -0
  50. data/lib/generators/admin/install/templates/erb/layouts/admin/base.html.erb +31 -0
  51. data/lib/generators/admin/install/templates/helpers/admin/application_helper.rb +25 -0
  52. data/lib/generators/admin/install/templates/images/admin/default_avatar.png +0 -0
  53. data/lib/generators/admin/install/templates/images/admin/logo.svg +4 -0
  54. data/lib/generators/admin/install/templates/jobs/attach_avatar_to_user_job.rb +21 -0
  55. data/lib/generators/admin/install/templates/js/editor.js +101 -0
  56. data/lib/generators/admin/install/templates/js/flash_message.js +5 -0
  57. data/lib/generators/admin/install/templates/js/stimulus.js +3 -0
  58. data/lib/generators/admin/install/templates/js/theme_switcher.js +93 -0
  59. data/lib/generators/admin/install/templates/js/time_zone.js +3 -0
  60. data/lib/generators/admin/install/templates/migrations/create_users.rb.tt +15 -0
  61. data/lib/generators/admin/install/templates/models/admin/application_record.rb +3 -0
  62. data/lib/generators/admin/install/templates/models/application_record.rb +3 -0
  63. data/lib/generators/admin/install/templates/models/user.rb +34 -0
  64. data/lib/generators/admin/install/templates/modules/ejs_parser.rb +119 -0
  65. data/lib/generators/admin/install/templates/passwords_mailer/reset_admin.html.erb +4 -0
  66. data/lib/generators/admin/install/templates/passwords_mailer/reset_admin.text.erb +2 -0
  67. data/lib/generators/admin/install/templates/seeds.rb +12 -0
  68. data/lib/generators/admin/install/templates/test_unit/controllers/admin/home_controller_test.rb +12 -0
  69. data/lib/generators/admin/install/templates/test_unit/controllers/admin/profile_controller_test.rb +26 -0
  70. data/lib/generators/admin/install/templates/test_unit/controllers/admin/sessions_controller_test.rb +36 -0
  71. data/lib/generators/admin/install/templates/test_unit/controllers/admin/users_controller_test.rb +55 -0
  72. data/lib/generators/admin/install/templates/test_unit/models/user_test.rb +34 -0
  73. data/lib/generators/admin/install/templates/test_unit/test_helper.rb +25 -0
  74. data/lib/generators/admin/install/templates/test_unit/users.yml +13 -0
  75. data/lib/generators/admin/scaffold/USAGE +5 -0
  76. data/lib/generators/admin/scaffold/scaffold_generator.rb +132 -0
  77. data/lib/generators/admin/scaffold/templates/controller.rb.tt +58 -0
  78. data/lib/generators/admin/scaffold/templates/erb/_form.html.erb.tt +48 -0
  79. data/lib/generators/admin/scaffold/templates/erb/edit.html.erb.tt +17 -0
  80. data/lib/generators/admin/scaffold/templates/erb/index.html.erb.tt +76 -0
  81. data/lib/generators/admin/scaffold/templates/erb/new.html.erb.tt +16 -0
  82. data/lib/generators/admin/scaffold/templates/erb/show.html.erb.tt +39 -0
  83. data/lib/generators/admin/scaffold/templates/functional_test.rb.tt +53 -0
  84. metadata +124 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1a5a5aaf21f14faf81ce38558d16c86a7f4950c7cfa2edd1f0ac16f157776a70
4
+ data.tar.gz: ede5fb32819425bab63db3fbb764fb2c147009e9e8261e67d9b6c49256318865
5
+ SHA512:
6
+ metadata.gz: c8811f2e428b657bad4155cfda93bb66d2c73e7e4abff09754d4876ef241b188022cbe086e8e5eda76d8bd884114f3d376b7cadd70d4b0b08138ead730529aee
7
+ data.tar.gz: 9d57bc0b477cc328ee682424be509ac2b8eaea70095fb46e2aa2163e803d2ebd57d96de7d5f7a1ef3929a5e3749dcbaabbbab3af7ce85c6be4f104e9a3ce498a
data/.dockerignore ADDED
@@ -0,0 +1,51 @@
1
+ # See https://docs.docker.com/engine/reference/builder/#dockerignore-file for more about ignoring files.
2
+
3
+ # Ignore git directory.
4
+ /.git/
5
+ /.gitignore
6
+
7
+ # Ignore bundler config.
8
+ /.bundle
9
+
10
+ # Ignore all environment files.
11
+ /.env*
12
+
13
+ # Ignore all default key files.
14
+ /config/master.key
15
+ /config/credentials/*.key
16
+
17
+ # Ignore all logfiles and tempfiles.
18
+ /log/*
19
+ /tmp/*
20
+ !/log/.keep
21
+ !/tmp/.keep
22
+
23
+ # Ignore pidfiles, but keep the directory.
24
+ /tmp/pids/*
25
+ !/tmp/pids/.keep
26
+
27
+ # Ignore storage (uploaded files in development and any SQLite databases).
28
+ /storage/*
29
+ !/storage/.keep
30
+ /tmp/storage/*
31
+ !/tmp/storage/.keep
32
+
33
+ # Ignore assets.
34
+ /node_modules/
35
+ /app/assets/builds/*
36
+ !/app/assets/builds/.keep
37
+ /public/assets
38
+
39
+ # Ignore CI service files.
40
+ /.github
41
+
42
+ # Ignore Kamal files.
43
+ /config/deploy*.yml
44
+ /.kamal
45
+
46
+ # Ignore development files
47
+ /.devcontainer
48
+
49
+ # Ignore Docker-related files
50
+ /.dockerignore
51
+ /Dockerfile*
data/.gitattributes ADDED
@@ -0,0 +1,9 @@
1
+ # See https://git-scm.com/docs/gitattributes for more about git attribute files.
2
+
3
+ # Mark the database schema as having been generated.
4
+ db/schema.rb linguist-generated
5
+
6
+ # Mark any vendored files as having been vendored.
7
+ vendor/* linguist-vendored
8
+ config/credentials/*.yml.enc diff=rails_credentials
9
+ config/credentials.yml.enc diff=rails_credentials
data/.gitignore ADDED
@@ -0,0 +1,37 @@
1
+ # See https://help.github.com/articles/ignoring-files for more about ignoring files.
2
+ #
3
+ # Temporary files generated by your text editor or operating system
4
+ # belong in git's global ignore instead:
5
+ # `$XDG_CONFIG_HOME/git/ignore` or `~/.config/git/ignore`
6
+
7
+ # Ignore bundler config.
8
+ /.bundle
9
+
10
+ # Ignore all environment files.
11
+ /.env*
12
+
13
+ # Ignore all logfiles and tempfiles.
14
+ /log/*
15
+ /tmp/*
16
+ !/log/.keep
17
+ !/tmp/.keep
18
+
19
+ # Ignore pidfiles, but keep the directory.
20
+ /tmp/pids/*
21
+ !/tmp/pids/
22
+ !/tmp/pids/.keep
23
+
24
+ # Ignore storage (uploaded files in development and any SQLite databases).
25
+ /storage/*
26
+ !/storage/.keep
27
+ /tmp/storage/*
28
+ !/tmp/storage/
29
+ !/tmp/storage/.keep
30
+
31
+ /public/assets
32
+
33
+ # Ignore master key for decrypting credentials and more.
34
+ /config/master.key
35
+
36
+ #Built gem files
37
+ *.gem
data/.rubocop.yml ADDED
@@ -0,0 +1,8 @@
1
+ # Omakase Ruby styling for Rails
2
+ inherit_gem: { rubocop-rails-omakase: rubocop.yml }
3
+
4
+ # Overwrite or add rules to create your own house style
5
+ #
6
+ # # Use `[a, [b, c]]` not `[ a, [ b, c ] ]`
7
+ # Layout/SpaceInsideArrayLiteralBrackets:
8
+ # Enabled: false
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ ruby-3.4.1
data/CHANGELOG.md ADDED
@@ -0,0 +1,2 @@
1
+ ## [1.0.0] - 2025-05-16
2
+ - Initial release
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in administration_one.gemspec
6
+ gemspec
7
+
8
+ gem "rake", "~> 13.0"
data/Gemfile.lock ADDED
@@ -0,0 +1,21 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ administration-one (1.0.6)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ rake (13.0.6)
10
+
11
+ PLATFORMS
12
+ x86_64-darwin-21
13
+ x86_64-darwin-22
14
+ x86_64-darwin-23
15
+
16
+ DEPENDENCIES
17
+ administration-one!
18
+ rake (~> 13.0)
19
+
20
+ BUNDLED WITH
21
+ 2.3.7
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 Alexey Beregovoy
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,52 @@
1
+ # Administration One
2
+
3
+ Administration One is a fork of [Administration Zero by Lázaro Nixon](https://github.com/lazaronixon/administration-zero), which can be considered as its charged and production-ready version. Like the original gem, it is designed for generating an administrative interface for Ruby on Rails applications. Administration-One includes all the original gem’s features but also comes with the following:
4
+
5
+ * User management is built with the standard Rails authorization generator introduced in version 8+. Administrators are users with the `is_admin` field set to `true`.
6
+ * EditorJS as the WYSIWYG editor.
7
+ * Basic user profile management.
8
+ * A functional light/dark theme switcher and other minor improvements.
9
+
10
+ <img src=".documentation/screenshot.png" alt="screenshot" style="max-width: 100%;">
11
+
12
+ ## Installation
13
+
14
+ ```ruby
15
+ bundle add administration-one
16
+ ```
17
+
18
+ Then:
19
+
20
+ ```
21
+ rails generate admin:install
22
+ ```
23
+
24
+ Then run `bundle install`
25
+
26
+ Then run `rails db:migrate db:seed`
27
+
28
+ You can access the admin panel in `/admin`, using `email: "admin@example.com", password: "Password1234"`
29
+
30
+ ## Scaffolding
31
+
32
+ You'll need to create a model to be administrated, if you don't have one. for this example let's use the following:
33
+
34
+ ```
35
+ rails generate model Post title:string content:text published:boolean
36
+ ```
37
+
38
+ Now you are free to generate your admin scaffolds.
39
+
40
+ ```
41
+ rails generate admin:scaffold posts title:string content:text published:boolean
42
+ ```
43
+
44
+ Since this model has an attribute `content:text`, the generator will ask whether to use EditorJS. This question will be asked for any model that has an attribute named «content».
45
+
46
+ ## A few words about EditorJS
47
+
48
+ Since uploading images via EditorJS typically requires storage (which can be implemented in many different ways depending on the project) Administration One uses an universal solution: encoding all images to Base64. However, this approach may be not database-friendly. The author strongly recommends implementing file-based image uploads tailored to your project’s specific.
49
+
50
+ ## License
51
+
52
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ task default: %i[]
@@ -0,0 +1,22 @@
1
+ require_relative 'lib/administration_one/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "administration-one"
5
+ spec.version = AdministrationOne::VERSION
6
+ spec.authors = ["Alexey Beregovoy"]
7
+ spec.email = ["thismailis4spam@icloud.com"]
8
+
9
+ spec.summary = "An administration system generator for Rails applications"
10
+ spec.homepage = "https://github.com/LawfulEvilRaccoon/administration-one"
11
+ spec.license = "MIT"
12
+
13
+ spec.metadata["homepage_uri"] = spec.homepage
14
+ spec.metadata["source_code_uri"] = "https://github.com/LawfulEvilRaccoon/administration-one"
15
+ spec.metadata["changelog_uri"] = "https://github.com/LawfulEvilRaccoon/administration-one/blob/main/CHANGELOG.md"
16
+
17
+ # Specify which files should be added to the gem when it is released.
18
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
19
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
20
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
21
+ end
22
+ end
@@ -0,0 +1 @@
1
+ require "administration_one"
@@ -0,0 +1,3 @@
1
+ module AdministrationOne
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,4 @@
1
+ require_relative "administration_one/version"
2
+
3
+ module AdministrationOne
4
+ end
@@ -0,0 +1,5 @@
1
+ Description:
2
+ Explain the generator
3
+
4
+ Example:
5
+ bin/rails generate admin:install
@@ -0,0 +1,142 @@
1
+ require "rails/generators/active_record"
2
+
3
+ module Admin
4
+ module Generators
5
+ class InstallGenerator < Rails::Generators::Base
6
+ include ActiveRecord::Generators::Migration
7
+
8
+ source_root File.expand_path("templates", __dir__)
9
+
10
+ def add_field_error_proc
11
+ field_error_proc_code = <<~RUBY
12
+ # Provides an HTML generator for displaying errors that come from Active Model
13
+ config.action_view.field_error_proc = Proc.new do |html_tag, instance|
14
+ raw Nokogiri::HTML.fragment(html_tag).child.add_class("is-invalid")
15
+ end
16
+ RUBY
17
+
18
+ environment field_error_proc_code
19
+ end
20
+
21
+ def generate_authentication
22
+ system "bin/rails generate authentication"
23
+
24
+ # Rails auth generator makes route that generates routes for index & show actions. This is a quick fix for that.
25
+ gsub_file "config/routes.rb", "resources :passwords, param: :token",
26
+ "resources :passwords, except: %i[ index show ], param: :token"
27
+ end
28
+
29
+ def install_active_storage
30
+ system "bin/rails active_storage:install"
31
+ end
32
+
33
+ def remove_default_users_migration
34
+ users_migration_filename = Dir.children('./db/migrate').find { |c| c.include?('users') }
35
+ File.delete("db/migrate/#{users_migration_filename}")
36
+ end
37
+
38
+ def add_gems
39
+ uncomment_lines "Gemfile", /"bcrypt"/
40
+ gem "pagy", comment: "Use Pagy to add paginated results [https://github.com/ddnexus/pagy]"
41
+ gem "ransack", comment: "Use Ransack to enable the creation of search forms for your application [https://github.com/activerecord-hackery/ransack]"
42
+ gem "spreadsheet_architect", comment: "Spreadsheet Architect is a library that allows you to create XLSX, ODS, or CSV spreadsheets super easily [https://github.com/westonganger/spreadsheet_architect]"
43
+ end
44
+
45
+ def create_db_files
46
+ copy_file "seeds.rb", "db/seeds.rb", force: true
47
+ migration_template "migrations/create_users.rb", "#{db_migrate_path}/create_users.rb"
48
+ end
49
+
50
+ def create_models
51
+ directory "models/admin", "app/models/admin"
52
+ copy_file "models/application_record.rb", "app/models/application_record.rb", force: true
53
+ copy_file "models/user.rb", "app/models/user.rb", force: true
54
+ end
55
+
56
+ def create_fixture_file
57
+ File.delete("test/fixtures/users.yml")
58
+ copy_file "test_unit/users.yml", "test/fixtures/users.yml"
59
+ end
60
+
61
+ def create_controllers
62
+ directory "controllers", "app/controllers", force: true
63
+ end
64
+
65
+ def create_views
66
+ directory "erb", "app/views"
67
+ end
68
+
69
+ def create_helpers
70
+ directory "helpers", "app/helpers"
71
+ end
72
+
73
+ def create_images
74
+ directory "images", "app/assets/images"
75
+ end
76
+
77
+ def create_jobs
78
+ directory "jobs", "app/jobs"
79
+ end
80
+
81
+ def copy_css_files
82
+ directory "css", "app/assets/stylesheets/admin"
83
+ end
84
+
85
+ def copy_javascript
86
+ directory "js", "app/javascript/admin"
87
+ end
88
+
89
+ def copy_modules
90
+ directory "modules", "app/modules"
91
+ end
92
+
93
+ def add_ejs_inflector
94
+ line = "ActiveSupport::Inflector.inflections "
95
+ RUBY_VERSION >= "3.4.0" ? line += "{ it.acronym \"EJS\" }" : line += "{ |it| it.acronym \"EJS\" }"
96
+ inject_into_file "config/initializers/inflections.rb", line
97
+ end
98
+
99
+ def add_admin_password_reset_mailer
100
+ inject_into_class "app/mailers/passwords_mailer.rb", "PasswordsMailer" do
101
+ " def reset_admin(user)\n" +
102
+ " @user = user\n" +
103
+ " mail subject: \"Reset your admin's password\", to: user.email_address\n" +
104
+ " end\n\n"
105
+ end
106
+
107
+ directory "passwords_mailer", "app/views/passwords_mailer"
108
+ end
109
+
110
+ def add_routes
111
+ route "get '/', to: 'home#index', as: 'root'", namespace: :admin
112
+ route "resources :users", namespace: :admin
113
+ route "get 'sign_in', to: 'sessions#new'", namespace: :admin
114
+ route "post 'sign_in', to: 'sessions#create'", namespace: :admin
115
+ route "post 'sign_out', to: 'sessions#destroy'", namespace: :admin
116
+ route "root to: 'admin/home#index'"
117
+ route 'get "profile", to: "profile#show"', namespace: :admin
118
+ route 'get "edit_profile", to: "profile#edit", as: "edit_profile"', namespace: :admin
119
+ route 'patch "profile", to: "profile#update"', namespace: :admin
120
+ route 'resources :passwords, except: %i[ index show ], param: :token', namespace: :admin
121
+ end
122
+
123
+ def create_test_files
124
+ File.delete('test/models/user_test.rb')
125
+ directory "test_unit/controllers", "test/controllers"
126
+ directory "test_unit/models", "test/models"
127
+ copy_file "test_unit/test_helper.rb", "test/test_helper.rb", force: true
128
+ end
129
+
130
+ def add_avatars_storage_config
131
+ empty_directory Rails.root.join("storage", "avatar_uploads_#{Rails.env}")
132
+ inject_into_file "config/storage.yml", "avatars_local_storage:\n" +
133
+ " service: Disk\n" +
134
+ " root: <%= Rails.root.join(\"storage\", \"avatar_uploads_\#{Rails.env}\") %>"
135
+ end
136
+
137
+ def restart_server
138
+ system "bin/rails restart"
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,20 @@
1
+ class Admin::BaseController < ActionController::Base
2
+ include Authentication
3
+ include Pagy::Backend
4
+
5
+ skip_before_action :require_authentication
6
+ before_action :authenticate_as_admin
7
+ around_action :set_time_zone
8
+
9
+ private
10
+ def set_time_zone
11
+ Time.use_zone(cookies[:time_zone]) { yield }
12
+ end
13
+
14
+ def authenticate_as_admin
15
+ resume_session
16
+ unless Current.user && Current.user.is_admin?
17
+ redirect_to admin_sign_in_path, alert: "Insufficient permissions for this action."
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,4 @@
1
+ class Admin::HomeController < Admin::BaseController
2
+ def index
3
+ end
4
+ end
@@ -0,0 +1,35 @@
1
+ class Admin::PasswordsController < Admin::BaseController
2
+ skip_before_action :authenticate_as_admin
3
+ before_action :set_user_by_token, only: %i[ edit update ]
4
+
5
+ layout "admin/authentication"
6
+
7
+ def new
8
+ end
9
+
10
+ def create
11
+ if user = User.find_by(email_address: params[:email_address])
12
+ PasswordsMailer.reset_admin(user).deliver_later
13
+ end
14
+
15
+ redirect_to admin_sign_in_path, notice: "Password reset instructions sent (if user with that email address exists)."
16
+ end
17
+
18
+ def edit
19
+ end
20
+
21
+ def update
22
+ if @user.update(params.permit(:password, :password_confirmation))
23
+ redirect_to admin_sign_in_path, notice: "Password has been reset."
24
+ else
25
+ redirect_to edit_admin_password_path(params[:token]), alert: "Password does not match the confirmation or fails to meet the requirements."
26
+ end
27
+ end
28
+
29
+ private
30
+ def set_user_by_token
31
+ @user = User.find_by_password_reset_token!(params[:token])
32
+ rescue ActiveSupport::MessageVerifier::InvalidSignature
33
+ redirect_to new_admin_password_path, alert: "Password reset link is invalid or has expired."
34
+ end
35
+ end
@@ -0,0 +1,23 @@
1
+ class Admin::ProfileController < Admin::BaseController
2
+ before_action :set_user, only: %i[ show edit update ]
3
+
4
+ def show
5
+ end
6
+
7
+ def edit
8
+ end
9
+
10
+ def update
11
+ if @user.update params.require(:user).permit(:username, :email_address, :password, :password_confirmation)
12
+ redirect_to admin_profile_path, notice: "Profile has been updated"
13
+ else
14
+ render :edit, status: :unprocessable_entity
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def set_user
21
+ @user = Current.user
22
+ end
23
+ end
@@ -0,0 +1,24 @@
1
+ class Admin::SessionsController < Admin::BaseController
2
+ skip_before_action :authenticate_as_admin, except: :destroy
3
+ rate_limit to: 5, within: 10.minutes, only: :create, with: -> { redirect_to admin_sign_in_path, alert: "Try again later." }
4
+
5
+ layout "admin/authentication"
6
+
7
+ def new
8
+ end
9
+
10
+ def create
11
+ user = User.authenticate_by(params.permit(:email_address, :password))
12
+ if user && user.is_admin?
13
+ start_new_session_for user
14
+ redirect_to admin_root_path, notice: "Welcome!"
15
+ else
16
+ redirect_to admin_sign_in_path, alert: "Try another email address or password."
17
+ end
18
+ end
19
+
20
+ def destroy
21
+ terminate_session
22
+ redirect_to admin_sign_in_path, notice: "You've been signed out."
23
+ end
24
+ end
@@ -0,0 +1,54 @@
1
+ class Admin::UsersController < Admin::BaseController
2
+ before_action :set_user, only: %i[ show edit update destroy ]
3
+
4
+ def index
5
+ @search = User.all.ransack(params[:q])
6
+
7
+ respond_to do |format|
8
+ format.html { @pagy, @users = pagy(@search.result) }
9
+ format.csv { render csv: @search.result }
10
+ end
11
+ end
12
+
13
+ def show
14
+ end
15
+
16
+ def new
17
+ @user = User.new
18
+ end
19
+
20
+ def edit
21
+ end
22
+
23
+ def create
24
+ @user = User.new(user_params)
25
+
26
+ if @user.save
27
+ redirect_to admin_user_path(@user), notice: "User was successfully created."
28
+ else
29
+ render :new, status: :unprocessable_entity
30
+ end
31
+ end
32
+
33
+ def update
34
+ if @user.update(user_params)
35
+ redirect_to admin_user_path(@user), notice: "User was successfully updated."
36
+ else
37
+ render :edit, status: :unprocessable_entity
38
+ end
39
+ end
40
+
41
+ def destroy
42
+ @user.destroy
43
+ redirect_to admin_users_path, notice: "User was successfully destroyed.", status: :see_other
44
+ end
45
+
46
+ private
47
+ def set_user
48
+ @user = User.find_by(id: params[:id])
49
+ end
50
+
51
+ def user_params
52
+ params.require(:user).permit(:username, :email_address, :password, :password_confirmation, :is_admin)
53
+ end
54
+ end
@@ -0,0 +1,85 @@
1
+ .form-group {
2
+ padding-top: 0.5rem;
3
+ }
4
+ .true {
5
+ color: var(--tblr-green);
6
+ }
7
+ .false {
8
+ color: var(--tblr-red);
9
+ }
10
+
11
+ .theme-switcher {
12
+ cursor: pointer;
13
+ }
14
+
15
+ /* Flash messages */
16
+
17
+ .flash-messages-wrapper {
18
+ position: absolute;
19
+ top: 1rem;
20
+ left: calc((100% - 350px) / 2);
21
+ display: flex;
22
+ flex-direction: column;
23
+ align-items: center;
24
+ z-index: 1050;
25
+ right: auto;
26
+ }
27
+
28
+ .toast {
29
+ animation-name: flash-message;
30
+ animation-duration: 3s;
31
+ }
32
+
33
+ .toast:not(:first-child) {
34
+ margin-top: 1rem;
35
+ }
36
+
37
+ .toast-body {
38
+ background-color: var(--tblr-gray-600);
39
+ }
40
+
41
+ .message-alert {
42
+ background-color: var(--tblr-warning);
43
+ }
44
+
45
+ @keyframes flash-message {
46
+ 0% { opacity: 0 }
47
+ 15% { opacity: 0.95 }
48
+ 75% { opacity: 0.95 }
49
+ 100% { opacity: 0 }
50
+ }
51
+
52
+ /* EditorJS */
53
+
54
+ .ce-paragraph.cdx-block {
55
+ font-weight: 400;
56
+ }
57
+
58
+ .ce-toolbar__plus, .ce-toolbar__settings-btn {
59
+ background-color: var(--tblr-indigo);
60
+ color: var(--tblr-gray-100);
61
+ }
62
+
63
+ .ce-toolbar__plus:hover,
64
+ .cdx-settings-button:hover,
65
+ .ce-settings__button:hover,
66
+ .ce-toolbox__button--active,
67
+ .ce-toolbox__button:hover,
68
+ .cdx-button:hover,
69
+ .ce-inline-toolbar__dropdown:hover,
70
+ .ce-inline-tool:hover,
71
+ .ce-popover__item:hover,
72
+ .ce-toolbar__settings-btn:hover {
73
+ background-color: var(--tblr-indigo-darken);
74
+ color: var(--tblr-gray-100);
75
+ }
76
+
77
+ @media (min-width: 768px) {
78
+ .ce-toolbar__content {
79
+ max-width: calc(100% - 8rem);
80
+ }
81
+
82
+ .ce-block__content {
83
+ max-width: calc(100% - 10rem);
84
+ }
85
+ }