one-for-all-framework 4.2.0 → 4.4.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: 1634666412d07dfeae3b9416c7a27b16436ba9d1a03f726df8b245e6f6fe71f7
4
- data.tar.gz: 4240f873f851ecfccd556a7b061e2ac3c2020aa26d383b87a217506160c6295b
3
+ metadata.gz: 345583a2af3a1c7ee4eb6d923c1d87ea769a5f6e70c98537a36575d1ee89dc03
4
+ data.tar.gz: 596e7f2abe8bcf277d180e5044e9b5fb77fbfa894c894c92639d7cda3fc4136a
5
5
  SHA512:
6
- metadata.gz: efb0120858fb436c64f1ec7a92433deda88fee6dc1738661af68a0a303d8874cb4f6a5a7f2583a527ad8e9a9188b0e67b16abaf201c205044a421cb2a3780635
7
- data.tar.gz: ff176420fce31ecfec8fffcec1862e911d64f8b78478d3a073f3b317bd98fa553df7b9d05fd36b5a0c219582ec1b835a83eb854480cdeb62a624822c2df02170
6
+ metadata.gz: 9b5c07c96b28fc73548e35ecbb81113361d6b32575c4a681bf7bf0b916e2c39e4b18e34032f252c83176116a89d4377d8f1f58ee16acab90cdafb4d0ce467b03
7
+ data.tar.gz: bcb67f5526ebca56809ec9fa8009199b0911c2668445b8eb15ecd7f5995edde4fd8b33271b4168a416fc04aa87f768947d8109a1e0816a97b2620571cd1f911d
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.2.0
5
+ # ⚡ One-For-All (OFA) Framework v4.4.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)
@@ -21,6 +21,7 @@
21
21
  - **🔐 Enterprise Ready**: Built-in CSRF protection, secure session management, and input validation.
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
+ - **📡 Modern API**: Built-in JWT support and automated Swagger/OpenAPI documentation.
24
25
 
25
26
  ---
26
27
 
@@ -82,6 +83,8 @@ Boots the high-performance Eksa Server engine.
82
83
  ### 🏗️ Generators (Scaffolding)
83
84
  #### `ofa g controller NAME`
84
85
  * **Output:** `✅ Created app/controllers/blog_controller.rb`
86
+ #### `ofa g api NAME`
87
+ * **Output:** `✅ Created app/controllers/shop_controller.rb`
85
88
  #### `ofa g model NAME`
86
89
  * **Output:** `✅ Created app/models/product.rb`
87
90
  #### `ofa g post TITLE [args]`
@@ -89,6 +92,11 @@ Creates a SEO-optimized blog post with metadata.
89
92
  * **Example:** `./ofa g post "Hello World" --author Antigravity`
90
93
  * **Output:** `✅ Created app/views/posts/hello_world.erb`
91
94
 
95
+ ### 📡 API & Documentation
96
+ #### `ofa swagger`
97
+ Generates a standards-compliant `openapi.json` file.
98
+ * **Output:** `✅ Documentation saved to: openapi.json`
99
+
92
100
  ### 📂 Database Management
93
101
  #### `ofa db switch TYPE [URL]`
94
102
  Switches your database adapter on the fly.
@@ -132,6 +140,9 @@ Securely manages admin credentials.
132
140
  | `ofa console` | **Interactive REPL.** Starts a Ruby console pre-loaded with your application environment and models for testing and debugging. |
133
141
  | `ofa doctor` | **System Health Check.** Validates `.env` config, database connectivity (SQL/MongoDB), Ruby version, and dependencies. |
134
142
  | `ofa routes` | **Route Inspection.** Lists all registered routes in your application in a clean tabular format. |
143
+ | `ofa swagger` | **OpenAPI Generation.** Auto-generates `openapi.json` for your entire application. |
144
+ | `ofa task NAME` | **Run Background Task.** Executes a task defined in `lib/tasks/`. |
145
+ | `ofa test` | **Run Test Suite.** Executes all unit tests in the `test/` directory using Minitest. |
135
146
  | `ofa deploy` | **Production Deployment.** Automatically detects deployment targets (Railway/Docker/Git). |
136
147
 
137
148
  ---
@@ -142,8 +153,12 @@ Automate the creation of boilerplate code with the generator command.
142
153
  | Command | Description |
143
154
  | :--- | :--- |
144
155
  | `ofa g controller NAME` | Creates a new controller in `app/controllers/{name}_controller.rb` with a default `index` action. |
156
+ | `ofa g api NAME` | Creates a JSON-based controller in `app/controllers/{name}_controller.rb` inheriting from `ApiController`. |
145
157
  | `ofa g model NAME` | Generates a database model in `app/models/{name}.rb` integrated with the Sequel ORM. |
