one-for-all-framework 4.3.0 → 4.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fd84838fc3bc3f5241aa3d3a701accc2f9bfe8b6bf7303d708319e14ea68fe17
4
- data.tar.gz: 5cce2c8ac4cfbc1586f12c5551eec509ef0536a189d7fa901d53dbb7a2d2f25d
3
+ metadata.gz: d724d07382e25a25fc566c6c889497bcabca8ef5536f86c13d9337f48507ef74
4
+ data.tar.gz: d7322c62e8380e22f17773b3202a7574859db716289706591db041205df38b61
5
5
  SHA512:
6
- metadata.gz: 4ded6af832ba7cb08647f002f871cebf223057733142d8ac21c241d43fc0a96058e1439f3946195c0b231174e07293de69b08011bf2603cdfa9ddadab4bc93b4
7
- data.tar.gz: 43a940c1c4288fbf4b273937bc5725c4418736cc88c525db71b494b4cd45b74b642220cf1fb1ce13766dbed1428653bf6907a1c27be1b2c6cd2fdd376889d18c
6
+ metadata.gz: 896e3107819354a1e335519e4a4ec8f6934bdab41ea7f2e99558bed67ede658295b1be2aeead6d50608d5a9c3b51603e71d1754837f6c45cd93b43580a5b1a72
7
+ data.tar.gz: 7d9f02100feb5a46ee909932672c03443e585ba7fd6641d05d84006f3de780f5de85e56e381a84c0b4337fc6c59a5e9ea621f5281c112f8093be3063d19c3360
data/.env.example CHANGED
@@ -2,3 +2,8 @@ DATABASE_URL="sqlite://db/development.sqlite3"
2
2
  CLOUDINARY_URL="cloudinary://API_KEY:API_SECRET@CLOUD_NAME"
3
3
  EKS_CENT_SECRET_KEY_BASE="your_secret_key"
4
4
  EKS_ENV="development"
5
+ MAILER_FROM="admin@example.com"
6
+ SMTP_ADDRESS="smtp.gmail.com"
7
+ SMTP_PORT=587
8
+ SMTP_USER_NAME="your_email@gmail.com"
9
+ SMTP_PASSWORD="your_app_password"
data/Gemfile CHANGED
@@ -13,6 +13,8 @@ gem 'mongo'
13
13
  gem 'dotenv'
14
14
  gem 'kramdown'
15
15
  gem 'kramdown-parser-gfm'
16
+ gem 'mail'
17
+ gem 'minitest'
16
18
 
17
19
  group :test do
18
20
  gem 'eksa-mination'
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
  <img src="public/images/logo.png" width="500" height="500" alt="OFA Framework Logo">
3
3
  </p>
4
4
 
5
- # ⚡ One-For-All (OFA) Framework v4.3.0
5
+ # ⚡ One-For-All (OFA) Framework v4.5.0
6
6
 
