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 +4 -4
- data/.env.example +5 -0
- data/Gemfile +2 -0
- data/README.md +15 -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/mailers/application_mailer.rb +42 -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 +113 -6
- data/app/views/index.erb +1 -1
- data/app/views/maintenance.erb +143 -0
- data/bin/ofa +195 -10
- data/config/boot.rb +14 -1
- 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 +9 -2
- data/db/target.sqlite3 +0 -0
|
@@ -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
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
2
|
|
|
3
|
+
# --- Bundler Setup ---
|
|
4
|
+
# This ensures that if we are in a project with a Gemfile, we use the correct
|
|
5
|
+
# gem versions specified in Gemfile.lock, preventing conflicts with system gems.
|
|
6
|
+
if File.exist?(File.join(Dir.pwd, 'Gemfile'))
|
|
7
|
+
begin
|
|
8
|
+
require 'bundler/setup'
|
|
9
|
+
rescue LoadError
|
|
10
|
+
# Bundler not installed, proceed without it
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
3
14
|
require 'fileutils'
|
|
4
15
|
require 'optparse'
|
|
5
16
|
require 'json'
|
|
@@ -22,7 +33,7 @@ def help
|
|
|
22
33
|
puts " / __ \\/ ____/ / | "
|
|
23
34
|
puts " / / / / /_ / /| | Framework "
|
|
24
35
|
puts "/ /_/ / __/ / ___ | Premium MVC "
|
|
25
|
-
puts "\\____/_/ /_/ |_| v4.
|
|
36
|
+
puts "\\____/_/ /_/ |_| v4.5.0 "
|
|
26
37
|
puts " "
|
|
27
38
|
puts "✨ One-For-All Framework CLI ✨"
|
|
28
39
|
puts "-----------------------------"
|
|
@@ -33,6 +44,9 @@ def help
|
|
|
33
44
|
puts " ofa g api NAME - Generate a new API controller (JSON)"
|
|
34
45
|
puts " ofa g model NAME - Generate a new model"
|
|
35
46
|
puts " ofa g migration NAME - Generate a new migration"
|
|
47
|
+
puts " ofa g mailer NAME ACTION - Generate a new mailer and template"
|
|
48
|
+
puts " ofa g task NAME - Generate a new background task"
|
|
49
|
+
puts " ofa g test NAME - Generate a new unit test"
|
|
36
50
|
puts " ofa g post TITLE - Create a new post [args: --category, --author, --image]"
|
|
37
51
|
puts " ofa feature ACTION F - Toggle features: action=enable/disable, F=cms/auth/rich_text"
|
|
38
52
|
puts " ofa type NAME - Set application type: portfolio, blog, landing_page, e_commerce"
|
|
@@ -47,6 +61,9 @@ def help
|
|
|
47
61
|
puts " ofa doctor - Check system health (DB, config, gems)"
|
|
48
62
|
puts " ofa routes - List all registered routes"
|
|
49
63
|
puts " ofa swagger - Auto-generate OpenAPI/Swagger documentation"
|
|
64
|
+
puts " ofa maintenance ACTION - Toggle maintenance mode (on/off)"
|
|
65
|
+
puts " ofa task NAME - Run a background task"
|
|
66
|
+
puts " ofa test - Run all application tests"
|
|
50
67
|
puts " ofa run - Start the application server"
|
|
51
68
|
puts " ofa deploy - Deploy project to production"
|
|
52
69
|
puts "-----------------------------"
|
|
@@ -231,7 +248,7 @@ when 'init'
|
|
|
231
248
|
puts " / __ \\/ ____/ / | "
|
|
232
249
|
puts " / / / / /_ / /| | Framework "
|
|
233
250
|
puts "/ /_/ / __/ / ___ | Premium MVC "
|
|
234
|
-
puts "\\____/_/ /_/ |_| v4.
|
|
251
|
+
puts "\\____/_/ /_/ |_| v4.5.0 "
|
|
235
252
|
puts " "
|
|
236
253
|
puts "Initializing One-For-All project as '#{app_type}' in #{PROJECT_ROOT}..."
|
|
237
254
|
|
|
@@ -262,7 +279,7 @@ when 'init'
|
|
|
262
279
|
puts "-----------------------------\n"
|
|
263
280
|
|
|
264
281
|
# Create directories
|
|
265
|
-
dirs = ['app/controllers', 'app/models', 'app/views', 'config', 'db/migrations', 'public/css', 'public/js', 'public/images', 'public/images/uploads']
|
|
282
|
+
dirs = ['app/controllers', 'app/models', 'app/views', 'app/mailers', 'lib/tasks', 'test', 'config', 'db/migrations', 'public/css', 'public/js', 'public/images', 'public/images/uploads']
|
|
266
283
|
dirs.each { |dir| FileUtils.mkdir_p(File.join(PROJECT_ROOT, dir)) }
|
|
267
284
|
|
|
268
285
|
# Copy framework core migrations
|
|
@@ -274,6 +291,21 @@ when 'init'
|
|
|
274
291
|
end
|
|
275
292
|
end
|
|
276
293
|
|
|
294
|
+
# Copy framework core helpers and test assets
|
|
295
|
+
['lib/task_helper.rb', 'app/mailers/application_mailer.rb', 'test/test_helper.rb'].each do |file|
|
|
296
|
+
src = File.join(FRAMEWORK_ROOT, file)
|
|
297
|
+
dest = File.join(PROJECT_ROOT, file)
|
|
298
|
+
FileUtils.mkdir_p(File.dirname(dest))
|
|
299
|
+
if File.exist?(src)
|
|
300
|
+
content = File.read(src)
|
|
301
|
+
# Inject FRAMEWORK_ROOT into test_helper.rb
|
|
302
|
+
if file == 'test/test_helper.rb'
|
|
303
|
+
content.gsub!("require_relative '../config/boot'", "$LOAD_PATH.unshift '#{FRAMEWORK_ROOT}'\nrequire 'config/boot'")
|
|
304
|
+
end
|
|
305
|
+
File.write(dest, content)
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
|
|
277
309
|
# Create .env
|
|
278
310
|
env_content = []
|
|
279
311
|
env_content << "EKS_ENV=development"
|
|
@@ -296,12 +328,16 @@ when 'init'
|
|
|
296
328
|
# Middleware setup
|
|
297
329
|
use EksCent::Middleware::ShowExceptions
|
|
298
330
|
use EksCent::Middleware::Session, secret: EksCent.secret_key_base
|
|
331
|
+
use MaintenanceMiddleware
|
|
299
332
|
use AuthMiddleware
|
|
300
333
|
use JwtMiddleware
|
|
301
334
|
use CSRFMiddleware
|
|
302
335
|
use EksCent::Middleware::Static, root: 'public'
|
|
303
336
|
use EksCent::Middleware::Logger
|
|
304
337
|
|
|
338
|
+
# Apply plugin routes
|
|
339
|
+
OFA.apply_pending_routes(ROUTES) if defined?(OFA)
|
|
340
|
+
|
|
305
341
|
run ROUTES
|
|
306
342
|
RUBY
|
|
307
343
|
File.write(File.join(PROJECT_ROOT, 'config.ru'), config_ru_content)
|
|
@@ -321,6 +357,8 @@ when 'init'
|
|
|
321
357
|
gem 'mysql2'
|
|
322
358
|
gem 'kramdown'
|
|
323
359
|
gem 'kramdown-parser-gfm'
|
|
360
|
+
gem 'mail'
|
|
361
|
+
gem 'minitest'
|
|
324
362
|
RUBY
|
|
325
363
|
File.write(File.join(PROJECT_ROOT, 'Gemfile'), gemfile_content)
|
|
326
364
|
|
|
@@ -499,6 +537,58 @@ when 'g'
|
|
|
499
537
|
File.write(file_name, content)
|
|
500
538
|
puts "Created API Controller: #{file_name}"
|
|
501
539
|
puts "💡 Don't forget to register it in config/routes.rb"
|
|
540
|
+
elsif sub == 'mailer' && name
|
|
541
|
+
action = ARGV.shift || 'welcome'
|
|
542
|
+
class_name = name.capitalize + "Mailer"
|
|
543
|
+
file_name = File.join(PROJECT_ROOT, "app/mailers/#{name.downcase}_mailer.rb")
|
|
544
|
+
content = <<~RUBY
|
|
545
|
+
class #{class_name} < ApplicationMailer
|
|
546
|
+
def #{action}(user)
|
|
547
|
+
mail(
|
|
548
|
+
to: user.email,
|
|
549
|
+
subject: "Welcome to our platform!",
|
|
550
|
+
template: "#{name.downcase}_mailer/#{action}",
|
|
551
|
+
locals: { user: user }
|
|
552
|
+
)
|
|
553
|
+
end
|
|
554
|
+
end
|
|
555
|
+
RUBY
|
|
556
|
+
File.write(file_name, content)
|
|
557
|
+
|
|
558
|
+
# Create template
|
|
559
|
+
template_dir = File.join(PROJECT_ROOT, "app/views/mailers/#{name.downcase}_mailer")
|
|
560
|
+
FileUtils.mkdir_p(template_dir)
|
|
561
|
+
template_file = File.join(template_dir, "#{action}.erb")
|
|
562
|
+
File.write(template_file, "<h1>Hello <%= @user.username %></h1>\n<p>Welcome to One-For-All Framework!</p>")
|
|
563
|
+
|
|
564
|
+
puts "Created Mailer: #{file_name}"
|
|
565
|
+
puts "Created Template: #{template_file}"
|
|
566
|
+
elsif sub == 'task' && name
|
|
567
|
+
dir_path = File.join(PROJECT_ROOT, "lib/tasks")
|
|
568
|
+
FileUtils.mkdir_p(dir_path)
|
|
569
|
+
file_name = File.join(dir_path, "#{name.downcase}.rb")
|
|
570
|
+
content = <<~RUBY
|
|
571
|
+
desc "Description for #{name} task"
|
|
572
|
+
task :#{name.downcase} do
|
|
573
|
+
puts "Running task: #{name}"
|
|
574
|
+
# Your background job logic here
|
|
575
|
+
end
|
|
576
|
+
RUBY
|
|
577
|
+
File.write(file_name, content)
|
|
578
|
+
puts "Created Task: #{file_name}"
|
|
579
|
+
elsif sub == 'test' && name
|
|
580
|
+
file_name = File.join(PROJECT_ROOT, "test/#{name.downcase}_test.rb")
|
|
581
|
+
content = <<~RUBY
|
|
582
|
+
require_relative 'test_helper'
|
|
583
|
+
|
|
584
|
+
class #{name.capitalize}Test < Minitest::Test
|
|
585
|
+
def test_example
|
|
586
|
+
assert true
|
|
587
|
+
end
|
|
588
|
+
end
|
|
589
|
+
RUBY
|
|
590
|
+
File.write(file_name, content)
|
|
591
|
+
puts "Created Test: #{file_name}"
|
|
502
592
|
elsif sub == 'model' && name
|
|
503
593
|
class_name = name.capitalize
|
|
504
594
|
file_name = File.join(PROJECT_ROOT, "app/models/#{name.downcase}.rb")
|
|
@@ -534,8 +624,28 @@ when 'g'
|
|
|
534
624
|
content = "<h1>#{name}</h1>\n<p>Author: #{options[:author] || 'Admin'}</p>\n<p>Category: #{options[:category] || 'General'}</p>"
|
|
535
625
|
File.write(file_name, content)
|
|
536
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."
|
|
537
647
|
else
|
|
538
|
-
puts "Usage: ofa g [controller|model|migration|post] NAME"
|
|
648
|
+
puts "Usage: ofa g [controller|api|model|migration|mailer|task|test|post|plugin] NAME"
|
|
539
649
|
end
|
|
540
650
|
|
|
541
651
|
when 'feature'
|
|
@@ -545,7 +655,8 @@ when 'feature'
|
|
|
545
655
|
config_path = File.join(PROJECT_ROOT, 'config', 'features.json')
|
|
546
656
|
config = JSON.parse(File.read(config_path))
|
|
547
657
|
config[feature] = (action == 'enable')
|
|
548
|
-
File.
|
|
658
|
+
File.open("#{config_path}.tmp", 'w') { |f| f.write(JSON.pretty_generate(config)) }
|
|
659
|
+
File.rename("#{config_path}.tmp", config_path)
|
|
549
660
|
puts "Feature '#{feature}' has been #{action}d."
|
|
550
661
|
|
|
551
662
|
if action == 'enable' && feature == 'cms' && !config['auth']
|
|
@@ -564,7 +675,8 @@ when 'type'
|
|
|
564
675
|
config_path = File.join(PROJECT_ROOT, 'config', 'features.json')
|
|
565
676
|
config = JSON.parse(File.read(config_path))
|
|
566
677
|
config['type'] = name
|
|
567
|
-
File.
|
|
678
|
+
File.open("#{config_path}.tmp", 'w') { |f| f.write(JSON.pretty_generate(config)) }
|
|
679
|
+
File.rename("#{config_path}.tmp", config_path)
|
|
568
680
|
puts "Application type set to '#{name}'."
|
|
569
681
|
|
|
570
682
|
when 'theme'
|
|
@@ -577,7 +689,8 @@ when 'theme'
|
|
|
577
689
|
config_path = File.join(PROJECT_ROOT, 'config', 'features.json')
|
|
578
690
|
config = JSON.parse(File.read(config_path))
|
|
579
691
|
config['theme'] = name
|
|
580
|
-
File.
|
|
692
|
+
File.open("#{config_path}.tmp", 'w') { |f| f.write(JSON.pretty_generate(config)) }
|
|
693
|
+
File.rename("#{config_path}.tmp", config_path)
|
|
581
694
|
puts "Application theme set to '#{name}'."
|
|
582
695
|
|
|
583
696
|
when 'storage'
|
|
@@ -593,6 +706,19 @@ when 'storage'
|
|
|
593
706
|
File.write(config_path, JSON.pretty_generate(config))
|
|
594
707
|
puts "Application storage set to '#{name}'."
|
|
595
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
|
+
|
|
596
722
|
when 'reset-password'
|
|
597
723
|
ENV['SKIP_MODELS'] = '1'
|
|
598
724
|
Object.const_set(:APP_ROOT, PROJECT_ROOT) unless defined?(APP_ROOT)
|
|
@@ -640,7 +766,8 @@ when 'db'
|
|
|
640
766
|
env_lines = File.exist?(env_path) ? File.readlines(env_path) : []
|
|
641
767
|
env_lines.reject! { |l| l.start_with?('DATABASE_URL=') }
|
|
642
768
|
env_lines << "DATABASE_URL=#{db_name}\n"
|
|
643
|
-
File.
|
|
769
|
+
File.open("#{env_path}.tmp", 'w') { |f| f.write(env_lines.join) }
|
|
770
|
+
File.rename("#{env_path}.tmp", env_path)
|
|
644
771
|
puts "✅ Connection string saved to .env"
|
|
645
772
|
end
|
|
646
773
|
when 'sqlite'
|
|
@@ -648,7 +775,8 @@ when 'db'
|
|
|
648
775
|
else
|
|
649
776
|
config = { "adapter" => type, "host" => "localhost", "user" => "root", "password" => "", "database" => db_name || "one_for_all" }
|
|
650
777
|
end
|
|
651
|
-
File.
|
|
778
|
+
File.open("#{config_path}.tmp", 'w') { |f| f.write(JSON.pretty_generate(config)) }
|
|
779
|
+
File.rename("#{config_path}.tmp", config_path)
|
|
652
780
|
puts "Switched to #{type} mode."
|
|
653
781
|
when 'migrate'
|
|
654
782
|
run_migrations
|
|
@@ -733,7 +861,7 @@ when 'console'
|
|
|
733
861
|
puts " / __ \\/ ____/ / | "
|
|
734
862
|
puts " / / / / /_ / /| | Framework "
|
|
735
863
|
puts "/ /_/ / __/ / ___ | Console (REPL) "
|
|
736
|
-
puts "\\____/_/ /_/ |_| v4.
|
|
864
|
+
puts "\\____/_/ /_/ |_| v4.5.0 "
|
|
737
865
|
puts " "
|
|
738
866
|
puts "✨ Loading environment... (Type 'exit' to quit)"
|
|
739
867
|
|
|
@@ -845,6 +973,63 @@ when 'deploy'
|
|
|
845
973
|
puts " This framework supports deployment via Docker or Git Push."
|
|
846
974
|
end
|
|
847
975
|
|
|
976
|
+
when 'task'
|
|
977
|
+
ensure_initialized!
|
|
978
|
+
name = ARGV.shift
|
|
979
|
+
if name.nil?
|
|
980
|
+
puts "Usage: ofa task NAME"
|
|
981
|
+
# List available tasks
|
|
982
|
+
tasks_dir = File.join(PROJECT_ROOT, 'lib/tasks')
|
|
983
|
+
if Dir.exist?(tasks_dir)
|
|
984
|
+
puts "\nAvailable tasks:"
|
|
985
|
+
Dir.glob(File.join(tasks_dir, '*.rb')).each do |f|
|
|
986
|
+
puts " - #{File.basename(f, '.rb')}"
|
|
987
|
+
end
|
|
988
|
+
end
|
|
989
|
+
exit 1
|
|
990
|
+
end
|
|
991
|
+
|
|
992
|
+
# Load framework & app
|
|
993
|
+
Object.const_set(:APP_ROOT, PROJECT_ROOT) unless defined?(APP_ROOT)
|
|
994
|
+
require File.join(FRAMEWORK_ROOT, 'config', 'boot')
|
|
995
|
+
require File.join(FRAMEWORK_ROOT, 'lib', 'task_helper')
|
|
996
|
+
|
|
997
|
+
# Load all tasks in lib/tasks
|
|
998
|
+
Dir.glob(File.join(PROJECT_ROOT, 'lib/tasks/*.rb')).each { |f| require f }
|
|
999
|
+
|
|
1000
|
+
run_task(name)
|
|
1001
|
+
|
|
1002
|
+
when 'test'
|
|
1003
|
+
ensure_initialized!
|
|
1004
|
+
puts "🧪 Running tests..."
|
|
1005
|
+
puts "------------------------------------------"
|
|
1006
|
+
|
|
1007
|
+
# Set environment to test
|
|
1008
|
+
ENV['EKS_ENV'] = 'test'
|
|
1009
|
+
Object.const_set(:APP_ROOT, PROJECT_ROOT) unless defined?(APP_ROOT)
|
|
1010
|
+
|
|
1011
|
+
# Add project root to load path
|
|
1012
|
+
$LOAD_PATH.unshift PROJECT_ROOT
|
|
1013
|
+
$LOAD_PATH.unshift File.join(PROJECT_ROOT, 'test')
|
|
1014
|
+
|
|
1015
|
+
# Find all tests (support both _test.rb and _spec.rb)
|
|
1016
|
+
if ARGV.empty?
|
|
1017
|
+
test_files = Dir.glob(File.join(PROJECT_ROOT, 'test/**/{*_test.rb,*_spec.rb}'))
|
|
1018
|
+
else
|
|
1019
|
+
test_files = ARGV.map { |arg| File.expand_path(arg, PROJECT_ROOT) }
|
|
1020
|
+
end
|
|
1021
|
+
|
|
1022
|
+
if test_files.empty?
|
|
1023
|
+
puts "❌ No tests found in test/ directory."
|
|
1024
|
+
exit 1
|
|
1025
|
+
end
|
|
1026
|
+
|
|
1027
|
+
# Run them by requiring
|
|
1028
|
+
test_files.each { |f| require f }
|
|
1029
|
+
|
|
1030
|
+
# Trigger EksaMination execution if available
|
|
1031
|
+
EksaMination.run if defined?(EksaMination)
|
|
1032
|
+
|
|
848
1033
|
else
|
|
849
1034
|
help
|
|
850
1035
|
end
|
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
|
|
|
@@ -93,7 +95,7 @@ require_relative 'database'
|
|
|
93
95
|
|
|
94
96
|
# Autoloading (Framework Core first, then APP_ROOT)
|
|
95
97
|
framework_app = File.expand_path('../app', __dir__)
|
|
96
|
-
['controllers', 'models', 'middleware', 'helpers'].each do |folder|
|
|
98
|
+
['controllers', 'models', 'middleware', 'helpers', 'mailers'].each do |folder|
|
|
97
99
|
next if folder == 'models' && ENV['SKIP_MODELS']
|
|
98
100
|
loaded = []
|
|
99
101
|
# Load framework core
|
|
@@ -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
|
|
@@ -210,9 +211,12 @@ files:
|
|
|
210
211
|
- app/controllers/products_controller.rb
|
|
211
212
|
- app/controllers/projects_controller.rb
|
|
212
213
|
- app/helpers/cloudinary_helper.rb
|
|
214
|
+
- app/mailers/application_mailer.rb
|
|
213
215
|
- app/middleware/auth_middleware.rb
|
|
214
216
|
- app/middleware/csrf_middleware.rb
|
|
215
217
|
- app/middleware/jwt_middleware.rb
|
|
218
|
+
- app/middleware/maintenance_middleware.rb
|
|
219
|
+
- app/models/activity_log.rb
|
|
216
220
|
- app/models/page.rb
|
|
217
221
|
- app/models/post.rb
|
|
218
222
|
- app/models/product.rb
|
|
@@ -220,6 +224,7 @@ files:
|
|
|
220
224
|
- app/models/user.rb
|
|
221
225
|
- app/views/blog_home.erb
|
|
222
226
|
- app/views/cart_index.erb
|
|
227
|
+
- app/views/cms/activity_logs.erb
|
|
223
228
|
- app/views/cms/pages_form.erb
|
|
224
229
|
- app/views/cms/pages_index.erb
|
|
225
230
|
- app/views/cms/posts_form.erb
|
|
@@ -234,6 +239,7 @@ files:
|
|
|
234
239
|
- app/views/index.erb
|
|
235
240
|
- app/views/layout.erb
|
|
236
241
|
- app/views/login.erb
|
|
242
|
+
- app/views/maintenance.erb
|
|
237
243
|
- app/views/page.erb
|
|
238
244
|
- app/views/portfolio.erb
|
|
239
245
|
- app/views/post.erb
|
|
@@ -254,7 +260,8 @@ files:
|
|
|
254
260
|
- db/data.sqlite3
|
|
255
261
|
- db/development.sqlite3
|
|
256
262
|
- db/migrations/20260502000000_create_products.rb
|
|
257
|
-
- db/
|
|
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
|
data/db/target.sqlite3
DELETED
|
Binary file
|