146
158
  | `ofa g migration NAME` | Creates a timestamped migration file in `db/migrations/`. Use this to define your schema changes. |
159
+ | `ofa g mailer NAME ACTION` | Generates a new mailer in `app/mailers/` and an ERB template in `app/views/mailers/`. |
160
+ | `ofa g task NAME` | Creates a new background task file in `lib/tasks/`. |
161
+ | `ofa g test NAME` | Generates a new Minitest unit test in `test/`. |
147
162
  | `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"` |
148
163
 
149
164
  ---
@@ -27,6 +27,10 @@ class ApplicationController
27
27
  @res.headers['Location'] = path
28
28
  end
29
29
 
30
+ def render_json(data, status: 200)
31
+ @res.json(data, status: status)
32
+ end
33
+
30
34
  # Validation Helper
31
35
  def validate!(required_params)
32
36
  missing = required_params.select { |p| params[p.to_s].nil? || params[p.to_s].empty? }
@@ -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,31 @@
1
+ begin
2
+ require 'jwt'
3
+ rescue LoadError
4
+ end
5
+
6
+ class JwtMiddleware
7
+ def initialize(app)
8
+ @app = app
9
+ end
10
+
11
+ def call(env)
12
+ auth_header = env['HTTP_AUTHORIZATION']
13
+ if auth_header && auth_header.start_with?('Bearer ')
14
+ token = auth_header.split(' ').last
15
+ begin
16
+ secret = ENV['JWT_SECRET'] || 'one-for-all-secret-key'
17
+ if defined?(JWT)
18
+ decoded_token = JWT.decode(token, secret, true, { algorithm: 'HS256' })
19
+ env['current_user_id'] = decoded_token[0]['user_id']
20
+ env['jwt_payload'] = decoded_token[0]
21
+ end
22
+ rescue JWT::DecodeError
23
+ # If token is invalid, we don't halt here, just don't set current_user_id
24
+ rescue NameError
25
+ # JWT not defined
26
+ end
27
+ end
28
+
29
+ @app.call(env)
30
+ end
31
+ end
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.2.0</div>
3
+ <div class="badge-premium">Documentation v4.4.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>
@@ -16,6 +16,9 @@
16
16
  <a href="#routing" 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">Routing System</a>
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
+ <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="#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>
19
22
  <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
23
  </nav>
21
24
  </aside>
@@ -74,24 +77,44 @@
74
77
  <td class="px-6 py-4 font-mono text-primary">./ofa routes</td>
75
78
  <td class="px-6 py-4 text-slate-600 dark:text-slate-400 text-xs">List all registered routes in a clean table.</td>
76
79
  </tr>
77
- <tr>
78
- <td class="px-6 py-4 font-mono text-primary">./ofa deploy</td>
79
- <td class="px-6 py-4 text-slate-600 dark:text-slate-400 text-xs italic">Railway/Docker/Git auto-detection for production.</td>
80
- </tr>
80
+ <tr>
81
+ <td class="px-6 py-4 font-mono text-primary">./ofa deploy</td>
82
+ <td class="px-6 py-4 text-slate-600 dark:text-slate-400 text-xs italic">Railway/Docker/Git auto-detection for production.</td>
83
+ </tr>
84
+ <tr>
85
+ <td class="px-6 py-4 font-mono text-primary">./ofa swagger</td>
86
+ <td class="px-6 py-4 text-slate-600 dark:text-slate-400 text-xs">Generate <code>openapi.json</code> for Swagger UI.</td>
87
+ </tr>
81
88
 
82
89
  <tr class="bg-slate-500/5"><td colspan="2" class="px-6 py-2 text-[10px] font-black uppercase tracking-tighter text-slate-400">Generators (Scaffolding)</td></tr>
83
- <tr>
84
- <td class="px-6 py-4 font-mono text-primary">./ofa g controller NAME</td>
85
- <td class="px-6 py-4 text-slate-600 dark:text-slate-400">Generates <code>app/controllers/{name}_controller.rb</code>.</td>
86
- </tr>
90
+ <tr>
91
+ <td class="px-6 py-4 font-mono text-primary">./ofa g controller NAME</td>
92
+ <td class="px-6 py-4 text-slate-600 dark:text-slate-400">Generates <code>app/controllers/{name}_controller.rb</code>.</td>
93
+ </tr>
94
+ <tr>
95
+ <td class="px-6 py-4 font-mono text-primary">./ofa g api NAME</td>
96
+ <td class="px-6 py-4 text-slate-600 dark:text-slate-400 text-xs">JSON-based controller inheriting from ApiController.</td>
97
+ </tr>
87
98
  <tr>