7
7
  [![Ruby Version](https://img.shields.io/badge/ruby-%3E%3D%203.0.0-red.svg)](https://www.ruby-lang.org/)
8
8
  [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
@@ -22,6 +22,9 @@
22
22
  - **🌐 Global Support**: Multi-language (I18n) support and SEO optimization ready.
23
23
  - **🖋️ Rich Text Editor**: Integrated Trix Editor for seamless post and page creation with image upload support.
24
24
  - **📡 Modern API**: Built-in JWT support and automated Swagger/OpenAPI documentation.
25
+ - **🚧 Maintenance Mode**: Instantly toggle "Under Construction" mode via CLI.
26
+ - **🔍 Activity Logs**: Full audit trail in CMS to monitor user actions and security.
27
+ - **🔌 Plugin System**: Modular architecture to extend the framework with custom add-ons.
25
28
 
26
29
  ---
27
30
 
@@ -129,6 +132,10 @@ Switches between local disk and Cloudinary cloud storage.
129
132
  Securely manages admin credentials.
130
133
  * **Output:** `✅ Password for 'admin' updated successfully.`
131
134
 
135
+ #### `ofa maintenance [on|off]`
136
+ Instantly toggles a premium "Under Construction" page for visitors while allowing admin access.
137
+ * **Output:** `✅ Maintenance mode has been turned on.`
138
+
132
139
  ---
133
140
 
134
141
  ### 📁 Project Lifecycle
@@ -141,6 +148,9 @@ Securely manages admin credentials.
141
148
  | `ofa doctor` | **System Health Check.** Validates `.env` config, database connectivity (SQL/MongoDB), Ruby version, and dependencies. |
142
149
  | `ofa routes` | **Route Inspection.** Lists all registered routes in your application in a clean tabular format. |
143
150
  | `ofa swagger` | **OpenAPI Generation.** Auto-generates `openapi.json` for your entire application. |
151
+ | `ofa task NAME` | **Run Background Task.** Executes a task defined in `lib/tasks/`. |
152
+ | `ofa test` | **Run Test Suite.** Executes all unit tests in the `test/` directory using Minitest. |
153
+ | `ofa maintenance on/off` | **Toggle Site Access.** Activates a beautiful maintenance page. Admins can still bypass it to work. |
144
154
  | `ofa deploy` | **Production Deployment.** Automatically detects deployment targets (Railway/Docker/Git). |
145
155
 
146
156
  ---
@@ -154,6 +164,10 @@ Automate the creation of boilerplate code with the generator command.
154
164
  | `ofa g api NAME` | Creates a JSON-based controller in `app/controllers/{name}_controller.rb` inheriting from `ApiController`. |
155
165
  | `ofa g model NAME` | Generates a database model in `app/models/{name}.rb` integrated with the Sequel ORM. |
156
166
  | `ofa g migration NAME` | Creates a timestamped migration file in `db/migrations/`. Use this to define your schema changes. |
167
+ | `ofa g mailer NAME ACTION` | Generates a new mailer in `app/mailers/` and an ERB template in `app/views/mailers/`. |
168
+ | `ofa g task NAME` | Creates a new background task file in `lib/tasks/`. |
169
+ | `ofa g test NAME` | Generates a new Minitest unit test in `test/`. |
170
+ | `ofa g plugin NAME` | **Plugin Scaffolding.** Creates a new plugin structure in the `plugins/` directory. |
157
171
  | `ofa g post TITLE` | Creates a new Markdown/ERB post in `app/views/posts/`. <br> *Args:* `--category`, `--author`, `--image`. <br> *Example:* `./ofa g post "My First Journey" --category Tech --author "John Doe"` |
158
172
 
159
173
  ---
@@ -0,0 +1,9 @@
1
+ require_relative 'application_controller'
2
+
3
+ class ActivityLogsController < ApplicationController
4
+ def index
5
+ # Fetch logs with user association
6
+ @logs = ActivityLog.order(Sequel.desc(:created_at)).limit(100).all
7
+ render 'cms/activity_logs'
8
+ end
9
+ end
@@ -46,6 +46,15 @@ class ApplicationController
46
46
  @req.env['eks_cent.csrf_token']
47
47
  end
48
48
 
49
+ # Activity Logger
50
+ def log_activity(action, target = nil, details = nil)
51
+ user_id = session['user_id'] || session[:user_id]
52
+ return unless user_id
53
+ ActivityLog.log(user_id, action, target, details)
54
+ end
55
+
56
+ private
57
+
49
58
  def boolean_param(val)
50
59
  ['1', 'true', 'on'].include?(val.to_s)
51
60
  end
@@ -17,6 +17,7 @@ class PagesController < ApplicationController
17
17
  data['is_nav'] = boolean_param(data['is_nav'])
18
18
  @page = Page.new(data)
19
19
  if @page.save
20
+ log_activity("Created page: #{@page.title}", @page, "Slug: #{@page.slug}, Nav: #{@page.is_nav}")
20
21
  redirect_to '/dashboard/pages'
21
22
  else
22
23
  render 'cms/pages_form'
@@ -34,6 +35,7 @@ class PagesController < ApplicationController
34
35
  data['is_active'] = boolean_param(data['is_active'])
35
36
  data['is_nav'] = boolean_param(data['is_nav'])
36
37
  if @page.update(data)
38
+ log_activity("Updated page: #{@page.title}", @page, "New Data: #{data.reject{|k| k == 'content'}.to_json}")
37
39
  redirect_to '/dashboard/pages'
38
40
  else
39
41
  render 'cms/pages_form'
@@ -42,8 +44,10 @@ class PagesController < ApplicationController
42
44
 
43
45
  def destroy
44
46
  @page = Page[params['id']]
47
+ title = @page.title
45
48
  delete_all_images_from_content(@page.content) if @page.respond_to?(:content)
46
49
  @page.destroy
50
+ log_activity("Deleted page: #{title}")
47
51
  redirect_to '/dashboard/pages'
48
52
  end
49
53
  end
@@ -16,6 +16,7 @@ class PostsController < ApplicationController
16
16
  data['is_active'] = boolean_param(data['is_active'])
17
17
  @post = Post.new(data)
18
18
  if @post.save
19
+ log_activity("Created post: #{@post.title}", @post, "Category: #{@post.category}")
19
20
  redirect_to '/dashboard/posts'
20
21
  else
21
22
  render 'cms/posts_form'
@@ -32,6 +33,7 @@ class PostsController < ApplicationController
32
33
  data = params['post']
33
34
  data['is_active'] = boolean_param(data['is_active'])
34
35
  if @post.update(data)
36
+ log_activity("Updated post: #{@post.title}", @post, "New Data: #{data.reject{|k| k == 'content'}.to_json}")
35
37
  redirect_to '/dashboard/posts'
36
38
  else
37
39
  render 'cms/posts_form'
@@ -40,9 +42,11 @@ class PostsController < ApplicationController
40
42
 
41
43
  def destroy
42
44
  @post = Post[params['id']]
45
+ title = @post.title
43
46
  delete_from_storage(@post.image_url) if @post.respond_to?(:image_url)
44
47
  delete_all_images_from_content(@post.content) if @post.respond_to?(:content)
45
48
  @post.destroy
49
+ log_activity("Deleted post: #{title}")
46
50
  redirect_to '/dashboard/posts'
47
51
  end
48
52
  end
@@ -0,0 +1,42 @@
1
+ require 'mail'
2
+ require 'erb'
3
+
4
+ class ApplicationMailer
5
+ # Configure default settings if needed
6
+ def self.mail(to:, subject:, template:, locals: {})
7
+ # Template name expected: "welcome_mailer/welcome"
8
+ template_path = File.join(APP_ROOT, 'app', 'views', 'mailers', "#{template}.erb")
9
+
10
+ unless File.exist?(template_path)
11
+ puts "❌ Mailer Error: Template not found at #{template_path}"
12
+ return
13
+ end
14
+
15
+ # Create rendering context
16
+ context = Object.new
17
+ locals.each { |k, v| context.instance_variable_set("@#{k}", v) }
18
+
19
+ body_content = ERB.new(File.read(template_path)).result(context.instance_eval { binding })
20
+
21
+ mail = Mail.new do
22
+ from ENV['MAILER_FROM'] || 'admin@example.com'
23
+ to to
24
+ subject subject
25
+ body body_content
26
+
27
+ # Optional: set content type to HTML if needed
28
+ # content_type 'text/html; charset=UTF-8'
29
+ end
30
+
31
+ # In development/test, we might want to just log it
32
+ if ENV['EKS_ENV'] == 'production'
33
+ mail.deliver!
34
+ else
35
+ puts "--- 📧 EMAIL SENT ---"
36
+ puts "To: #{to}"
37
+ puts "Subject: #{subject}"
38
+ puts "Body:\n#{body_content}"
39
+ puts "----------------------"
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,32 @@
1
+ class MaintenanceMiddleware
2
+ def initialize(app)
3
+ @app = app
4
+ end
5
+
6
+ def call(env)
7
+ # Reload config to ensure instant mode change without server restart
8
+ config = JSON.parse(File.read(File.join(APP_ROOT, 'config', 'features.json'))) rescue (defined?(FEATURES_CONFIG) ? FEATURES_CONFIG : {})
9
+
10
+ if config['maintenance']
11
+ path = env['PATH_INFO']
12
+
13
+ # Allow access to login, static assets, and CMS dashboard (if admin)
14
+ # This allows admins to turn off maintenance mode via the dashboard
15
+ allowed_paths = ['/login', '/logout', '/images', '/css', '/js', '/favicon.ico']
16
+ is_allowed = allowed_paths.any? { |p| path.start_with?(p) }
17
+
18
+ # Also allow if already logged in (admin access)
19
+ session = env['eks_cent.session'] || env['rack.session'] || {}
20
+ is_admin = session['user_id'] || session[:user_id]
21
+
22
+ if !is_allowed && !is_admin
23
+ # Render maintenance page
24
+ res = EksCent::Response.new
25
+ res.render('maintenance', layout: false)
26
+ return [503, res.headers, res.body]
27
+ end
28
+ end
29
+
30
+ @app.call(env)
31
+ end
32
+ end
@@ -0,0 +1,26 @@
1
+ class ActivityLog < Sequel::Model
2
+ # Custom user lookup to support both SQL and Mongo users
3
+ def user
4
+ return @user if defined?(@user)
5
+ @user = User.find(id: self.user_id) rescue nil
6
+ end
7
+
8
+ def self.log(user_id, action, target = nil, details = nil)
9
+ log_entry = {
10
+ user_id: user_id.to_s,
11
+ action: action,
12
+ created_at: Time.now
13
+ }
14
+
15
+ if target
16
+ log_entry[:target_type] = target.class.name
17
+ log_entry[:target_id] = target.id.to_s rescue nil
18
+ end
19
+
20
+ if details
21
+ log_entry[:details] = details.is_a?(Hash) ? details.to_json : details.to_s
22
+ end
23
+
24
+ create(log_entry)
25
+ end
26
+ end
data/app/models/user.rb CHANGED
@@ -18,6 +18,11 @@ class User < Sequel::Model
18
18
  # Override find for Mongo support
19
19
  def self.find(params)
20
20
  if mongo?
21
+ # Map :id to :_id and handle ObjectId conversion
22
+ if params[:id]
23
+ id_val = params.delete(:id)
24
+ params[:_id] = BSON::ObjectId.from_string(id_val) rescue id_val
25
+ end
21
26
  doc = MONGO_CLIENT[:users].find(params).first
22
27
  return nil unless doc
23
28
  user = User.new
@@ -0,0 +1,177 @@
1
+ <div class="glass-card">
2
+ <div class="card-header flex justify-between items-center">
3
+ <div>
4
+ <h2 class="card-title">Activity Logs (Audit Trail)</h2>
5
+ <p class="card-subtitle">Keep track of who did what in the CMS dashboard.</p>
6
+ </div>
7
+ <a href="/dashboard" class="btn-premium !py-2 !px-4 text-sm flex items-center gap-2">
8
+ <i class="fas fa-arrow-left"></i> Back to Dashboard
9
+ </a>
10
+ </div>
11
+
12
+ <div class="table-container">
13
+ <table class="premium-table">
14
+ <thead>
15
+ <tr>
16
+ <th>Time</th>
17
+ <th>User</th>
18
+ <th>Action</th>
19
+ <th>Target</th>
20
+ <th>Details</th>
21
+ </tr>
22
+ </thead>
23
+ <tbody>
24
+ <% if @logs.any? %>
25
+ <% @logs.each do |log| %>
26
+ <tr>
27
+ <td data-label="Time" class="text-sm opacity-70">
28
+ <%= log.created_at.strftime('%Y-%m-%d %H:%M:%S') %>
29
+ </td>
30
+ <td data-label="User">
31
+ <div class="flex items-center gap-2">
32
+ <div class="avatar-mini">
33
+ <%= log.user ? log.user.username[0].upcase : '?' %>
34
+ </div>
35
+ <span><%= log.user ? log.user.username : 'Unknown' %></span>
36
+ </div>
37
+ </td>
38
+ <td data-label="Action">
39
+ <span class="badge badge-primary">
40
+ <%= log.action %>
41
+ </span>
42
+ </td>
43
+ <td data-label="Target">
44
+ <% if log.target_type %>
45
+ <span class="text-xs opacity-60"><%= log.target_type %></span>
46
+ <code class="text-xs">#<%= log.target_id %></code>
47
+ <% else %>
48
+ -
49
+ <% end %>
50
+ </td>
51
+ <td data-label="Details" class="text-xs opacity-80 max-w-xs truncate">
52
+ <%= log.details || '-' %>
53
+ </td>
54
+ </tr>
55
+ <% end %>
56
+ <% else %>
57
+ <tr>
58
+ <td colspan="5" class="text-center py-8 opacity-50">
59
+ No logs found yet.
60
+ </td>
61
+ </tr>
62
+ <% end %>
63
+ </tbody>
64
+ </table>
65
+ </div>
66
+ </div>
67
+
68
+ <style>
69
+ .avatar-mini {
70
+ width: 24px;
71
+ height: 24px;
72
+ background: var(--primary);
73
+ color: white;
74
+ border-radius: 50%;
75
+ display: flex;
76
+ align-items: center;
77
+ justify-content: center;
78
+ font-size: 0.7rem;
79
+ font-weight: 600;
80
+ }
81
+
82
+ .premium-table {
83
+ width: 100%;
84
+ border-collapse: collapse;
85
+ }
86
+
87
+ .premium-table th {
88
+ text-align: left;
89
+ padding: 1rem;
90
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
91
+ font-size: 0.85rem;
92
+ text-transform: uppercase;
93
+ letter-spacing: 0.05em;
94
+ opacity: 0.7;
95
+ }
96
+
97
+ .premium-table td {
98
+ padding: 1rem;
99
+ border-bottom: 1px solid rgba(255, 255, 255, 0.05);
100
+ font-size: 0.9rem;
101
+ }
102
+
103
+ .premium-table tr:hover {
104
+ background: rgba(255, 255, 255, 0.02);
105
+ }
106
+
107
+ .badge {
108
+ padding: 0.2rem 0.6rem;
109
+ border-radius: 99px;
110
+ font-size: 0.75rem;
111
+ font-weight: 500;
112
+ }
113
+
114
+ .badge-primary {
115
+ background: rgba(99, 102, 241, 0.2);
116
+ color: #818cf8;
117
+ border: 1px solid rgba(99, 102, 241, 0.3);
118
+ }
119
+
120
+ .table-container {
121
+ overflow-x: auto;
122
+ }
123
+
124
+ /* Responsive adjustments */
125
+ @media (max-width: 768px) {
126
+ .card-header {
127
+ flex-direction: column;
128
+ align-items: flex-start !important;
129
+ gap: 1rem;
130
+ }
131
+
132
+ .premium-table thead {
133
+ display: none;
134
+ }
135
+
136
+ .premium-table tr {
137
+ display: block;
138
+ margin-bottom: 1.5rem;
139
+ background: rgba(255, 255, 255, 0.03);
140
+ border-radius: 12px;
141
+ padding: 0.5rem;
142
+ border: 1px solid rgba(255, 255, 255, 0.05);
143
+ }
144
+
145
+ .premium-table td {
146
+ display: flex;
147
+ justify-content: space-between;
148
+ align-items: center;
149
+ padding: 0.75rem 1rem;
150
+ border-bottom: 1px solid rgba(255, 255, 255, 0.03);
151
+ text-align: right;
152
+ }
153
+
154
+ .premium-table td:last-child {
155
+ border-bottom: none;
156
+ }
157
+
158
+ .premium-table td::before {
159
+ content: attr(data-label);
160
+ font-weight: 600;
161
+ font-size: 0.75rem;
162
+ text-transform: uppercase;
163
+ opacity: 0.5;
164
+ text-align: left;
165
+ margin-right: 1rem;
166
+ }
167
+
168
+ .premium-table td .flex {
169
+ justify-content: flex-end;
170
+ }
171
+
172
+ .truncate {
173
+ white-space: normal;
174
+ max-width: 60%;
175
+ }
176
+ }
177
+ </style>
@@ -35,6 +35,11 @@
35
35
  <span class="font-semibold">Products</span>
36
36
  </a>
37
37
  <% end %>
38
+
39
+ <a href="/dashboard/logs" class="flex items-center gap-3 px-4 py-3 rounded-2xl transition-all duration-300 <%= @req.path.start_with?('/dashboard/logs') ? 'bg-primary text-white shadow-lg shadow-primary/30' : 'text-slate-500 hover:bg-white/5' %>">
40
+ <i class="fas fa-history w-5"></i>
41
+ <span class="font-semibold">Activity Logs</span>
42
+ </a>
38
43
 
39
44
  <div class="h-px bg-white/5 my-4"></div>
40
45
 
data/app/views/docs.erb CHANGED
@@ -1,6 +1,6 @@
1
1
  <div class="space-y-12 pb-20">
2
2
  <div class="text-left">
3
- <div class="badge-premium">Documentation v4.3.0</div>
3
+ <div class="badge-premium">Documentation v4.5.0</div>
4
4
  <h1 class="text-5xl font-black tracking-tighter mb-4 text-slate-700 dark:text-white">Framework <span class="text-primary">Guide</span></h1>
5
5
  <p class="text-xl text-slate-500 max-w-2xl leading-relaxed">Everything you need to know about building premium web applications with the One-For-All framework.</p>
6
6
  </div>
@@ -17,7 +17,9 @@
17
17
  <a href="#cms" class="block px-4 py-2 rounded-xl hover:bg-primary/5 text-slate-600 hover:text-primary transition-all font-semibold border-l-2 border-transparent hover:border-primary">CMS & Features</a>
18
18
  <a href="#ecommerce" class="block px-4 py-2 rounded-xl hover:bg-primary/5 text-slate-600 hover:text-primary transition-all font-semibold border-l-2 border-transparent hover:border-primary">E-Commerce</a>
19
19
  <a href="#api" class="block px-4 py-2 rounded-xl hover:bg-primary/5 text-slate-600 hover:text-primary transition-all font-semibold border-l-2 border-transparent hover:border-primary">API & Modern Web</a>
20
- <a href="#deploy" class="block px-4 py-2 rounded-xl hover:bg-primary/5 text-slate-600 hover:text-primary transition-all font-semibold border-l-2 border-transparent hover:border-primary">Deployment</a>
20
+ <a href="#advanced" class="block px-4 py-2 rounded-xl hover:bg-primary/5 text-slate-600 hover:text-primary transition-all font-semibold border-l-2 border-transparent hover:border-primary">Mailers & Background Jobs</a>
21
+ <a href="#testing" class="block px-4 py-2 rounded-xl hover:bg-primary/5 text-slate-600 hover:text-primary transition-all font-semibold border-l-2 border-transparent hover:border-primary">Automated Testing</a>
22
+ <a href="#plugins" class="block px-4 py-2 rounded-xl hover:bg-primary/5 text-slate-600 hover:text-primary transition-all font-semibold border-l-2 border-transparent hover:border-primary">Plugin System</a>
21
23
  <a href="#deploy" class="block px-4 py-2 rounded-xl hover:bg-primary/5 text-slate-600 hover:text-primary transition-all font-semibold border-l-2 border-transparent hover:border-primary">Deployment</a>
22
24
  </nav>
23
25
  </aside>
@@ -98,10 +100,26 @@
98
100
  <td class="px-6 py-4 font-mono text-primary">./ofa g model NAME</td>
99
101
  <td class="px-6 py-4 text-slate-600 dark:text-slate-400">Creates model & migration linked to Sequel/NoSQL.</td>
100
102
  </tr>
101
- <tr>
102
- <td class="px-6 py-4 font-mono text-primary">./ofa g migration NAME</td>
103
- <td class="px-6 py-4 text-slate-600 dark:text-slate-400">Creates timestamped migration in <code>db/migrations/</code>.</td>
104
- </tr>
103
+ <tr>
104
+ <td class="px-6 py-4 font-mono text-primary">./ofa g migration NAME</td>
105
+ <td class="px-6 py-4 text-slate-600 dark:text-slate-400 text-xs">Creates timestamped migration in <code>db/migrations/</code>.</td>
106
+ </tr>
107
+ <tr>
108
+ <td class="px-6 py-4 font-mono text-primary">./ofa g mailer NAME</td>
109
+ <td class="px-6 py-4 text-slate-600 dark:text-slate-400 text-xs">Generates mailer class and ERB templates.</td>
110
+ </tr>
111
+ <tr>
112
+ <td class="px-6 py-4 font-mono text-primary">./ofa g task NAME</td>
113
+ <td class="px-6 py-4 text-slate-600 dark:text-slate-400 text-xs">Creates a background task in <code>lib/tasks/</code>.</td>
114
+ </tr>
115
+ <tr>
116
+ <td class="px-6 py-4 font-mono text-primary">./ofa g test NAME</td>
117
+ <td class="px-6 py-4 text-slate-600 dark:text-slate-400 text-xs">Generates a Minitest unit test file.</td>
118
+ </tr>
119
+ <tr>
120
+ <td class="px-6 py-4 font-mono text-primary">./ofa g plugin NAME</td>
121
+ <td class="px-6 py-4 text-slate-600 dark:text-slate-400 text-xs">Scaffolds a new plugin structure in <code>/plugins</code>.</td>
122
+ </tr>
105
123
 
106
124
  <tr class="bg-slate-500/5"><td colspan="2" class="px-6 py-2 text-[10px] font-black uppercase tracking-tighter text-slate-400">Customization</td></tr>
107
125
  <tr>
@@ -290,6 +308,26 @@ resources :posts
290
308
  <li>• <strong>Image Handling:</strong> Drag-and-drop images directly into the editor with auto-storage sync.</li>
291
309
  <li>• <strong>Code Blocks:</strong> Native support for code blocks with one-click "Copy" functionality.</li>
292
310
  </ul>
311
+
312
+ <h4 class="font-bold text-lg mt-8 mb-4">🔍 Activity Logs (Audit Trail)</h4>
313
+ <p class="text-sm text-slate-500 mb-4">Track every action taken by users in the CMS. Automatically logs creation, updates, and deletions of pages and posts.</p>
314
+ <ul class="text-xs space-y-2 text-slate-600 dark:text-slate-400">
315
+ <li>• <strong>User Identification:</strong> Logs include the username and avatar of the person who performed the action.</li>
316
+ <li>• <strong>Detailed Context:</strong> Records specific changes, such as new slugs or modified categories.</li>
317
+ <li>• <strong>Database Agnostic:</strong> Works seamlessly with both SQL and MongoDB storage.</li>
318
+ </ul>
319
+
320
+ <h4 class="font-bold text-lg mt-8 mb-4">🚧 Maintenance Mode</h4>
321
+ <p class="text-sm text-slate-500 mb-4">Instantly activate a professional "Under Construction" page for your visitors using the CLI. Perfect for updates or migrations.</p>
322
+ <div class="p-4 bg-white/5 border border-white/10 rounded-2xl mb-4 font-mono text-sm">
323
+ ./ofa maintenance on<br>
324
+ ./ofa maintenance off
325
+ </div>
326
+ <ul class="text-xs space-y-2 text-slate-600 dark:text-slate-400">
327
+ <li>• <strong>Admin Bypass:</strong> Logged-in administrators can still view and manage the site while maintenance is active.</li>
328
+ <li>• <strong>Premium Design:</strong> Features a high-quality Glassmorphism roket animation to keep your brand looking professional even when offline.</li>
329
+ <li>• <strong>Dynamic Updates:</strong> Changes take effect instantly without requiring a server restart.</li>
330
+ </ul>
293
331
  </div>
294
332
  </section>
295
333
 
@@ -350,6 +388,75 @@ resources :posts
350
388
  </div>
351
389
  </section>
352
390
 
391
+ <!-- Advanced Features -->
392
+ <section id="advanced" class="glass-panel p-8 md:p-12 text-left bg-gradient-to-br from-primary/10 to-transparent">
393
+ <h2 class="text-3xl font-black tracking-tight mb-6 flex items-center gap-3">
394
+ <i class="fas fa-envelope text-primary"></i> Mailers & Tasks
395
+ </h2>
396
+ <div class="markdown-content space-y-8">
397
+ <div>
398
+ <h4 class="font-bold text-lg mb-2">📧 Email Mailers</h4>
399
+ <p class="text-sm text-slate-600 dark:text-slate-400">Send transactional emails with ease using ERB templates. Mailers are stored in <code>app/mailers</code> and templates in <code>app/views/mailers</code>.</p>
400
+ <div class="p-4 bg-white/5 border border-white/10 rounded-2xl mt-4 font-mono text-sm">
401
+ ./ofa g mailer welcome signup
402
+ </div>
403
+ </div>
404
+
405
+ <div>
406
+ <h4 class="font-bold text-lg mb-2">⚙️ Background Tasks</h4>
407
+ <p class="text-sm text-slate-600 dark:text-slate-400">Run long-running processes or scheduled jobs using the Task runner. Defined in <code>lib/tasks/</code>.</p>
408
+ <div class="p-4 bg-white/5 border border-white/10 rounded-2xl mt-4 font-mono text-sm">
409
+ ./ofa g task sync_data<br>
410
+ ./ofa task sync_data
411
+ </div>
412
+ </div>
413
+ </div>
414
+ </section>
415
+
416
+ <!-- Testing -->
417
+ <section id="testing" class="glass-panel p-8 md:p-12 text-left">
418
+ <h2 class="text-3xl font-black tracking-tight mb-6 flex items-center gap-3">
419
+ <i class="fas fa-vial text-primary"></i> Automated Testing
420
+ </h2>
421
+ <div class="markdown-content">
422
+ <p class="text-slate-600 dark:text-slate-400 mb-6">OFA integrates <strong>Minitest</strong> out-of-the-box for reliable application testing. Run all your tests with a single command.</p>
423
+ <div class="p-4 bg-slate-900 rounded-2xl font-mono text-sm text-green-400 mb-6">
424
+ ./ofa test
425
+ </div>
426
+ <p class="text-sm text-slate-500">To create a new test file:</p>
427
+ <div class="p-4 bg-white/5 border border-white/10 rounded-2xl mt-2 font-mono text-sm">
428
+ ./ofa g test user_profile
429
+ </div>
430
+ </div>
431
+ </section>
432
+
433
+ <!-- Plugin System -->
434
+ <section id="plugins" class="glass-panel p-8 md:p-12 text-left bg-gradient-to-br from-primary/10 to-transparent">
435
+ <h2 class="text-3xl font-black tracking-tight mb-6 flex items-center gap-3">
436
+ <i class="fas fa-plug text-primary"></i> Plugin System
437
+ </h2>
438
+ <div class="markdown-content">
439
+ <p>OFA v4.5.0 introduces a powerful <strong>Plugin System</strong> that allows you to extend the framework without modifying the core files. Plugins are located in the <code>/plugins</code> directory.</p>
440
+
441
+ <h4 class="font-bold text-lg mt-8 mb-4">🛠️ Working with Plugins</h4>
442
+ <div class="p-4 bg-white/5 border border-white/10 rounded-2xl mb-4 font-mono text-sm">
443
+ ./ofa g plugin my_awesome_feature
444
+ </div>
445
+ <p class="text-sm text-slate-500 mb-4">Each plugin has an <code>init.rb</code> file where you can register hooks and routes:</p>
446
+ <div class="p-4 bg-slate-900 rounded-2xl font-mono text-xs text-green-400 mb-6">
447
+ <pre>
448
+ OFA.on_boot do
449
+ puts "Plugin Loaded!"
450
+ end
451
+
452
+ OFA.add_route(:get, "/my-plugin") do |req, res|
453
+ res.body << "Hello from Plugin!"
454
+ end
455
+ </pre>
456
+ </div>
457
+ </div>
458
+ </section>
459
+
353
460
  <!-- Deployment -->
354
461
  <section id="deploy" class="glass-panel p-8 md:p-12 text-left border-primary/20">
355
462
  <h2 class="text-3xl font-black tracking-tight mb-6 flex items-center gap-3">
data/app/views/index.erb CHANGED
@@ -1,6 +1,6 @@
1
1
  <div class="text-center space-y-6 max-w-2xl mx-auto py-12">
2
2
  <div class="inline-flex items-center px-4 py-1.5 rounded-full bg-primary/10 border border-primary/20 text-primary text-xs font-bold uppercase tracking-wider animate-pulse">
3
- Framework Version 4.3.0
3
+ Framework Version 4.5.0
4
4
  </div>
5
5
 
6
6
  <h1 class="text-5xl md:text-7xl font-black tracking-tight leading-tight">