My opinionated ruby on rails template
0
fork

Configure Feed

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

Enhance Rails template with security, monitoring, and improved admin UI

- Reorganize gems into logical categories with documentation
- Add Redis-backed session storage and caching
- Add Rack Attack for rate limiting and security throttling
- Add audit logging (audits1984, console1984)
- Add rails_performance monitoring
- Configure Redis cache store for production and development
- Create comprehensive admin dashboard with cards for all tools
- Fix typo: generare → generate

+290 -27
+290 -27
template.rb
··· 3 3 4 4 # Add gems 5 5 gem "devise" # Authentication 6 - gem "okcomputer" 7 - gem "lockbox" 8 - gem "blind_index" 9 - gem "mission_control-jobs" 10 - gem "pg_search" 11 - gem "paper_trail" 12 - gem "strong_migrations" 13 - gem "hashid-rails" 14 - gem "friendly_id" 15 - gem "aasm" 16 - gem "premailer-rails" 17 - gem "email_reply_parser" 18 - gem "invisible_captcha" 19 - gem "browser" 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 20 43 gem "ahoy_matey" # Analytics 21 44 gem "ahoy_email" # Email analytics 22 - gem "mailkick" 23 45 gem "blazer" # BI dashboard 24 46 gem "statsd-instrument" # StatsD metrics 25 - gem "audits1984" # Audit logging 26 - gem "console1984" # Console access auditing 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 + ############################################################################### 27 70 gem "tailwindcss-rails" # Tailwind CSS 28 - gem "pundit" # Authorization 29 - gem "acts_as_paranoid" 30 - gem "pg", "~> 1.6.2" 31 - gem 'rails_performance' 32 71 33 72 34 73 ############################################################################### ··· 205 244 OkComputer.logger = Rails.logger 206 245 RUBY 207 246 247 + generate "rails_performance:install" 248 + 208 249 initializer 'rails_performance.rb', <<~RUBY 209 250 RailsPerformance.setup do |config| 210 251 config.redis = Redis.new(url: ENV["REDIS_URL"].presence || "redis://127.0.0.1:6379/0") ··· 225 266 end if defined?(RailsPerformance) 226 267 RUBY 227 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 + 228 349 inject_into_file 'app/models/application_record.rb', 229 350 " include PgSearch::Model\n", 230 351 after: "primary_abstract_class\n" ··· 233 354 " has_paper_trail\n", 234 355 after: "include PgSearch::Model\n" 235 356 236 - inject_into_file 'app/models/application_record.rb', 237 - " has_paper_trail\n", 238 - after: "# include Hashid::Rails\n" 239 - 240 - 241 - generare "rails_performance:install" 242 357 generate "devise:views" 243 358 generate "pg_search:migration:multisearch" 244 359 generate "lockbox:audits" ··· 319 434 end 320 435 end 321 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 322 585 323 586 # Create Admin Policy 324 587 file 'app/policies/admin_policy.rb', <<~RUBY