88
99
  <td class="px-6 py-4 font-mono text-primary">./ofa g model NAME</td>
89
100
  <td class="px-6 py-4 text-slate-600 dark:text-slate-400">Creates model & migration linked to Sequel/NoSQL.</td>
90
101
  </tr>
91
- <tr>
92
- <td class="px-6 py-4 font-mono text-primary">./ofa g migration NAME</td>
93
- <td class="px-6 py-4 text-slate-600 dark:text-slate-400">Creates timestamped migration in <code>db/migrations/</code>.</td>
94
- </tr>
102
+ <tr>
103
+ <td class="px-6 py-4 font-mono text-primary">./ofa g migration NAME</td>
104
+ <td class="px-6 py-4 text-slate-600 dark:text-slate-400 text-xs">Creates timestamped migration in <code>db/migrations/</code>.</td>
105
+ </tr>
106
+ <tr>
107
+ <td class="px-6 py-4 font-mono text-primary">./ofa g mailer NAME</td>
108
+ <td class="px-6 py-4 text-slate-600 dark:text-slate-400 text-xs">Generates mailer class and ERB templates.</td>
109
+ </tr>
110
+ <tr>
111
+ <td class="px-6 py-4 font-mono text-primary">./ofa g task NAME</td>
112
+ <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>
113
+ </tr>
114
+ <tr>
115
+ <td class="px-6 py-4 font-mono text-primary">./ofa g test NAME</td>
116
+ <td class="px-6 py-4 text-slate-600 dark:text-slate-400 text-xs">Generates a Minitest unit test file.</td>
117
+ </tr>
95
118
 
96
119
  <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>
97
120
  <tr>
@@ -310,6 +333,78 @@ resources :posts
310
333
  </div>
311
334
  </section>
312
335
 
336
+ <!-- API & Modern Web -->
337
+ <section id="api" class="glass-panel p-8 md:p-12 text-left bg-gradient-to-br from-primary/10 to-transparent">
338
+ <h2 class="text-3xl font-black tracking-tight mb-6 flex items-center gap-3">
339
+ <i class="fas fa-network-wired text-primary"></i> API & Modern Web
340
+ </h2>
341
+ <div class="markdown-content space-y-8">
342
+ <div>
343
+ <h4 class="font-bold text-lg mb-2">📡 JSON API Support</h4>
344
+ <p class="text-sm text-slate-600 dark:text-slate-400">Build high-performance backends for mobile apps or SPAs. OFA provides a specialized <code>ApiController</code> and JSON rendering helpers.</p>
345
+ <div class="p-4 bg-white/5 border border-white/10 rounded-2xl mt-4 font-mono text-sm">
346
+ ./ofa g api order
347
+ </div>
348
+ </div>
349
+
350
+ <div>
351
+ <h4 class="font-bold text-lg mb-2">🔐 JWT Authentication</h4>
352
+ <p class="text-sm text-slate-600 dark:text-slate-400">Decoupled frontends require token-based auth. OFA automatically generates a <code>JWT_SECRET</code> during initialization and includes a <code>JwtMiddleware</code> to verify incoming requests.</p>
353
+ <p class="text-[10px] text-slate-500 mt-2 italic">Note: Tokens are expected in the 'Authorization: Bearer <TOKEN>' header.</p>
354
+ </div>
355
+
356
+ <div>
357
+ <h4 class="font-bold text-lg mb-2">📝 Swagger/OpenAPI Documentation</h4>
358
+ <p class="text-sm text-slate-600 dark:text-slate-400">Keep your API documentation in sync with your code. The <code>ofa swagger</code> command scans your routes and generates a <code>openapi.json</code> file.</p>
359
+ <div class="p-4 bg-white/5 border border-white/10 rounded-2xl mt-4 font-mono text-sm">
360
+ ./ofa swagger
361
+ </div>
362
+ </div>
363
+ </div>
364
+ </section>
365
+
366
+ <!-- Advanced Features -->
367
+ <section id="advanced" class="glass-panel p-8 md:p-12 text-left bg-gradient-to-br from-primary/10 to-transparent">
368
+ <h2 class="text-3xl font-black tracking-tight mb-6 flex items-center gap-3">
369
+ <i class="fas fa-envelope text-primary"></i> Mailers & Tasks
370
+ </h2>
371
+ <div class="markdown-content space-y-8">
372
+ <div>
373
+ <h4 class="font-bold text-lg mb-2">📧 Email Mailers</h4>
374
+ <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>
375
+ <div class="p-4 bg-white/5 border border-white/10 rounded-2xl mt-4 font-mono text-sm">
376
+ ./ofa g mailer welcome signup
377
+ </div>
378
+ </div>
379
+
380
+ <div>
381
+ <h4 class="font-bold text-lg mb-2">⚙️ Background Tasks</h4>
382
+ <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>
383
+ <div class="p-4 bg-white/5 border border-white/10 rounded-2xl mt-4 font-mono text-sm">
384
+ ./ofa g task sync_data<br>
385
+ ./ofa task sync_data
386
+ </div>
387
+ </div>
388
+ </div>
389
+ </section>
390
+
391
+ <!-- Testing -->
392
+ <section id="testing" class="glass-panel p-8 md:p-12 text-left">
393
+ <h2 class="text-3xl font-black tracking-tight mb-6 flex items-center gap-3">
394
+ <i class="fas fa-vial text-primary"></i> Automated Testing
395
+ </h2>
396
+ <div class="markdown-content">
397
+ <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>
398
+ <div class="p-4 bg-slate-900 rounded-2xl font-mono text-sm text-green-400 mb-6">
399
+ ./ofa test
400
+ </div>
401
+ <p class="text-sm text-slate-500">To create a new test file:</p>
402
+ <div class="p-4 bg-white/5 border border-white/10 rounded-2xl mt-2 font-mono text-sm">
403
+ ./ofa g test user_profile
404
+ </div>
405
+ </div>
406
+ </section>
407
+
313
408
  <!-- Deployment -->
