My opinionated ruby on rails template
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

remove old template

-670
-670
old/template.rb
··· 1 - # template.rb 2 - 3 - 4 - # Add gems 5 - gem "devise" # Authentication 6 - gem "pundit" # Authorization 7 - gem "pg", "~> 1.6.2" # PostgreSQL adapter 8 - gem "redis", "~> 5.0" # Redis client 9 - gem "redis-session-store" # Redis-backed session store 10 - gem "rack-attack" # Rate limiting and throttling 11 - 12 - ############################################################################### 13 - # SECURITY & ENCRYPTION 14 - ############################################################################### 15 - gem "lockbox" # Encryption 16 - gem "blind_index" # Encrypted searchable fields 17 - gem "invisible_captcha" # Spam protection 18 - 19 - ############################################################################### 20 - # AUDITING & VERSIONING 21 - ############################################################################### 22 - gem "paper_trail" # Model versioning 23 - gem "audits1984" # Audit logging 24 - gem "console1984" # Console access auditing 25 - gem "acts_as_paranoid" # Soft deletes 26 - 27 - ############################################################################### 28 - # SEARCH & INDEXING 29 - ############################################################################### 30 - gem "pg_search" # PostgreSQL full-text search 31 - gem "hashid-rails" # Obfuscate IDs 32 - gem "friendly_id" # Slugs and permalinks 33 - 34 - ############################################################################### 35 - # STATE MACHINES 36 - ############################################################################### 37 - gem "aasm" # State machines 38 - 39 - ############################################################################### 40 - # ANALYTICS & MONITORING 41 - ############################################################################### 42 - gem "okcomputer" # Health checks 43 - gem "ahoy_matey" # Analytics 44 - gem "ahoy_email" # Email analytics 45 - gem "blazer" # BI dashboard 46 - gem "statsd-instrument" # StatsD metrics 47 - gem "rails_performance" # Performance monitoring 48 - 49 - ############################################################################### 50 - # EMAIL 51 - ############################################################################### 52 - gem "premailer-rails" # Inline CSS for emails 53 - gem "email_reply_parser" # Parse email replies 54 - gem "mailkick" # Email unsubscribe management 55 - 56 - ############################################################################### 57 - # BACKGROUND JOBS 58 - ############################################################################### 59 - gem "mission_control-jobs" # Job dashboard 60 - 61 - ############################################################################### 62 - # UTILITIES 63 - ############################################################################### 64 - gem "browser" # Browser detection 65 - gem "strong_migrations" # Safe migrations 66 - 67 - ############################################################################### 68 - # UI & FRONTEND 69 - ############################################################################### 70 - gem "tailwindcss-rails" # Tailwind CSS 71 - 72 - 73 - ############################################################################### 74 - # FEATURE FLAGS & CONFIGURATION 75 - ############################################################################### 76 - gem "flipper" # Feature flags 77 - gem "flipper-active_record" # ActiveRecord adapter for Flipper 78 - gem "flipper-ui" # UI for Flipper 79 - gem "flipper-active_support_cache_store" 80 - 81 - ############################################################################### 82 - # ENVIRONMENT VARIABLES 83 - ############################################################################### 84 - gem "dotenv-rails" # Environment variables 85 - 86 - gem_group :development, :test do 87 - gem "rspec-rails", "~> 7.1" # Testing framework 88 - gem "factory_bot_rails" # Test data factories 89 - gem "faker" # Fake data generation 90 - gem "shoulda-matchers" # RSpec matchers 91 - gem "rubocop-capybara", "~> 2.22", ">= 2.22.1" 92 - gem "rubocop-rspec", "~> 3.6" 93 - gem "rubocop-rspec_rails", "~> 2.31" 94 - gem "relaxed-rubocop" 95 - gem "query_count" # SQL query counter 96 - gem "bullet" # N+1 query detection 97 - end 98 - 99 - gem_group :development do 100 - gem "actual_db_schema" # Rolls back phantom migrations 101 - gem "annotaterb" # Annotate models 102 - gem "listen", "~> 3.9" # File watcher 103 - gem "letter_opener_web" # Preview emails 104 - gem "foreman" # Process manager 105 - gem "awesome_print" # Pretty print objects 106 - gem "rack-mini-profiler", "~> 3.3", require: false # Performance profiling 107 - gem "stackprof" # Used by rack-mini-profiler for flamegraphs 108 - end 109 - 110 - # Configure database to use PostgreSQL 111 - remove_file "config/database.yml" 112 - create_file "config/database.yml", <<~YAML 113 - default: &default 114 - adapter: postgresql 115 - encoding: unicode 116 - pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> 117 - 118 - development: 119 - <<: *default 120 - database: #{app_name}_development 121 - 122 - test: 123 - <<: *default 124 - database: #{app_name}_test 125 - 126 - production: 127 - <<: *default 128 - database: #{app_name}_production 129 - username: #{app_name} 130 - password: <%= ENV["#{app_name.upcase}_DATABASE_PASSWORD"] %> 131 - YAML 132 - 133 - after_bundle do 134 - # Generate Devise 135 - generate "devise:install" 136 - generate "devise", "User" 137 - 138 - # Add fields to User 139 - generate "migration", "AddFieldsToUsers first_name:string last_name:string access_level:integer" 140 - 141 - # Update the migration to add default and null constraint for access_level 142 - in_root do 143 - migration_file = Dir.glob("db/migrate/*_add_fields_to_users.rb").first 144 - if migration_file 145 - # Replace the access_level line with one that has default and null constraint 146 - gsub_file migration_file, 147 - /add_column :users, :access_level, :integer$/, 148 - "add_column :users, :access_level, :integer, default: 0, null: false" 149 - end 150 - end 151 - 152 - # Generate Lockbox master key using Lockbox.generate_key 153 - # This needs to run after bundle install, so Lockbox is available 154 - lockbox_key = `rails runner "require 'lockbox'; puts Lockbox.generate_key"`.strip 155 - 156 - # Add lockbox key to credentials programmatically 157 - # Create a Ruby script that directly writes to credentials 158 - create_file 'tmp/add_lockbox_to_credentials.rb', <<~RUBY, force: true 159 - require 'active_support/encrypted_configuration' 160 - require 'yaml' 161 - 162 - # Path to credentials files 163 - credentials_path = Rails.root.join('config/credentials.yml.enc') 164 - key_path = Rails.root.join('config/master.key') 165 - 166 - # Read the key 167 - key = File.read(key_path).strip 168 - 169 - # Create encrypted configuration instance 170 - credentials = ActiveSupport::EncryptedConfiguration.new( 171 - config_path: credentials_path, 172 - key_path: key_path, 173 - env_key: 'RAILS_MASTER_KEY', 174 - raise_if_missing_key: true 175 - ) 176 - 177 - # Read existing credentials 178 - current_config = credentials.config 179 - 180 - # Parse as YAML if it's a string, and ensure we have a hash with string keys 181 - if current_config.is_a?(String) 182 - current_config = YAML.safe_load(current_config, permitted_classes: [Symbol]) || {} 183 - end 184 - 185 - # Convert all keys to strings recursively 186 - current_config = current_config.deep_stringify_keys if current_config.respond_to?(:deep_stringify_keys) 187 - 188 - # Add lockbox config if not present 189 - unless current_config.key?('lockbox') 190 - current_config['lockbox'] = { 'master_key' => '#{lockbox_key}' } 191 - 192 - # Write back to credentials - ensure clean YAML format 193 - yaml_content = current_config.to_yaml 194 - # Remove the YAML document separator for cleaner output 195 - yaml_content = yaml_content.sub(/^---\\n/, '') 196 - # Add blank line before lockbox section for better readability 197 - yaml_content = yaml_content.sub(/^lockbox:/, "\\nlockbox:") 198 - 199 - credentials.write(yaml_content) 200 - puts "✓ Added lockbox master_key to credentials" 201 - else 202 - puts "⚠ Lockbox config already exists in credentials" 203 - end 204 - RUBY 205 - 206 - rails_command "runner tmp/add_lockbox_to_credentials.rb" 207 - remove_file 'tmp/add_lockbox_to_credentials.rb' 208 - 209 - initializer 'lockbox.rb', <<~RUBY 210 - # Set Lockbox master key from credentials 211 - if Rails.application.credentials.lockbox&.key?(:master_key) 212 - Lockbox.master_key = Rails.application.credentials.lockbox[:master_key] 213 - else 214 - Rails.logger.warn "Lockbox master_key not found in credentials. Please add it by running: rails credentials:edit" 215 - end 216 - RUBY 217 - 218 - initializer 'okcomputer.rb', <<~RUBY 219 - # frozen_string_literal: true 220 - 221 - # https://github.com/jphenow/okcomputer#registering-additional-checks 222 - # 223 - # class MyCustomCheck < OKComputer::Check 224 - # def call 225 - # if rand(10).even? 226 - # "Even is great!" 227 - # else 228 - # mark_failure 229 - # "We don't like odd numbers" 230 - # end 231 - # end 232 - # end 233 - 234 - OkComputer::Registry.register "database", OkComputer::ActiveRecordCheck.new 235 - OkComputer::Registry.register "cache", OkComputer::CacheCheck.new 236 - 237 - OkComputer::Registry.register "app_version", OkComputer::AppVersionCheck.new 238 - OkComputer::Registry.register "action_mailer", OkComputer::ActionMailerCheck.new 239 - 240 - # Run checks in parallel 241 - OkComputer.check_in_parallel = true 242 - 243 - # Log when health checks are run 244 - OkComputer.logger = Rails.logger 245 - RUBY 246 - 247 - generate "rails_performance:install" 248 - 249 - initializer 'rails_performance.rb', <<~RUBY 250 - RailsPerformance.setup do |config| 251 - config.redis = Redis.new(url: ENV["REDIS_URL"].presence || "redis://127.0.0.1:6379/0") 252 - config.duration = 4.hours 253 - 254 - config.enabled = true 255 - 256 - # protect with authentication 257 - config.verify_access_proc = proc { |controller| 258 - controller.current_user&.admin_access? 259 - } 260 - 261 - # Ignore admin and performance paths 262 - config.ignored_paths = ['/admin', '/rails/performance'] 263 - 264 - config.home_link = '/' 265 - config.skipable_rake_tasks = ['webpacker:compile'] 266 - end if defined?(RailsPerformance) 267 - RUBY 268 - 269 - initializer 'session_store.rb', <<~RUBY 270 - # Use Redis for session storage 271 - Rails.application.config.session_store :redis_store, 272 - servers: ENV.fetch("REDIS_URL") { "redis://localhost:6379/2/session" }, 273 - expire_after: 90.minutes, 274 - key: "_#{Rails.application.class.module_parent_name.underscore}_session", 275 - threadsafe: true, 276 - signed: true 277 - RUBY 278 - 279 - initializer 'rack_attack.rb', <<~RUBY 280 - # Configure Rack Attack for rate limiting 281 - class Rack::Attack 282 - # Use Redis for Rack Attack storage 283 - Rack::Attack.cache.store = ActiveSupport::Cache::RedisCacheStore.new( 284 - url: ENV.fetch("REDIS_URL") { "redis://localhost:6379/5" } 285 - ) 286 - 287 - # Throttle all requests by IP (300 req/5 minutes) 288 - throttle('req/ip', limit: 300, period: 5.minutes) do |req| 289 - req.ip unless req.path.start_with?('/assets') 290 - end 291 - 292 - # Throttle login attempts by email 293 - throttle('logins/email', limit: 5, period: 20.seconds) do |req| 294 - if req.path == '/users/sign_in' && req.post? 295 - req.params['user']&.dig('email')&.to_s&.downcase&.gsub(/\\s+/, "") 296 - end 297 - end 298 - 299 - # Throttle password reset attempts 300 - throttle('password_resets/email', limit: 3, period: 20.minutes) do |req| 301 - if req.path == '/users/password' && req.post? 302 - req.params['user']&.dig('email')&.to_s&.downcase&.gsub(/\\s+/, "") 303 - end 304 - end 305 - 306 - # Block suspicious requests 307 - blocklist('block suspicious requests') do |req| 308 - # Block requests with suspicious patterns 309 - Rack::Attack::Fail2Ban.filter("pentesters-\#{req.ip}", maxretry: 5, findtime: 10.minutes, bantime: 1.hour) do 310 - # Return true if this is a suspicious request 311 - CGI.unescape(req.query_string) =~ %r{/etc/passwd} || 312 - req.path.include?('/etc/passwd') || 313 - req.path.include?('wp-admin') || 314 - req.path.include?('wp-login') 315 - end 316 - end 317 - 318 - # Always allow requests from localhost in development 319 - safelist('allow from localhost') do |req| 320 - req.ip == '127.0.0.1' || req.ip == '::1' if Rails.env.development? 321 - end 322 - end 323 - RUBY 324 - 325 - # Configure cache store to use Redis in production 326 - inject_into_file 'config/environments/production.rb', after: "Rails.application.configure do\n" do 327 - <<~RUBY 328 - # Use Redis for caching 329 - config.cache_store = :redis_cache_store, { url: ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } } 330 - 331 - # Enable Rack Attack middleware 332 - config.middleware.use Rack::Attack 333 - 334 - RUBY 335 - end 336 - 337 - # Configure cache store to use Redis in development 338 - inject_into_file 'config/environments/development.rb', after: "Rails.application.configure do\n" do 339 - <<~RUBY 340 - # Use Redis for caching (shared across processes) 341 - config.cache_store = :redis_cache_store, { url: ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } } 342 - 343 - # Enable Rack Attack middleware (useful for testing rate limits) 344 - config.middleware.use Rack::Attack 345 - 346 - RUBY 347 - end 348 - 349 - inject_into_file 'app/models/application_record.rb', 350 - " include PgSearch::Model\n", 351 - after: "primary_abstract_class\n" 352 - 353 - inject_into_file 'app/models/application_record.rb', 354 - " has_paper_trail\n", 355 - after: "include PgSearch::Model\n" 356 - 357 - generate "devise:views" 358 - generate "pg_search:migration:multisearch" 359 - generate "lockbox:audits" 360 - generate "pundit:install" 361 - generate "ahoy:install" 362 - generate "ahoy:messages --encryption=lockbox" 363 - generate "ahoy:clicks" 364 - generate "annotate_rb:install" 365 - generate "blazer:install" 366 - generate "flipper:setup" 367 - generate "bullet:install" 368 - generate "strong_migrations:install" 369 - generate "solid_queue:install" 370 - generate "paper_trail:install" 371 - generate "mailkick:install" 372 - generate "mailkick:views" 373 - 374 - # Set up RSpec 375 - generate "rspec:install" 376 - 377 - # Create and run migrations 378 - rails_command "db:create" 379 - rails_command "db:migrate" 380 - 381 - # Create a custom controller 382 - # generate :controller, "pages", "home" 383 - # route "root to: 'pages#home'" 384 - 385 - # Add enum and has_subscriptions to User model 386 - inject_into_file 'app/models/user.rb', after: "class User < ApplicationRecord\n" do 387 - <<~RUBY 388 - enum :access_level, { 389 - user: 0, 390 - admin: 1, 391 - super_admin: 2, 392 - owner: 3 393 - }, default: :user, null: false 394 - 395 - has_subscriptions 396 - 397 - # Helper method to check if user has any admin access 398 - def admin_access? 399 - admin? || super_admin? || owner? 400 - end 401 - 402 - # Return full name 403 - def full_name 404 - "\#{first_name} \#{last_name}".strip 405 - end 406 - 407 - RUBY 408 - end 409 - 410 - file 'app/controllers/admin/application_controller.rb', <<~RUBY 411 - module Admin 412 - class ApplicationController < ::ApplicationController 413 - include Pundit 414 - rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized 415 - before_action :require_admin 416 - 417 - # Shared admin logic here 418 - def index 419 - @current_user = current_user 420 - end 421 - 422 - private 423 - 424 - def require_admin 425 - unless current_user&.admin? || current_user&.super_admin? || current_user&.owner? 426 - redirect_to root_path, alert: "You are not authorized to access this area." 427 - end 428 - end 429 - 430 - def user_not_authorized 431 - flash[:alert] = "You are not authorized to perform this action." 432 - redirect_to(request.referrer || root_path) 433 - end 434 - end 435 - end 436 - RUBY 437 - 438 - # Create Admin index view 439 - file 'app/views/admin/application/index.html.erb', <<~HTML 440 - <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8"> 441 - <div class="mb-8"> 442 - <h1 class="text-3xl font-bold text-gray-900">Admin Dashboard</h1> 443 - <p class="mt-2 text-sm text-gray-600"> 444 - Welcome back, <%= @current_user.full_name %> 445 - </p> 446 - </div> 447 - 448 - <div class="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3"> 449 - <!-- Blazer Card --> 450 - <div class="bg-white overflow-hidden shadow rounded-lg hover:shadow-lg transition-shadow"> 451 - <div class="p-6"> 452 - <div class="flex items-center"> 453 - <div class="flex-shrink-0 bg-blue-500 rounded-md p-3"> 454 - <svg class="h-6 w-6 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor"> 455 - <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" /> 456 - </svg> 457 - </div> 458 - <div class="ml-5 w-0 flex-1"> 459 - <dl> 460 - <dt class="text-sm font-medium text-gray-500 truncate">Blazer</dt> 461 - <dd class="mt-1 text-sm text-gray-900">Business Intelligence & Analytics</dd> 462 - </dl> 463 - </div> 464 - </div> 465 - <div class="mt-4"> 466 - <%= link_to "Open Blazer", "/admin/blazer", class: "text-blue-600 hover:text-blue-800 text-sm font-medium" %> 467 - </div> 468 - </div> 469 - </div> 470 - 471 - <% if @current_user.super_admin? || @current_user.owner? %> 472 - <!-- Flipper Card --> 473 - <div class="bg-white overflow-hidden shadow rounded-lg hover:shadow-lg transition-shadow"> 474 - <div class="p-6"> 475 - <div class="flex items-center"> 476 - <div class="flex-shrink-0 bg-purple-500 rounded-md p-3"> 477 - <svg class="h-6 w-6 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor"> 478 - <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4" /> 479 - </svg> 480 - </div> 481 - <div class="ml-5 w-0 flex-1"> 482 - <dl> 483 - <dt class="text-sm font-medium text-gray-500 truncate">Flipper</dt> 484 - <dd class="mt-1 text-sm text-gray-900">Feature Flags Management</dd> 485 - </dl> 486 - </div> 487 - </div> 488 - <div class="mt-4"> 489 - <%= link_to "Manage Features", "/admin/flipper", class: "text-purple-600 hover:text-purple-800 text-sm font-medium" %> 490 - </div> 491 - </div> 492 - </div> 493 - <% end %> 494 - 495 - <!-- Performance Card --> 496 - <div class="bg-white overflow-hidden shadow rounded-lg hover:shadow-lg transition-shadow"> 497 - <div class="p-6"> 498 - <div class="flex items-center"> 499 - <div class="flex-shrink-0 bg-green-500 rounded-md p-3"> 500 - <svg class="h-6 w-6 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor"> 501 - <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" /> 502 - </svg> 503 - </div> 504 - <div class="ml-5 w-0 flex-1"> 505 - <dl> 506 - <dt class="text-sm font-medium text-gray-500 truncate">Performance</dt> 507 - <dd class="mt-1 text-sm text-gray-900">Monitor Application Performance</dd> 508 - </dl> 509 - </div> 510 - </div> 511 - <div class="mt-4"> 512 - <%= link_to "View Performance", "/admin/performance", class: "text-green-600 hover:text-green-800 text-sm font-medium" %> 513 - </div> 514 - </div> 515 - </div> 516 - 517 - <!-- Users Card --> 518 - <div class="bg-white overflow-hidden shadow rounded-lg hover:shadow-lg transition-shadow"> 519 - <div class="p-6"> 520 - <div class="flex items-center"> 521 - <div class="flex-shrink-0 bg-yellow-500 rounded-md p-3"> 522 - <svg class="h-6 w-6 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor"> 523 - <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" /> 524 - </svg> 525 - </div> 526 - <div class="ml-5 w-0 flex-1"> 527 - <dl> 528 - <dt class="text-sm font-medium text-gray-500 truncate">Users</dt> 529 - <dd class="mt-1 text-sm text-gray-900">Manage User Accounts</dd> 530 - </dl> 531 - </div> 532 - </div> 533 - <div class="mt-4"> 534 - <%= link_to "Manage Users", admin_users_path, class: "text-yellow-600 hover:text-yellow-800 text-sm font-medium" %> 535 - </div> 536 - </div> 537 - </div> 538 - 539 - <!-- Health Checks Card --> 540 - <div class="bg-white overflow-hidden shadow rounded-lg hover:shadow-lg transition-shadow"> 541 - <div class="p-6"> 542 - <div class="flex items-center"> 543 - <div class="flex-shrink-0 bg-red-500 rounded-md p-3"> 544 - <svg class="h-6 w-6 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor"> 545 - <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z" /> 546 - </svg> 547 - </div> 548 - <div class="ml-5 w-0 flex-1"> 549 - <dl> 550 - <dt class="text-sm font-medium text-gray-500 truncate">Health Checks</dt> 551 - <dd class="mt-1 text-sm text-gray-900">System Health Monitoring</dd> 552 - </dl> 553 - </div> 554 - </div> 555 - <div class="mt-4"> 556 - <%= link_to "View Health", "/healthchecks", class: "text-red-600 hover:text-red-800 text-sm font-medium" %> 557 - </div> 558 - </div> 559 - </div> 560 - 561 - <!-- Mission Control Card --> 562 - <div class="bg-white overflow-hidden shadow rounded-lg hover:shadow-lg transition-shadow"> 563 - <div class="p-6"> 564 - <div class="flex items-center"> 565 - <div class="flex-shrink-0 bg-indigo-500 rounded-md p-3"> 566 - <svg class="h-6 w-6 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor"> 567 - <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" /> 568 - </svg> 569 - </div> 570 - <div class="ml-5 w-0 flex-1"> 571 - <dl> 572 - <dt class="text-sm font-medium text-gray-500 truncate">Background Jobs</dt> 573 - <dd class="mt-1 text-sm text-gray-900">Monitor Background Jobs</dd> 574 - </dl> 575 - </div> 576 - </div> 577 - <div class="mt-4"> 578 - <%= link_to "View Jobs", "/jobs", class: "text-indigo-600 hover:text-indigo-800 text-sm font-medium" %> 579 - </div> 580 - </div> 581 - </div> 582 - </div> 583 - </div> 584 - HTML 585 - 586 - # Create Admin Policy 587 - file 'app/policies/admin_policy.rb', <<~RUBY 588 - class AdminPolicy < ApplicationPolicy 589 - def blazer? 590 - user&.admin? || user&.super_admin? || user&.owner? 591 - end 592 - 593 - def flipper? 594 - user&.super_admin? || user&.owner? 595 - end 596 - 597 - def access_admin_endpoints? 598 - user&.admin? || user&.super_admin? || user&.owner? 599 - end 600 - end 601 - RUBY 602 - 603 - 604 - # Configure admin routes 605 - route <<~RUBY 606 - namespace :admin do 607 - root to: "application#index" 608 - 609 - mount Blazer::Engine, at: "blazer", constraints: ->(request) { 610 - user = User.find_by(id: request.session[:user_id]) 611 - user && AdminPolicy.new(user, :admin).blazer? 612 - } 613 - 614 - mount Flipper::UI.app(Flipper), at: "flipper", constraints: ->(request) { 615 - user = User.find_by(id: request.session[:user_id]) 616 - user && AdminPolicy.new(user, :admin).flipper? 617 - } 618 - 619 - mount RailsPerformance::Engine, at: "performance", constraints: ->(request) { 620 - user = User.find_by(id: request.session[:user_id]) 621 - user && AdminPolicy.new(user, :admin).access_admin_endpoints? 622 - } 623 - 624 - resources :users, shallow: true 625 - end 626 - RUBY 627 - 628 - # Mount OkComputer health checks 629 - route <<~RUBY 630 - mount OkComputer::Engine, at: "/healthchecks" 631 - RUBY 632 - 633 - inject_into_file 'app/mailers/application_mailer.rb', 634 - " has_history\nutm_params\n", 635 - after: "class ApplicationMailer < ActionMailer::Base\n" 636 - 637 - inject_into_file 'app/controllers/application_controller.rb', 638 - " include Pundit::Authorization\n before_action :set_paper_trail_whodunnit\n", 639 - after: "class ApplicationController < ActionController::Base\n" 640 - 641 - # Create GitHub workflows 642 - empty_directory '.github/workflows' 643 - 644 - file '.github/workflows/check-indexes.yml', <<~YAML 645 - name: Check Indexes 646 - on: 647 - pull_request: 648 - paths: 649 - - 'db/migrate/**.rb' 650 - 651 - jobs: 652 - check-indexes: 653 - runs-on: ubuntu-latest 654 - steps: 655 - - uses: actions/checkout@v4 656 - with: 657 - fetch-depth: 0 658 - 659 - - name: Check Migration Indexes 660 - uses: speedshop/ids_must_be_indexed@v1.2.1 661 - YAML 662 - 663 - # Run migrations one final time to catch any remaining pending migrations 664 - rails_command "db:migrate" 665 - 666 - # Git initialization 667 - git :init 668 - git add: "." 669 - git commit: "-m 'Initial commit (from @jaspermayone/rails-template)'" 670 - end