one-for-all-framework 4.4.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 +4 -4
- data/README.md +10 -1
- data/app/controllers/activity_logs_controller.rb +9 -0
- data/app/controllers/application_controller.rb +9 -0
- data/app/controllers/pages_controller.rb +4 -0
- data/app/controllers/posts_controller.rb +4 -0
- data/app/middleware/maintenance_middleware.rb +32 -0
- data/app/models/activity_log.rb +26 -0
- data/app/models/user.rb +5 -0
- data/app/views/cms/activity_logs.erb +177 -0
- data/app/views/dashboard.erb +5 -0
- data/app/views/docs.erb +53 -1
- data/app/views/index.erb +1 -1
- data/app/views/maintenance.erb +143 -0
- data/bin/ofa +42 -4
- data/config/boot.rb +13 -0
- data/config/database.rb +12 -0
- data/config/features.json +2 -1
- data/config/routes.rb +4 -0
- data/config.ru +4 -0
- data/db/data.sqlite3 +0 -0
- data/db/migrations/20260502000000_create_products.rb +1 -1
- data/db/migrations/20260507000000_create_activity_logs.rb +13 -0
- data/db/migrations/20260507000001_fix_activity_logs_user_id.rb +7 -0
- metadata +8 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d724d07382e25a25fc566c6c889497bcabca8ef5536f86c13d9337f48507ef74
|
|
4
|
+
data.tar.gz: d7322c62e8380e22f17773b3202a7574859db716289706591db041205df38b61
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 896e3107819354a1e335519e4a4ec8f6934bdab41ea7f2e99558bed67ede658295b1be2aeead6d50608d5a9c3b51603e71d1754837f6c45cd93b43580a5b1a72
|
|
7
|
+
data.tar.gz: 7d9f02100feb5a46ee909932672c03443e585ba7fd6641d05d84006f3de780f5de85e56e381a84c0b4337fc6c59a5e9ea621f5281c112f8093be3063d19c3360
|
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.
|
|
5
|
+
# ⚡ One-For-All (OFA) Framework v4.5.0
|
|
6
6
|
|
|
7
7
|
[](https://www.ruby-lang.org/)
|
|
8
8
|
[](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
|
|
@@ -143,6 +150,7 @@ Securely manages admin credentials.
|
|
|
143
150
|
| `ofa swagger` | **OpenAPI Generation.** Auto-generates `openapi.json` for your entire application. |
|
|
144
151
|
| `ofa task NAME` | **Run Background Task.** Executes a task defined in `lib/tasks/`. |
|
|
145
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. |
|
|
146
154
|
| `ofa deploy` | **Production Deployment.** Automatically detects deployment targets (Railway/Docker/Git). |
|
|
147
155
|
|
|
148
156
|
---
|
|
@@ -159,6 +167,7 @@ Automate the creation of boilerplate code with the generator command.
|
|
|
159
167
|
| `ofa g mailer NAME ACTION` | Generates a new mailer in `app/mailers/` and an ERB template in `app/views/mailers/`. |
|
|
160
168
|
| `ofa g task NAME` | Creates a new background task file in `lib/tasks/`. |
|
|
161
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. |
|
|
162
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"` |
|
|
163
172
|
|
|
164
173
|
---
|
|
@@ -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,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>
|
data/app/views/dashboard.erb
CHANGED
|
@@ -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
|
+
<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>
|
|
@@ -19,6 +19,7 @@
|
|
|
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
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
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>
|
|
22
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>
|
|
23
24
|
</nav>
|
|
24
25
|
</aside>
|
|
@@ -115,6 +116,10 @@
|
|
|
115
116
|
<td class="px-6 py-4 font-mono text-primary">./ofa g test NAME</td>
|
|
116
117
|
<td class="px-6 py-4 text-slate-600 dark:text-slate-400 text-xs">Generates a Minitest unit test file.</td>
|
|
117
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>
|
|
118
123
|
|
|
119
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>
|
|
120
125
|
<tr>
|
|
@@ -303,6 +308,26 @@ resources :posts
|
|
|
303
308
|
<li>• <strong>Image Handling:</strong> Drag-and-drop images directly into the editor with auto-storage sync.</li>
|
|
304
309
|
<li>• <strong>Code Blocks:</strong> Native support for code blocks with one-click "Copy" functionality.</li>
|
|
305
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>
|
|
306
331
|
</div>
|
|
307
332
|
</section>
|
|
308
333
|
|
|
@@ -405,6 +430,33 @@ resources :posts
|
|
|
405
430
|
</div>
|
|
406
431
|
</section>
|
|
407
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
|
+
|
|
408
460
|
<!-- Deployment -->
|
|
409
461
|
<section id="deploy" class="glass-panel p-8 md:p-12 text-left border-primary/20">
|
|
410
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
|
+
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">
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Under Construction | One-For-All</title>
|
|
7
|
+
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600&display=swap" rel="stylesheet">
|
|
8
|
+
<style>
|
|
9
|
+
:root {
|
|
10
|
+
--primary: #6366f1;
|
|
11
|
+
--primary-glow: rgba(99, 102, 241, 0.5);
|
|
12
|
+
--bg: #0f172a;
|
|
13
|
+
--glass: rgba(255, 255, 255, 0.05);
|
|
14
|
+
--glass-border: rgba(255, 255, 255, 0.1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
* {
|
|
18
|
+
margin: 0;
|
|
19
|
+
padding: 0;
|
|
20
|
+
box-sizing: border-box;
|
|
21
|
+
font-family: 'Outfit', sans-serif;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
body {
|
|
25
|
+
background: var(--bg);
|
|
26
|
+
color: white;
|
|
27
|
+
height: 100vh;
|
|
28
|
+
display: flex;
|
|
29
|
+
align-items: center;
|
|
30
|
+
justify-content: center;
|
|
31
|
+
overflow: hidden;
|
|
32
|
+
background-image:
|
|
33
|
+
radial-gradient(circle at 20% 20%, rgba(99, 102, 241, 0.15) 0%, transparent 40%),
|
|
34
|
+
radial-gradient(circle at 80% 80%, rgba(236, 72, 153, 0.15) 0%, transparent 40%);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.container {
|
|
38
|
+
text-align: center;
|
|
39
|
+
padding: 3rem;
|
|
40
|
+
background: var(--glass);
|
|
41
|
+
backdrop-filter: blur(20px);
|
|
42
|
+
border: 1px solid var(--glass-border);
|
|
43
|
+
border-radius: 2rem;
|
|
44
|
+
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
|
|
45
|
+
max-width: 600px;
|
|
46
|
+
width: 90%;
|
|
47
|
+
animation: fadeIn 1s ease-out;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
@keyframes fadeIn {
|
|
51
|
+
from { opacity: 0; transform: translateY(20px); }
|
|
52
|
+
to { opacity: 1; transform: translateY(0); }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.icon {
|
|
56
|
+
font-size: 5rem;
|
|
57
|
+
margin-bottom: 1.5rem;
|
|
58
|
+
display: inline-block;
|
|
59
|
+
animation: float 3s ease-in-out infinite;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@keyframes float {
|
|
63
|
+
0%, 100% { transform: translateY(0); }
|
|
64
|
+
50% { transform: translateY(-10px); }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
h1 {
|
|
68
|
+
font-size: 2.5rem;
|
|
69
|
+
font-weight: 600;
|
|
70
|
+
margin-bottom: 1rem;
|
|
71
|
+
background: linear-gradient(to right, #818cf8, #f472b6);
|
|
72
|
+
-webkit-background-clip: text;
|
|
73
|
+
-webkit-text-fill-color: transparent;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
p {
|
|
77
|
+
font-size: 1.1rem;
|
|
78
|
+
color: #94a3b8;
|
|
79
|
+
line-height: 1.6;
|
|
80
|
+
margin-bottom: 2rem;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.loader {
|
|
84
|
+
width: 100%;
|
|
85
|
+
height: 4px;
|
|
86
|
+
background: rgba(255, 255, 255, 0.1);
|
|
87
|
+
border-radius: 2px;
|
|
88
|
+
overflow: hidden;
|
|
89
|
+
margin-bottom: 2rem;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.loader-bar {
|
|
93
|
+
width: 40%;
|
|
94
|
+
height: 100%;
|
|
95
|
+
background: var(--primary);
|
|
96
|
+
box-shadow: 0 0 15px var(--primary-glow);
|
|
97
|
+
border-radius: 2px;
|
|
98
|
+
animation: loading 2s infinite ease-in-out;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
@keyframes loading {
|
|
102
|
+
0% { transform: translateX(-100%); }
|
|
103
|
+
100% { transform: translateX(300%); }
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.footer {
|
|
107
|
+
font-size: 0.9rem;
|
|
108
|
+
color: #64748b;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.admin-link {
|
|
112
|
+
margin-top: 2rem;
|
|
113
|
+
display: block;
|
|
114
|
+
color: var(--primary);
|
|
115
|
+
text-decoration: none;
|
|
116
|
+
font-weight: 500;
|
|
117
|
+
opacity: 0.5;
|
|
118
|
+
transition: opacity 0.3s;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.admin-link:hover {
|
|
122
|
+
opacity: 1;
|
|
123
|
+
}
|
|
124
|
+
</style>
|
|
125
|
+
</head>
|
|
126
|
+
<body>
|
|
127
|
+
<div class="container">
|
|
128
|
+
<div class="icon">🚀</div>
|
|
129
|
+
<h1>Under Construction</h1>
|
|
130
|
+
<p>We're working hard to bring you something amazing. Our site is currently undergoing scheduled maintenance. Please check back soon!</p>
|
|
131
|
+
|
|
132
|
+
<div class="loader">
|
|
133
|
+
<div class="loader-bar"></div>
|
|
134
|
+
</div>
|
|
135
|
+
|
|
136
|
+
<div class="footer">
|
|
137
|
+
© <%= Time.now.year %> One-For-All Framework. All rights reserved.
|
|
138
|
+
</div>
|
|
139
|
+
|
|
140
|
+
<a href="/login" class="admin-link">Admin Login</a>
|
|
141
|
+
</div>
|
|
142
|
+
</body>
|
|
143
|
+
</html>
|
data/bin/ofa
CHANGED
|
@@ -33,7 +33,7 @@ def help
|
|
|
33
33
|
puts " / __ \\/ ____/ / | "
|
|
34
34
|
puts " / / / / /_ / /| | Framework "
|
|
35
35
|
puts "/ /_/ / __/ / ___ | Premium MVC "
|
|
36
|
-
puts "\\____/_/ /_/ |_| v4.
|
|
36
|
+
puts "\\____/_/ /_/ |_| v4.5.0 "
|
|
37
37
|
puts " "
|
|
38
38
|
puts "✨ One-For-All Framework CLI ✨"
|
|
39
39
|
puts "-----------------------------"
|
|
@@ -61,6 +61,7 @@ def help
|
|
|
61
61
|
puts " ofa doctor - Check system health (DB, config, gems)"
|
|
62
62
|
puts " ofa routes - List all registered routes"
|
|
63
63
|
puts " ofa swagger - Auto-generate OpenAPI/Swagger documentation"
|
|
64
|
+
puts " ofa maintenance ACTION - Toggle maintenance mode (on/off)"
|
|
64
65
|
puts " ofa task NAME - Run a background task"
|
|
65
66
|
puts " ofa test - Run all application tests"
|
|
66
67
|
puts " ofa run - Start the application server"
|
|
@@ -247,7 +248,7 @@ when 'init'
|
|
|
247
248
|
puts " / __ \\/ ____/ / | "
|
|
248
249
|
puts " / / / / /_ / /| | Framework "
|
|
249
250
|
puts "/ /_/ / __/ / ___ | Premium MVC "
|
|
250
|
-
puts "\\____/_/ /_/ |_| v4.
|
|
251
|
+
puts "\\____/_/ /_/ |_| v4.5.0 "
|
|
251
252
|
puts " "
|
|
252
253
|
puts "Initializing One-For-All project as '#{app_type}' in #{PROJECT_ROOT}..."
|
|
253
254
|
|
|
@@ -327,12 +328,16 @@ when 'init'
|
|
|
327
328
|
# Middleware setup
|
|
328
329
|
use EksCent::Middleware::ShowExceptions
|
|
329
330
|
use EksCent::Middleware::Session, secret: EksCent.secret_key_base
|
|
331
|
+
use MaintenanceMiddleware
|
|
330
332
|
use AuthMiddleware
|
|
331
333
|
use JwtMiddleware
|
|
332
334
|
use CSRFMiddleware
|
|
333
335
|
use EksCent::Middleware::Static, root: 'public'
|
|
334
336
|
use EksCent::Middleware::Logger
|
|
335
337
|
|
|
338
|
+
# Apply plugin routes
|
|
339
|
+
OFA.apply_pending_routes(ROUTES) if defined?(OFA)
|
|
340
|
+
|
|
336
341
|
run ROUTES
|
|
337
342
|
RUBY
|
|
338
343
|
File.write(File.join(PROJECT_ROOT, 'config.ru'), config_ru_content)
|
|
@@ -619,8 +624,28 @@ when 'g'
|
|
|
619
624
|
content = "<h1>#{name}</h1>\n<p>Author: #{options[:author] || 'Admin'}</p>\n<p>Category: #{options[:category] || 'General'}</p>"
|
|
620
625
|
File.write(file_name, content)
|
|
621
626
|
puts "Created post: #{file_name}"
|
|
627
|
+
elsif sub == 'plugin' && name
|
|
628
|
+
dir_path = File.join(PROJECT_ROOT, "plugins/#{name.downcase}")
|
|
629
|
+
FileUtils.mkdir_p(dir_path)
|
|
630
|
+
file_name = File.join(dir_path, "init.rb")
|
|
631
|
+
content = <<~RUBY
|
|
632
|
+
# Plugin: #{name.capitalize}
|
|
633
|
+
# Created at: #{Time.now}
|
|
634
|
+
|
|
635
|
+
OFA.on_boot do
|
|
636
|
+
puts "🚀 Plugin #{name.capitalize} loaded!"
|
|
637
|
+
end
|
|
638
|
+
|
|
639
|
+
# Example route added by plugin
|
|
640
|
+
# OFA.add_route(:get, '/#{name.downcase}') do |req, res|
|
|
641
|
+
# res.body << "Hello from #{name.capitalize} Plugin!"
|
|
642
|
+
# end
|
|
643
|
+
RUBY
|
|
644
|
+
File.write(file_name, content)
|
|
645
|
+
puts "Created Plugin: #{dir_path}"
|
|
646
|
+
puts "💡 Edit #{file_name} to start building your plugin."
|
|
622
647
|
else
|
|
623
|
-
puts "Usage: ofa g [controller|api|model|migration|mailer|task|test|post] NAME"
|
|
648
|
+
puts "Usage: ofa g [controller|api|model|migration|mailer|task|test|post|plugin] NAME"
|
|
624
649
|
end
|
|
625
650
|
|
|
626
651
|
when 'feature'
|
|
@@ -681,6 +706,19 @@ when 'storage'
|
|
|
681
706
|
File.write(config_path, JSON.pretty_generate(config))
|
|
682
707
|
puts "Application storage set to '#{name}'."
|
|
683
708
|
|
|
709
|
+
when 'maintenance'
|
|
710
|
+
ensure_initialized!
|
|
711
|
+
action = ARGV.shift
|
|
712
|
+
unless %w[on off].include?(action)
|
|
713
|
+
puts "Usage: ofa maintenance [on|off]"
|
|
714
|
+
exit 1
|
|
715
|
+
end
|
|
716
|
+
config_path = File.join(PROJECT_ROOT, 'config', 'features.json')
|
|
717
|
+
config = JSON.parse(File.read(config_path))
|
|
718
|
+
config['maintenance'] = (action == 'on')
|
|
719
|
+
File.write(config_path, JSON.pretty_generate(config))
|
|
720
|
+
puts "Maintenance mode has been turned #{action}."
|
|
721
|
+
|
|
684
722
|
when 'reset-password'
|
|
685
723
|
ENV['SKIP_MODELS'] = '1'
|
|
686
724
|
Object.const_set(:APP_ROOT, PROJECT_ROOT) unless defined?(APP_ROOT)
|
|
@@ -823,7 +861,7 @@ when 'console'
|
|
|
823
861
|
puts " / __ \\/ ____/ / | "
|
|
824
862
|
puts " / / / / /_ / /| | Framework "
|
|
825
863
|
puts "/ /_/ / __/ / ___ | Console (REPL) "
|
|
826
|
-
puts "\\____/_/ /_/ |_| v4.
|
|
864
|
+
puts "\\____/_/ /_/ |_| v4.5.0 "
|
|
827
865
|
puts " "
|
|
828
866
|
puts "✨ Loading environment... (Type 'exit' to quit)"
|
|
829
867
|
|
data/config/boot.rb
CHANGED
|
@@ -9,6 +9,8 @@ begin
|
|
|
9
9
|
rescue LoadError
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
+
require_relative '../lib/plugin_helper'
|
|
13
|
+
|
|
12
14
|
# Basic project structure constants
|
|
13
15
|
APP_ROOT ||= File.expand_path('..', __dir__)
|
|
14
16
|
|
|
@@ -110,6 +112,14 @@ framework_app = File.expand_path('../app', __dir__)
|
|
|
110
112
|
end
|
|
111
113
|
end
|
|
112
114
|
|
|
115
|
+
# Load Plugins
|
|
116
|
+
plugins_dir = File.join(APP_ROOT, 'plugins')
|
|
117
|
+
if File.directory?(plugins_dir)
|
|
118
|
+
Dir.glob(File.join(plugins_dir, '**', '*.rb')).each do |plugin_file|
|
|
119
|
+
require plugin_file
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
113
123
|
# ─── Routes DSL Extensions ─────────────────────────────────────────────────────
|
|
114
124
|
# Extends EksCent::Router with a `resources` helper that auto-generates RESTful
|
|
115
125
|
# routes (index, show, new, create, edit, update, destroy) for a given resource.
|
|
@@ -151,3 +161,6 @@ end
|
|
|
151
161
|
|
|
152
162
|
# Load routes
|
|
153
163
|
require_relative 'routes'
|
|
164
|
+
|
|
165
|
+
# Run Plugin Boot Hooks
|
|
166
|
+
OFA.run_boot_hooks if defined?(OFA)
|
data/config/database.rb
CHANGED
|
@@ -130,6 +130,18 @@ if DB
|
|
|
130
130
|
end
|
|
131
131
|
end
|
|
132
132
|
|
|
133
|
+
unless DB.table_exists?(:activity_logs)
|
|
134
|
+
DB.create_table :activity_logs do
|
|
135
|
+
primary_key :id
|
|
136
|
+
String :user_id
|
|
137
|
+
String :action, null: false
|
|
138
|
+
String :target_type
|
|
139
|
+
String :target_id
|
|
140
|
+
Text :details
|
|
141
|
+
DateTime :created_at, default: Sequel::CURRENT_TIMESTAMP
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
133
145
|
# Quick Migration for existing tables
|
|
134
146
|
if DB.table_exists?(:pages)
|
|
135
147
|
DB.alter_table(:pages) { add_column :is_active, TrueClass, default: true unless DB[:pages].columns.include?(:is_active) }
|
data/config/features.json
CHANGED
data/config/routes.rb
CHANGED
|
@@ -61,6 +61,10 @@ ROUTES = EksCent::Router.new do
|
|
|
61
61
|
DashboardController.new(req, res).upload
|
|
62
62
|
end
|
|
63
63
|
|
|
64
|
+
get '/dashboard/logs' do |req, res|
|
|
65
|
+
ActivityLogsController.new(req, res).index
|
|
66
|
+
end
|
|
67
|
+
|
|
64
68
|
# Resourceful CMS Routes
|
|
65
69
|
resources :pages, prefix: '/dashboard'
|
|
66
70
|
resources :posts, prefix: '/dashboard'
|
data/config.ru
CHANGED
|
@@ -3,9 +3,13 @@ require_relative 'config/boot'
|
|
|
3
3
|
# Middleware setup
|
|
4
4
|
use EksCent::Middleware::ShowExceptions
|
|
5
5
|
use EksCent::Middleware::Session, secret: EksCent.secret_key_base
|
|
6
|
+
use MaintenanceMiddleware
|
|
6
7
|
use AuthMiddleware
|
|
7
8
|
use CSRFMiddleware
|
|
8
9
|
use EksCent::Middleware::Static, root: 'public'
|
|
9
10
|
use EksCent::Middleware::Logger
|
|
10
11
|
|
|
12
|
+
# Apply plugin routes
|
|
13
|
+
OFA.apply_pending_routes(ROUTES) if defined?(OFA)
|
|
14
|
+
|
|
11
15
|
run ROUTES
|
data/db/data.sqlite3
CHANGED
|
Binary file
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Sequel.migration do
|
|
2
|
+
change do
|
|
3
|
+
create_table(:activity_logs) do
|
|
4
|
+
primary_key :id
|
|
5
|
+
String :user_id
|
|
6
|
+
String :action, null: false
|
|
7
|
+
String :target_type
|
|
8
|
+
String :target_id
|
|
9
|
+
Text :details
|
|
10
|
+
DateTime :created_at, default: Sequel::CURRENT_TIMESTAMP
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: one-for-all-framework
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 4.
|
|
4
|
+
version: 4.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ishikawa Uta
|
|
@@ -200,6 +200,7 @@ files:
|
|
|
200
200
|
- LICENSE
|
|
201
201
|
- Procfile
|
|
202
202
|
- README.md
|
|
203
|
+
- app/controllers/activity_logs_controller.rb
|
|
203
204
|
- app/controllers/api_controller.rb
|
|
204
205
|
- app/controllers/application_controller.rb
|
|
205
206
|
- app/controllers/auth_controller.rb
|
|
@@ -214,6 +215,8 @@ files:
|
|
|
214
215
|
- app/middleware/auth_middleware.rb
|
|
215
216
|
- app/middleware/csrf_middleware.rb
|
|
216
217
|
- app/middleware/jwt_middleware.rb
|
|
218
|
+
- app/middleware/maintenance_middleware.rb
|
|
219
|
+
- app/models/activity_log.rb
|
|
217
220
|
- app/models/page.rb
|
|
218
221
|
- app/models/post.rb
|
|
219
222
|
- app/models/product.rb
|
|
@@ -221,6 +224,7 @@ files:
|
|
|
221
224
|
- app/models/user.rb
|
|
222
225
|
- app/views/blog_home.erb
|
|
223
226
|
- app/views/cart_index.erb
|
|
227
|
+
- app/views/cms/activity_logs.erb
|
|
224
228
|
- app/views/cms/pages_form.erb
|
|
225
229
|
- app/views/cms/pages_index.erb
|
|
226
230
|
- app/views/cms/posts_form.erb
|
|
@@ -235,6 +239,7 @@ files:
|
|
|
235
239
|
- app/views/index.erb
|
|
236
240
|
- app/views/layout.erb
|
|
237
241
|
- app/views/login.erb
|
|
242
|
+
- app/views/maintenance.erb
|
|
238
243
|
- app/views/page.erb
|
|
239
244
|
- app/views/portfolio.erb
|
|
240
245
|
- app/views/post.erb
|
|
@@ -255,6 +260,8 @@ files:
|
|
|
255
260
|
- db/data.sqlite3
|
|
256
261
|
- db/development.sqlite3
|
|
257
262
|
- db/migrations/20260502000000_create_products.rb
|
|
263
|
+
- db/migrations/20260507000000_create_activity_logs.rb
|
|
264
|
+
- db/migrations/20260507000001_fix_activity_logs_user_id.rb
|
|
258
265
|
- ofa
|
|
259
266
|
- public/css/cms.css
|
|
260
267
|
- public/images/logo.jpg
|