314
409
  <section id="deploy" class="glass-panel p-8 md:p-12 text-left border-primary/20">
315
410
  <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.2.0
3
+ Framework Version 4.4.0
4
4
  </div>
5
5
 
6
6
  <h1 class="text-5xl md:text-7xl font-black tracking-tight leading-tight">
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.2.0 "
36
+ puts "\\____/_/ /_/ |_| v4.4.0 "
26
37
  puts " "
27
38
  puts "✨ One-For-All Framework CLI ✨"
28
39
  puts "-----------------------------"
@@ -30,8 +41,12 @@ def help
30
41
  puts " ofa new NAME [TYPE] - Create a new project + automatic bundle install"
31
42
  puts " ofa init [TYPE] - Initialize project in current folder"
32
43
  puts " ofa g controller NAME - Generate a new controller"
44
+ puts " ofa g api NAME - Generate a new API controller (JSON)"
33
45
  puts " ofa g model NAME - Generate a new model"
34
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"
35
50
  puts " ofa g post TITLE - Create a new post [args: --category, --author, --image]"
36
51
  puts " ofa feature ACTION F - Toggle features: action=enable/disable, F=cms/auth/rich_text"
37
52
  puts " ofa type NAME - Set application type: portfolio, blog, landing_page, e_commerce"
@@ -45,6 +60,9 @@ def help
45
60
  puts " ofa console - Start interactive REPL session"
46
61
  puts " ofa doctor - Check system health (DB, config, gems)"
47
62
  puts " ofa routes - List all registered routes"
63
+ puts " ofa swagger - Auto-generate OpenAPI/Swagger documentation"
64
+ puts " ofa task NAME - Run a background task"
65
+ puts " ofa test - Run all application tests"
48
66
  puts " ofa run - Start the application server"
49
67
  puts " ofa deploy - Deploy project to production"
50
68
  puts "-----------------------------"
@@ -229,7 +247,7 @@ when 'init'
229
247
  puts " / __ \\/ ____/ / | "
230
248
  puts " / / / / /_ / /| | Framework "
231
249
  puts "/ /_/ / __/ / ___ | Premium MVC "
232
- puts "\\____/_/ /_/ |_| v4.2.0 "
250
+ puts "\\____/_/ /_/ |_| v4.4.0 "
233
251
  puts " "
234
252
  puts "Initializing One-For-All project as '#{app_type}' in #{PROJECT_ROOT}..."
235
253
 
@@ -260,7 +278,7 @@ when 'init'
260
278
  puts "-----------------------------\n"
261
279
 
262
280
  # Create directories
263
- dirs = ['app/controllers', 'app/models', 'app/views', 'config', 'db/migrations', 'public/css', 'public/js', 'public/images', 'public/images/uploads']
281
+ dirs = ['app/controllers', 'app/models', 'app/views', 'app/mailers', 'lib/tasks', 'test', 'config', 'db/migrations', 'public/css', 'public/js', 'public/images', 'public/images/uploads']
264
282
  dirs.each { |dir| FileUtils.mkdir_p(File.join(PROJECT_ROOT, dir)) }
265
283
 
266
284
  # Copy framework core migrations
@@ -272,11 +290,27 @@ when 'init'
272
290
  end
273
291
  end
274
292
 
293
+ # Copy framework core helpers and test assets
294
+ ['lib/task_helper.rb', 'app/mailers/application_mailer.rb', 'test/test_helper.rb'].each do |file|
295
+ src = File.join(FRAMEWORK_ROOT, file)
296
+ dest = File.join(PROJECT_ROOT, file)
297
+ FileUtils.mkdir_p(File.dirname(dest))
298
+ if File.exist?(src)
299
+ content = File.read(src)
300
+ # Inject FRAMEWORK_ROOT into test_helper.rb
301
+ if file == 'test/test_helper.rb'
302
+ content.gsub!("require_relative '../config/boot'", "$LOAD_PATH.unshift '#{FRAMEWORK_ROOT}'\nrequire 'config/boot'")
303
+ end
304
+ File.write(dest, content)
305
+ end
306
+ end
307
+
275
308
  # Create .env
276
309
  env_content = []
277
310
  env_content << "EKS_ENV=development"
278
311
  env_content << "DATABASE_URL=#{db_url}" if db_type == 'mongodb'
279
312
  env_content << "CLOUDINARY_URL=#{cloudinary_url}" if img_storage == 'cloudinary'
313
+ env_content << "JWT_SECRET=#{Array.new(32){[*'0'..'9',*'a'..'z',*'A'..'Z'].sample}.join}"
280
314
  File.write(File.join(PROJECT_ROOT, '.env'), env_content.join("\n"))
281
315
 
282
316
  # Create config.ru
@@ -294,6 +328,7 @@ when 'init'
294
328
  use EksCent::Middleware::ShowExceptions
295
329
  use EksCent::Middleware::Session, secret: EksCent.secret_key_base
296
330
  use AuthMiddleware
331
+ use JwtMiddleware
297
332
  use CSRFMiddleware
298
333
  use EksCent::Middleware::Static, root: 'public'
299
334
  use EksCent::Middleware::Logger
@@ -317,6 +352,8 @@ when 'init'
317
352
  gem 'mysql2'
318
353
  gem 'kramdown'
319
354
  gem 'kramdown-parser-gfm'
355
+ gem 'mail'
356
+ gem 'minitest'
320
357
  RUBY
321
358
  File.write(File.join(PROJECT_ROOT, 'Gemfile'), gemfile_content)
322
359
 
@@ -463,11 +500,90 @@ when 'g'
463
500
  class #{class_name} < ApplicationController
464
501
  def index
465
502
  # Logic for #{name}
503
+ render_json({ message: "Welcome to #{class_name}" })
466
504
  end
467
505
  end
468
506
  RUBY
469
507
  File.write(file_name, content)
470
508
  puts "Created: #{file_name}"
509
+ elsif sub == 'api' && name
510
+ class_name = name.capitalize + "Controller"
511
+ file_name = File.join(PROJECT_ROOT, "app/controllers/#{name.downcase}_controller.rb")
512
+ content = <<~RUBY
513
+ require_relative 'api_controller'
514
+
515
+ class #{class_name} < ApiController
516
+ # GET /api/#{name.downcase}
517
+ def index
518
+ render_json({
519
+ status: 'success',
520
+ message: "API endpoint for #{name} ready",
521
+ data: []
522
+ })
523
+ end
524
+
525
+ # POST /api/#{name.downcase}
526
+ def create
527
+ # TODO: Implement creation logic
528
+ render_json({ status: 'success', message: "#{name} created" }, status: 201)
529
+ end
530
+ end
531
+ RUBY
532
+ File.write(file_name, content)
533
+ puts "Created API Controller: #{file_name}"
534
+ puts "💡 Don't forget to register it in config/routes.rb"
535
+ elsif sub == 'mailer' && name
536
+ action = ARGV.shift || 'welcome'
537
+ class_name = name.capitalize + "Mailer"
538
+ file_name = File.join(PROJECT_ROOT, "app/mailers/#{name.downcase}_mailer.rb")
539
+ content = <<~RUBY
540
+ class #{class_name} < ApplicationMailer
541
+ def #{action}(user)
542
+ mail(
543
+ to: user.email,
544
+ subject: "Welcome to our platform!",
545
+ template: "#{name.downcase}_mailer/#{action}",
546
+ locals: { user: user }
547
+ )
548
+ end
549
+ end
550
+ RUBY
551
+ File.write(file_name, content)
552
+
553
+ # Create template
554
+ template_dir = File.join(PROJECT_ROOT, "app/views/mailers/#{name.downcase}_mailer")
555
+ FileUtils.mkdir_p(template_dir)
556
+ template_file = File.join(template_dir, "#{action}.erb")
557
+ File.write(template_file, "<h1>Hello <%= @user.username %></h1>\n<p>Welcome to One-For-All Framework!</p>")
558
+
559
+ puts "Created Mailer: #{file_name}"
560
+ puts "Created Template: #{template_file}"
561
+ elsif sub == 'task' && name
562
+ dir_path = File.join(PROJECT_ROOT, "lib/tasks")
563
+ FileUtils.mkdir_p(dir_path)
564
+ file_name = File.join(dir_path, "#{name.downcase}.rb")
565
+ content = <<~RUBY
566
+ desc "Description for #{name} task"
567
+ task :#{name.downcase} do
568
+ puts "Running task: #{name}"
569
+ # Your background job logic here
570
+ end
571
+ RUBY
572
+ File.write(file_name, content)
573
+ puts "Created Task: #{file_name}"
574
+ elsif sub == 'test' && name
575
+ file_name = File.join(PROJECT_ROOT, "test/#{name.downcase}_test.rb")
576
+ content = <<~RUBY
577
+ require_relative 'test_helper'
578
+
579
+ class #{name.capitalize}Test < Minitest::Test
580
+ def test_example
581
+ assert true
582
+ end
583
+ end
584
+ RUBY
585
+ File.write(file_name, content)
586
+ puts "Created Test: #{file_name}"
471
587
  elsif sub == 'model' && name
472
588
  class_name = name.capitalize
473
589
  file_name = File.join(PROJECT_ROOT, "app/models/#{name.downcase}.rb")
@@ -504,7 +620,7 @@ when 'g'
504
620
  File.write(file_name, content)
505
621
  puts "Created post: #{file_name}"
506
622
  else
507
- puts "Usage: ofa g [controller|model|migration|post] NAME"
623
+ puts "Usage: ofa g [controller|api|model|migration|mailer|task|test|post] NAME"
508
624
  end
509
625
 
510
626
  when 'feature'
@@ -514,7 +630,8 @@ when 'feature'
514
630
  config_path = File.join(PROJECT_ROOT, 'config', 'features.json')
515
631
  config = JSON.parse(File.read(config_path))
516
632
  config[feature] = (action == 'enable')
517
- File.write(config_path, JSON.pretty_generate(config))
633
+ File.open("#{config_path}.tmp", 'w') { |f| f.write(JSON.pretty_generate(config)) }
634
+ File.rename("#{config_path}.tmp", config_path)
518
635
  puts "Feature '#{feature}' has been #{action}d."
519
636
 
520
637
  if action == 'enable' && feature == 'cms' && !config['auth']
@@ -533,7 +650,8 @@ when 'type'
533
650
  config_path = File.join(PROJECT_ROOT, 'config', 'features.json')
534
651
  config = JSON.parse(File.read(config_path))
535
652
  config['type'] = name
536
- File.write(config_path, JSON.pretty_generate(config))
653
+ File.open("#{config_path}.tmp", 'w') { |f| f.write(JSON.pretty_generate(config)) }
654
+ File.rename("#{config_path}.tmp", config_path)
537
655
  puts "Application type set to '#{name}'."
538
656
 
539
657
  when 'theme'
@@ -546,7 +664,8 @@ when 'theme'
546
664
  config_path = File.join(PROJECT_ROOT, 'config', 'features.json')
547
665
  config = JSON.parse(File.read(config_path))
548
666
  config['theme'] = name
549
- File.write(config_path, JSON.pretty_generate(config))
667
+ File.open("#{config_path}.tmp", 'w') { |f| f.write(JSON.pretty_generate(config)) }
668
+ File.rename("#{config_path}.tmp", config_path)
550
669
  puts "Application theme set to '#{name}'."
551
670
 
552
671
  when 'storage'
@@ -609,7 +728,8 @@ when 'db'
609
728
  env_lines = File.exist?(env_path) ? File.readlines(env_path) : []
610
729
  env_lines.reject! { |l| l.start_with?('DATABASE_URL=') }
611
730
  env_lines << "DATABASE_URL=#{db_name}\n"
612
- File.write(env_path, env_lines.join)
731
+ File.open("#{env_path}.tmp", 'w') { |f| f.write(env_lines.join) }
732
+ File.rename("#{env_path}.tmp", env_path)
613
733
  puts "✅ Connection string saved to .env"
614
734
  end
615
735
  when 'sqlite'
@@ -617,7 +737,8 @@ when 'db'
617
737
  else
618
738
  config = { "adapter" => type, "host" => "localhost", "user" => "root", "password" => "", "database" => db_name || "one_for_all" }
619
739
  end
620
- File.write(config_path, JSON.pretty_generate(config))
740
+ File.open("#{config_path}.tmp", 'w') { |f| f.write(JSON.pretty_generate(config)) }
741
+ File.rename("#{config_path}.tmp", config_path)
621
742
  puts "Switched to #{type} mode."
622
743
  when 'migrate'
623
744
  run_migrations
@@ -702,7 +823,7 @@ when 'console'
702
823
  puts " / __ \\/ ____/ / | "
703
824
  puts " / / / / /_ / /| | Framework "
704
825
  puts "/ /_/ / __/ / ___ | Console (REPL) "
705
- puts "\\____/_/ /_/ |_| v4.2.0 "
826
+ puts "\\____/_/ /_/ |_| v4.4.0 "
706
827
  puts " "
707
828
  puts "✨ Loading environment... (Type 'exit' to quit)"
708
829
 
@@ -736,6 +857,53 @@ when 'routes'
736
857
  end
737
858
  puts "--------------------------------------------------------"
738
859
 
860
+ when 'swagger'
861
+ ensure_initialized!
862
+ puts "📝 Generating OpenAPI 3.0 documentation..."
863
+
864
+ Object.const_set(:APP_ROOT, PROJECT_ROOT) unless defined?(APP_ROOT)
865
+ ENV['EKS_ENV'] = 'test'
866
+ require File.join(FRAMEWORK_ROOT, 'config', 'boot')
867
+
868
+ openapi = {
869
+ openapi: "3.0.0",
870
+ info: {
871
+ title: "#{File.basename(PROJECT_ROOT)} API",
872
+ version: "1.0.0",
873
+ description: "Auto-generated by One-For-All Framework"
874
+ },
875
+ paths: {}
876
+ }
877
+
878
+ if defined?(ROUTES)
879
+ routes_hash = ROUTES.instance_variable_get(:@routes)
880
+ routes_hash.each do |verb, list|
881
+ list.each do |r|
882
+ path = r[:path].gsub(/:(\w+)/, '{\1}') # Convert :id to {id}
883
+ openapi[:paths][path] ||= {}
884
+ openapi[:paths][path][verb.to_s.downcase] = {
885
+ summary: "Endpoint for #{path}",
886
+ responses: {
887
+ "200" => { description: "Successful response" }
888
+ }
889
+ }
890
+
891
+ # Add parameters if dynamic path
892
+ if path.include?('{')
893
+ params = path.scan(/\{(\w+)\}/).flatten
894
+ openapi[:paths][path][verb.to_s.downcase][:parameters] = params.map do |p|
895
+ { name: p, in: "path", required: true, schema: { type: "string" } }
896
+ end
897
+ end
898
+ end
899
+ end
900
+ end
901
+
902
+ output_path = File.join(PROJECT_ROOT, 'openapi.json')
903
+ File.write(output_path, JSON.pretty_generate(openapi))
904
+ puts "✅ Documentation saved to: #{output_path}"
905
+ puts "💡 You can paste this into https://editor.swagger.io to view it."
906
+
739
907
  when 'run'
740
908
  ensure_initialized!
741
909
  # Check for config.ru
@@ -767,6 +935,63 @@ when 'deploy'
767
935
  puts " This framework supports deployment via Docker or Git Push."
768
936
  end
769
937
 
938
+ when 'task'
939
+ ensure_initialized!
940
+ name = ARGV.shift
941
+ if name.nil?
942
+ puts "Usage: ofa task NAME"
943
+ # List available tasks
944
+ tasks_dir = File.join(PROJECT_ROOT, 'lib/tasks')
945
+ if Dir.exist?(tasks_dir)
946
+ puts "\nAvailable tasks:"
947
+ Dir.glob(File.join(tasks_dir, '*.rb')).each do |f|
948
+ puts " - #{File.basename(f, '.rb')}"
949
+ end
950
+ end
951
+ exit 1
952
+ end
953
+
954
+ # Load framework & app
955
+ Object.const_set(:APP_ROOT, PROJECT_ROOT) unless defined?(APP_ROOT)
956
+ require File.join(FRAMEWORK_ROOT, 'config', 'boot')
957
+ require File.join(FRAMEWORK_ROOT, 'lib', 'task_helper')
958
+
959
+ # Load all tasks in lib/tasks
960
+ Dir.glob(File.join(PROJECT_ROOT, 'lib/tasks/*.rb')).each { |f| require f }
961
+
962
+ run_task(name)
963
+
964
+ when 'test'
965
+ ensure_initialized!
966
+ puts "🧪 Running tests..."
967
+ puts "------------------------------------------"
968
+
969
+ # Set environment to test
970
+ ENV['EKS_ENV'] = 'test'
971
+ Object.const_set(:APP_ROOT, PROJECT_ROOT) unless defined?(APP_ROOT)
972
+
973
+ # Add project root to load path
974
+ $LOAD_PATH.unshift PROJECT_ROOT
975
+ $LOAD_PATH.unshift File.join(PROJECT_ROOT, 'test')
976
+
977
+ # Find all tests (support both _test.rb and _spec.rb)
978
+ if ARGV.empty?
979
+ test_files = Dir.glob(File.join(PROJECT_ROOT, 'test/**/{*_test.rb,*_spec.rb}'))
980
+ else
981
+ test_files = ARGV.map { |arg| File.expand_path(arg, PROJECT_ROOT) }
982
+ end
983
+
984
+ if test_files.empty?
985
+ puts "❌ No tests found in test/ directory."
986
+ exit 1
987
+ end
988
+
989
+ # Run them by requiring
990
+ test_files.each { |f| require f }
991
+
992
+ # Trigger EksaMination execution if available
993
+ EksaMination.run if defined?(EksaMination)
994
+
770
995
  else
771
996
  help
772
997
  end
data/config/boot.rb CHANGED
@@ -4,6 +4,10 @@ Bundler.require(:default)
4
4
  require 'eks-cent'
5
5
  require 'json'
6
6
  require 'kramdown'
7
+ begin
8
+ require 'jwt'
9
+ rescue LoadError
10
+ end
7
11
 
8
12
  # Basic project structure constants
9
13
  APP_ROOT ||= File.expand_path('..', __dir__)
@@ -75,6 +79,12 @@ module EksCent
75
79
  @headers['Content-Type'] ||= 'text/html'
76
80
  @body << result
77
81
  end
82
+
83
+ def json(data, status: 200)
84
+ @status = status
85
+ @headers['Content-Type'] = 'application/json'
86
+ @body << data.to_json
87
+ end
78
88
  end
79
89
  end
80
90
 
@@ -83,7 +93,7 @@ require_relative 'database'
83
93
 
84
94
  # Autoloading (Framework Core first, then APP_ROOT)
85
95
  framework_app = File.expand_path('../app', __dir__)
86
- ['controllers', 'models', 'middleware', 'helpers'].each do |folder|
96
+ ['controllers', 'models', 'middleware', 'helpers', 'mailers'].each do |folder|
87
97
  next if folder == 'models' && ENV['SKIP_MODELS']
88
98
  loaded = []
89
99
  # Load framework core
data/db/data.sqlite3 CHANGED
Binary file
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.2.0
4
+ version: 4.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ishikawa Uta
@@ -169,6 +169,20 @@ dependencies:
169
169
  - - "~>"
170
170
  - !ruby/object:Gem::Version
171
171
  version: '1.1'
172
+ - !ruby/object:Gem::Dependency
173
+ name: jwt
174
+ requirement: !ruby/object:Gem::Requirement
175
+ requirements:
176
+ - - "~>"
177
+ - !ruby/object:Gem::Version
178
+ version: '2.10'
179
+ type: :runtime
180
+ prerelease: false
181
+ version_requirements: !ruby/object:Gem::Requirement
182
+ requirements:
183
+ - - "~>"
184
+ - !ruby/object:Gem::Version
185
+ version: '2.10'
172
186
  description: One-For-All is a high-performance Ruby web framework built for speed
173
187
  and aesthetics. It features a built-in CMS, premium Glassmorphism design, and supports
174
188
  multiple databases including SQLite, MySQL, and MongoDB.
@@ -196,8 +210,10 @@ files:
196
210
  - app/controllers/products_controller.rb
197
211
  - app/controllers/projects_controller.rb
198
212
  - app/helpers/cloudinary_helper.rb
213
+ - app/mailers/application_mailer.rb
199
214
  - app/middleware/auth_middleware.rb
200
215
  - app/middleware/csrf_middleware.rb
216
+ - app/middleware/jwt_middleware.rb
201
217
  - app/models/page.rb
202
218
  - app/models/post.rb
203
219
  - app/models/product.rb
@@ -239,7 +255,6 @@ files:
239
255
  - db/data.sqlite3
240
256
  - db/development.sqlite3
241
257
  - db/migrations/20260502000000_create_products.rb
242
- - db/target.sqlite3
243
258
  - ofa
244
259
  - public/css/cms.css
245
260
  - public/images/logo.jpg
data/db/target.sqlite3 DELETED
Binary file