eksa-framework 1.1.1 โ†’ 2.2.1

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: eac9fa3b3a99369457c3dbbda0ea70f15d0f6a7c52aa0e53c64012d78746790d
4
- data.tar.gz: 3579652838460c2b91b6945ee9b54418009d18d9215bf8e87aae88545cd1eea8
3
+ metadata.gz: ba5e76573384bd8808a04847817e7237efc4dc6230d05b7c6ca732d7de32608b
4
+ data.tar.gz: 786e3dd03a18afd6f4ed632c12b52d01339136dade7575789c9c355bb086829e
5
5
  SHA512:
6
- metadata.gz: 46f87ef4e149ed0517512fd9394dfae71347cf3f9628630146da98d9e0b82435c92a487e3aaff354e650ba7b1aaecc1e9a658253004de584d7727d7d1e83e4c8
7
- data.tar.gz: 3a5390e386e690d3bc2d695c1c645d70105d99949a29be3d077ddded8b99ce1375fdcf93df6196837edfeed69164d6ce6106069fee9a5575faec6ae91847c7b1
6
+ metadata.gz: fbbabdc3eb08873aca77b657ae5c1d56590738b7b89618276e841189c810a46c0fcfc459e7aef8ed78495a0739363bbb474de7c17cb620cbe675cfd2a78a8863
7
+ data.tar.gz: 7fba997f5684df20931e3fb297d6d8cdb73c9f0b174d765b6abcfb95c60b491fbf8608ebfb60b39998e6fc8e8420000bf8518829966c132f22aae716ecbf3b6d
data/Gemfile CHANGED
@@ -1,5 +1,10 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
+ group :development, :test do
4
+ gem "rspec", "~> 3.12"
5
+ gem "rack-test", "~> 2.1"
6
+ end
7
+
3
8
  gem 'rack'
4
9
  gem 'puma'
5
10
  gem "rackup"
data/Gemfile.lock ADDED
@@ -0,0 +1,54 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ diff-lcs (1.6.2)
5
+ nio4r (2.7.5)
6
+ puma (7.2.0)
7
+ nio4r (~> 2.0)
8
+ rack (3.2.5)
9
+ rack-test (2.2.0)
10
+ rack (>= 1.3)
11
+ rackup (2.3.1)
12
+ rack (>= 3)
13
+ rspec (3.13.2)
14
+ rspec-core (~> 3.13.0)
15
+ rspec-expectations (~> 3.13.0)
16
+ rspec-mocks (~> 3.13.0)
17
+ rspec-core (3.13.6)
18
+ rspec-support (~> 3.13.0)
19
+ rspec-expectations (3.13.5)
20
+ diff-lcs (>= 1.2.0, < 2.0)
21
+ rspec-support (~> 3.13.0)
22
+ rspec-mocks (3.13.8)
23
+ diff-lcs (>= 1.2.0, < 2.0)
24
+ rspec-support (~> 3.13.0)
25
+ rspec-support (3.13.7)
26
+ sqlite3 (2.9.1-x86_64-linux-gnu)
27
+
28
+ PLATFORMS
29
+ x86_64-linux-gnu
30
+
31
+ DEPENDENCIES
32
+ puma
33
+ rack
34
+ rack-test (~> 2.1)
35
+ rackup
36
+ rspec (~> 3.12)
37
+ sqlite3
38
+
39
+ CHECKSUMS
40
+ diff-lcs (1.6.2) sha256=9ae0d2cba7d4df3075fe8cd8602a8604993efc0dfa934cff568969efb1909962
41
+ nio4r (2.7.5) sha256=6c90168e48fb5f8e768419c93abb94ba2b892a1d0602cb06eef16d8b7df1dca1
42
+ puma (7.2.0) sha256=bf8ef4ab514a4e6d4554cb4326b2004eba5036ae05cf765cfe51aba9706a72a8
43
+ rack (3.2.5) sha256=4cbd0974c0b79f7a139b4812004a62e4c60b145cba76422e288ee670601ed6d3
44
+ rack-test (2.2.0) sha256=005a36692c306ac0b4a9350355ee080fd09ddef1148a5f8b2ac636c720f5c463
45
+ rackup (2.3.1) sha256=6c79c26753778e90983761d677a48937ee3192b3ffef6bc963c0950f94688868
46
+ rspec (3.13.2) sha256=206284a08ad798e61f86d7ca3e376718d52c0bc944626b2349266f239f820587
47
+ rspec-core (3.13.6) sha256=a8823c6411667b60a8bca135364351dda34cd55e44ff94c4be4633b37d828b2d
48
+ rspec-expectations (3.13.5) sha256=33a4d3a1d95060aea4c94e9f237030a8f9eae5615e9bd85718fe3a09e4b58836
49
+ rspec-mocks (3.13.8) sha256=086ad3d3d17533f4237643de0b5c42f04b66348c28bf6b9c2d3f4a3b01af1d47
50
+ rspec-support (3.13.7) sha256=0640e5570872aafefd79867901deeeeb40b0c9875a36b983d85f54fb7381c47c
51
+ sqlite3 (2.9.1-x86_64-linux-gnu) sha256=1cbb644204ed143e5c96f6d59b5c571ba6f18b18a9dc5aa11c101187ff227afd
52
+
53
+ BUNDLED WITH
54
+ 4.0.4
data/README.md CHANGED
@@ -12,97 +12,95 @@
12
12
  ## ๐Ÿš€ Fitur Unggulan
13
13
 
14
14
  * ๐Ÿ’Ž **Modern Glassmorphism UI**: Tampilan transparan yang indah dengan Tailwind CSS & Lucide Icons.
15
- * โšก **Rack 3 Ready**: Menggunakan standar terbaru dengan penanganan header modern.
16
- * ๐Ÿ› ๏ธ **CLI Generator**: Siapkan struktur project instan dengan perintah `eksa-init`.
17
- * ๐Ÿ’พ **Auto-Migration DB**: Database SQLite otomatis dibuat saat server pertama kali dijalankan.
18
- * ๐Ÿ”” **Flash Messages**: Notifikasi animasi elegan yang kompatibel dengan cookie Rack 3.
19
- * ๐Ÿ›ค๏ธ **Simple Routing**: Sistem routing yang eksplisit dan mudah dikelola di `config.ru`.
20
- * ๐Ÿ” **Dynamic SEO Engine**: Penanganan otomatis file `robots.txt` dan `sitemap.xml` yang adaptif terhadap rute aplikasi.
21
- * ๐Ÿงฉ **Smart Response Handling**: Framework kini mampu mendeteksi dan mengeksekusi array respons Rack manual untuk konten non-HTML (XML/Plain Text).
15
+ * โšก **Rack 3 & Middleware Support**: Mendukung standar terbaru dan pembuatan pipeline middleware kustom.
16
+ * ๐Ÿ› ๏ธ **Powerful CLI**: Inisialisasi project (`eksa init`), jalankan server (`eksa run`), generate komponen, dan **auto-routing** otomatis.
17
+ * ๐Ÿ’พ **Dynamic Database Engine**: Database SQLite otomatis dengan schema yang ditentukan oleh model Anda sendiri.
18
+ * ๐Ÿงช **Built-in Testing**: Lingkungan pengujian otomatis siap pakai menggunakan RSpec dan `rack-test`.
19
+ * ๐ŸŽจ **Asset Helpers**: Library bawaan untuk pengelolaan CSS dan JS yang lebih rapi.
20
+ * ๐Ÿ” **Dynamic SEO Engine**: Penanganan otomatis file `robots.txt` dan `sitemap.xml`.
21
+ * ๐Ÿ‘ป **Aesthetic Error Pages**: Halaman 404 dengan desain Glassmorphism yang elegan secara native.
22
22
 
23
23
  ---
24
24
 
25
25
  ## ๐Ÿ› ๏ธ Instalasi Cepat
26
26
 
27
27
  ### 1. Install via Gem
28
- Anda bisa menginstal framework ini langsung dengan `gem`:
29
-
30
28
  ```bash
31
29
  gem install eksa-framework
32
30
  ```
33
31
 
34
32
  ### 2. Inisialisasi Project Baru
35
-
36
- Masuk ke folder baru, lalu jalankan generator Eksa:
37
-
38
33
  ```bash
39
- mkdir my-awesome-app
40
- cd my-awesome-app
41
- eksa-init
34
+ mkdir my-app && cd my-app
35
+ eksa init
42
36
  ```
43
37
 
44
38
  ### 3. Jalankan Server
45
-
46
39
  ```bash
47
40
  bundle install
48
- rackup config.ru
41
+ eksa run
49
42
  ```
50
43
 
51
- Buka browser dan akses `http://localhost:9292`.
52
-
53
44
  ---
54
45
 
55
46
  ## ๐Ÿ’ป Panduan Pengembangan
56
47
 
57
- ### 1. Struktur Folder
58
-
59
- Eksa memisahkan data dari logika aplikasi dengan folder `db` di level root:
60
-
61
- * `app/`: Logic & Views (Controllers, Models, Views).
62
- * `db/`: File database SQLite `eksa_app.db` (Terpisah dari app).
63
- * `lib/eksa/`: Core Engine (The magic happens here).
64
- * `public/`: File statis (CSS, Images, JS).
65
-
66
- ### 2. Menambah Route & Controller
67
-
68
- Daftarkan rute di `config.ru`:
48
+ ### 1. Konfigurasi Aplikasi (`config.ru`)
49
+ Eksa kini menggunakan blok inisialisasi untuk konfigurasi yang lebih fleksibel:
69
50
 
70
51
  ```ruby
71
- app.add_route "/profil", PagesController, :profil
52
+ app = Eksa::Application.new do |config|
53
+ config.config[:db_path] = "./db/production.db"
54
+
55
+ config.use Rack::Static, urls: ["/css", "/img"], root: "public"
56
+ config.use Rack::ShowExceptions
57
+ end
72
58
  ```
73
59
 
74
- Buat method di `app/controllers/pages_controller.rb`:
60
+ ### 2. CLI Generator
61
+ Hemat waktu dengan menggunakan generator bawaan:
75
62
 
76
- ```ruby
77
- def profil
78
- render :profil # Merujuk ke app/views/profil.html.erb
79
- end
63
+ ```bash
64
+ # Membuat controller dan view template
65
+ eksa g controller Blog
66
+
67
+ # Membuat model dan schema database
68
+ eksa g model Post
80
69
  ```
81
70
 
82
71
  ### 3. Database & Model
83
-
84
- Eksa akan otomatis membuat tabel saat Anda mendefinisikannya di `lib/eksa/model.rb`. Untuk menggunakan data di controller:
72
+ Definisikan schema tabel Anda langsung di dalam model:
85
73
 
86
74
  ```ruby
87
- @pesan = Pesan.all # Menggunakan model app/models/pesan.rb
75
+ class Post < Eksa::Model
76
+ def self.setup_schema
77
+ db.execute <<~SQL
78
+ CREATE TABLE IF NOT EXISTS posts (
79
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
80
+ title TEXT,
81
+ content TEXT
82
+ )
83
+ SQL
84
+ end
85
+ end
88
86
  ```
89
87
 
90
- ---
88
+ ### 4. Asset Helpers
89
+ Gunakan helper di dalam view untuk menyisipkan asset:
91
90
 
92
- ## ๐ŸŽจ Kustomisasi Visual
91
+ ```erb
92
+ <%= stylesheet_tag "style" %>
93
+ <%= javascript_tag "app" %>
94
+ ```
93
95
 
94
- Eksa menggunakan **Tailwind CSS** dan **Animate.css**. Anda dapat mengatur efek kaca pada class `.glass` di `app/views/layout.html.erb`:
96
+ ### 5. Menjalankan Test
97
+ Pastikan aplikasi Anda berjalan dengan benar menggunakan RSpec:
95
98
 
96
- ```css
97
- .glass {
98
- background: rgba(255, 255, 255, 0.05);
99
- backdrop-filter: blur(16px);
100
- border: 1px solid rgba(255, 255, 255, 0.1);
101
- }
99
+ ```bash
100
+ bundle exec rspec
102
101
  ```
103
102
 
104
103
  ---
105
104
 
106
105
  ## ๐Ÿ“œ Lisensi
107
-
108
106
  Proyek ini dilisensikan di bawah **MIT License**. Lihat file [LICENSE](LICENSE) untuk detail lebih lanjut.
data/app/models/pesan.rb CHANGED
@@ -1,4 +1,14 @@
1
1
  class Pesan < Eksa::Model
2
+ def self.setup_schema
3
+ db.execute <<-SQL
4
+ CREATE TABLE IF NOT EXISTS pesan (
5
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
6
+ konten TEXT,
7
+ pengirim TEXT
8
+ );
9
+ SQL
10
+ end
11
+
2
12
  def self.semua
3
13
  db.execute("SELECT * FROM pesan ORDER BY id DESC")
4
14
  end
@@ -44,7 +44,7 @@
44
44
  </div>
45
45
  <div>
46
46
  <p class="text-white/30"># 2. Inisialisasi project baru</p>
47
- <p class="text-indigo-300">eksa-init</p>
47
+ <p class="text-indigo-300">eksa init</p>
48
48
  </div>
49
49
  <div>
50
50
  <p class="text-white/30"># 3. Install dependensi</p>
@@ -52,7 +52,7 @@
52
52
  </div>
53
53
  <div>
54
54
  <p class="text-white/30"># 4. Jalankan server</p>
55
- <p class="text-indigo-300">rackup config.ru</p>
55
+ <p class="text-indigo-300">eksa run</p>
56
56
  </div>
57
57
  </div>
58
58
  </section>
@@ -74,9 +74,13 @@
74
74
  <code class="text-indigo-300 font-bold">lib/eksa/</code>
75
75
  <p class="text-xs text-white/50 mt-1">Mesin inti (Core Engine) dari Eksa Framework.</p>
76
76
  </div>
77
+ <div class="bg-white/5 p-4 rounded-xl border border-white/10">
78
+ <code class="text-indigo-300 font-bold">spec/</code>
79
+ <p class="text-xs text-white/50 mt-1">Folder testing otomatis menggunakan RSpec (disertakan saat <code class="text-indigo-200">init</code>).</p>
80
+ </div>
77
81
  <div class="bg-white/5 p-4 rounded-xl border border-white/10">
78
82
  <code class="text-indigo-300 font-bold">exe/</code>
79
- <p class="text-xs text-white/50 mt-1">Executable files seperti generator <code class="text-indigo-200">eksa-init</code>.</p>
83
+ <p class="text-xs text-white/50 mt-1">Executable files seperti CLI <code class="text-indigo-200">eksa</code>.</p>
80
84
  </div>
81
85
  </div>
82
86
  </section>
@@ -123,11 +127,13 @@
123
127
 
124
128
  <section id="build" class="mb-12 scroll-mt-24">
125
129
  <h2 class="text-2xl font-bold mb-4 flex items-center gap-2">
126
- <i data-lucide="package-check" class="w-6 h-6 text-indigo-400"></i> Build ke RubyGems
130
+ <i data-lucide="package-check" class="w-6 h-6 text-indigo-400"></i> Build & Publish
127
131
  </h2>
128
132
  <div class="bg-black/40 rounded-2xl p-6 font-mono text-sm border border-white/10">
133
+ <p class="text-white/30"># Build gem lokal</p>
129
134
  <p class="text-indigo-300">gem build eksa-framework.gemspec</p>
130
- <p class="text-indigo-300 mt-2">gem install ./eksa-framework-0.1.0.gem</p>
135
+ <p class="text-white/30 mt-4"># Publish ke RubyGems / GitHub Packages</p>
136
+ <p class="text-indigo-300">gem push eksa-framework-1.2.1.gem</p>
131
137
  </div>
132
138
  </section>
133
139
 
@@ -2,7 +2,7 @@
2
2
  <div class="flex flex-col md:flex-row justify-between items-start md:items-center mb-8 gap-4">
3
3
  <div>
4
4
  <span class="inline-block bg-indigo-500/20 text-indigo-200 text-xs font-bold px-3 py-1 rounded-full mb-3 border border-indigo-500/30">
5
- v0.1.0 Alpha
5
+ v1.2.1 Alpha
6
6
  </span>
7
7
  <h1 class="text-4xl font-extrabold tracking-tight">
8
8
  Halo, <span class="text-indigo-300"><%= @nama %></span>!
data/config.ru CHANGED
@@ -1,12 +1,13 @@
1
1
  require './lib/eksa'
2
- require './app/controllers/pages_controller'
3
-
4
- use Rack::Static, urls: ["/css", "/img"], root: "public"
5
- use Rack::ShowExceptions
6
2
 
7
3
  Dir[File.join(__dir__, 'app/controllers/*.rb')].each { |file| require_relative file }
8
4
 
9
- app = Eksa::Application.new
5
+ app = Eksa::Application.new do |config|
6
+ config.config[:db_path] = File.expand_path("./db/eksa_app.db")
7
+
8
+ config.use Rack::Static, urls: ["/css", "/img"], root: "public"
9
+ config.use Rack::ShowExceptions
10
+ end
10
11
 
11
12
  app.add_route "/", PagesController, :index
12
13
  app.add_route "/hapus", PagesController, :hapus_pesan
data/exe/eksa ADDED
@@ -0,0 +1,146 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'fileutils'
5
+
6
+ def usage
7
+ puts "\nโœจ Eksa Framework CLI โœจ"
8
+ puts "-----------------------------"
9
+ puts "Usage:"
10
+ puts " eksa init - Inisialisasi project baru"
11
+ puts " eksa g controller NAME - Generate controller baru"
12
+ puts " eksa g model NAME - Generate model baru"
13
+ puts " eksa run - Jalankan server aplikasi"
14
+ puts "-----------------------------\n"
15
+ end
16
+
17
+ def init_project
18
+ gem_root = File.expand_path("../..", __FILE__)
19
+ target_dir = Dir.pwd
20
+
21
+ puts "๐Ÿš€ Menginisialisasi project di: #{target_dir}"
22
+
23
+ items_to_init = ['app', 'lib', 'public', 'db', 'spec', 'config.ru', 'Gemfile', 'README.md']
24
+
25
+ items_to_init.each do |item|
26
+ source = File.join(gem_root, item)
27
+ destination = File.join(target_dir, item)
28
+
29
+ if File.exist?(source)
30
+ if File.directory?(source)
31
+ FileUtils.cp_r(source, target_dir)
32
+ puts " [OK] Folder '#{item}' berhasil disalin."
33
+ else
34
+ FileUtils.cp(source, destination)
35
+ puts " [OK] File '#{item}' berhasil disalin."
36
+ end
37
+ elsif item == 'db'
38
+ FileUtils.mkdir_p(destination)
39
+ puts " [OK] Folder 'db' baru berhasil dibuat."
40
+ end
41
+ end
42
+ puts "\nโœ… Inisialisasi Selesai!"
43
+ end
44
+
45
+ def generate_controller(name)
46
+ name = name.downcase
47
+ class_name = name.split('_').map(&:capitalize).join
48
+ file_path = "app/controllers/#{name}_controller.rb"
49
+ view_dir = "app/views/#{name}"
50
+
51
+ puts "๐Ÿ› ๏ธ Generating controller: #{class_name}Controller"
52
+
53
+ content = <<~RUBY
54
+ class #{class_name}Controller < Eksa::Controller
55
+ def index
56
+ render "#{name}/index"
57
+ end
58
+ end
59
+ RUBY
60
+
61
+ FileUtils.mkdir_p("app/controllers")
62
+ File.write(file_path, content)
63
+ puts " [OK] Created #{file_path}"
64
+
65
+ FileUtils.mkdir_p(view_dir)
66
+ index_view = File.join(view_dir, "index.html.erb")
67
+ File.write(index_view, "<h1>#{class_name}#index</h1>\n<p>Temukan saya di #{index_view}</p>")
68
+ puts " [OK] Created #{index_view}"
69
+
70
+ # Update config.ru with a new route
71
+ if File.exist?('config.ru')
72
+ config_content = File.read('config.ru')
73
+ route_line = %(app.add_route "/#{name}", #{class_name}Controller, :index)
74
+
75
+ unless config_content.include?(route_line)
76
+ if config_content.match?(/run app/)
77
+ new_content = config_content.gsub(/run app/, "#{route_line}\n\nrun app")
78
+ File.write('config.ru', new_content)
79
+ puts " [OK] Added route to config.ru"
80
+ else
81
+ File.open('config.ru', 'a') { |f| f.puts "\n#{route_line}" }
82
+ puts " [OK] Appended route to config.ru"
83
+ end
84
+ end
85
+ end
86
+ end
87
+
88
+ def generate_model(name)
89
+ name = name.downcase
90
+ class_name = name.split('_').map(&:capitalize).join
91
+ file_path = "app/models/#{name}.rb"
92
+
93
+ puts "๐Ÿ› ๏ธ Generating model: #{class_name}"
94
+
95
+ content = <<~RUBY
96
+ class #{class_name} < Eksa::Model
97
+ def self.setup_schema
98
+ db.execute <<~SQL
99
+ CREATE TABLE IF NOT EXISTS #{name}s (
100
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
101
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
102
+ )
103
+ SQL
104
+ end
105
+
106
+ def self.all
107
+ db.execute("SELECT * FROM #{name}s ORDER BY id DESC")
108
+ end
109
+ end
110
+ RUBY
111
+
112
+ FileUtils.mkdir_p("app/models")
113
+ File.write(file_path, content)
114
+ puts " [OK] Created #{file_path}"
115
+ end
116
+
117
+ def start_server
118
+ if File.exist?('config.ru')
119
+ puts "๐Ÿš€ Memulai Eksa Framework Server..."
120
+ exec "rackup config.ru"
121
+ else
122
+ puts "โŒ Error: config.ru tidak ditemukan. Pastikan Anda berada di root project Eksa."
123
+ exit 1
124
+ end
125
+ end
126
+
127
+ command = ARGV.shift
128
+
129
+ case command
130
+ when 'init'
131
+ init_project
132
+ when 'run'
133
+ start_server
134
+ when 'g', 'generate'
135
+ subcommand = ARGV.shift
136
+ name = ARGV.shift
137
+ if subcommand == 'controller' && name
138
+ generate_controller(name)
139
+ elsif subcommand == 'model' && name
140
+ generate_model(name)
141
+ else
142
+ usage
143
+ end
144
+ else
145
+ usage
146
+ end
@@ -15,6 +15,18 @@ module Eksa
15
15
  @request.params
16
16
  end
17
17
 
18
+ def stylesheet_tag(filename)
19
+ "<link rel='stylesheet' href='/css/#{filename}.css'>"
20
+ end
21
+
22
+ def javascript_tag(filename)
23
+ "<script src='/js/#{filename}.js'></script>"
24
+ end
25
+
26
+ def asset_path(path)
27
+ path.start_with?('/') ? path : "/#{path}"
28
+ end
29
+
18
30
  def redirect_to(url, notice: nil)
19
31
  @status = 302
20
32
  @redirect_url = url
@@ -36,7 +48,11 @@ module Eksa
36
48
  @content
37
49
  end
38
50
  else
39
- "Error: View '#{template_name}' tidak ditemukan."
51
+ "<div class='glass' style='padding: 2rem; border-radius: 1rem; color: #ff5555; background: rgba(255,0,0,0.1); backdrop-filter: blur(10px);'>
52
+ <h2 style='margin-top:0;'>โš ๏ธ View Error</h2>
53
+ <p>Template <strong>#{template_name}</strong> tidak ditemukan di:</p>
54
+ <code style='display:block; background:rgba(0,0,0,0.2); padding:0.5rem; border-radius:0.5rem;'>#{content_path}</code>
55
+ </div>"
40
56
  end
41
57
  end
42
58
  end
data/lib/eksa/model.rb CHANGED
@@ -3,23 +3,28 @@ require 'fileutils'
3
3
 
4
4
  module Eksa
5
5
  class Model
6
+ class << self
7
+ attr_accessor :database_path
8
+ end
9
+
6
10
  def self.db
7
- db_dir = File.expand_path("../../db", __dir__)
8
- db_path = File.join(db_dir, "eksa_app.db")
11
+ path = database_path || default_db_path
12
+ db_dir = File.dirname(path)
9
13
  FileUtils.mkdir_p(db_dir) unless Dir.exist?(db_dir)
10
- @db ||= SQLite3::Database.new(db_path)
11
- setup_initial_schema
14
+
15
+ @db ||= SQLite3::Database.new(path)
16
+ ensure_schema
12
17
  @db
13
18
  end
14
19
 
15
- def self.setup_initial_schema
16
- @db.execute <<-SQL
17
- CREATE TABLE IF NOT EXISTS pesan (
18
- id INTEGER PRIMARY KEY AUTOINCREMENT,
19
- konten TEXT,
20
- pengirim TEXT
21
- );
22
- SQL
20
+ def self.default_db_path
21
+ File.expand_path("../../db/eksa_app.db", __dir__)
22
+ end
23
+
24
+ def self.ensure_schema
25
+ return if @schema_initialized
26
+ @schema_initialized = true
27
+ setup_schema if respond_to?(:setup_schema)
23
28
  end
24
29
  end
25
30
  end
data/lib/eksa/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Eksa
2
- VERSION = "1.1.1"
2
+ VERSION = "2.2.1"
3
3
  end
data/lib/eksa.rb CHANGED
@@ -5,15 +5,45 @@ require_relative 'eksa/model'
5
5
 
6
6
  module Eksa
7
7
  class Application
8
+ attr_reader :config, :middlewares
9
+
8
10
  def initialize
9
11
  @routes = {}
12
+ @middlewares = []
13
+ @config = {
14
+ db_path: File.expand_path("./db/eksa_app.db")
15
+ }
16
+ yield self if block_given?
17
+ configure_framework
18
+ end
19
+
20
+ def configure_framework
21
+ Eksa::Model.database_path = @config[:db_path]
10
22
  end
11
23
 
12
24
  def add_route(path, controller_class, action)
13
25
  @routes[path] = { controller: controller_class, action: action }
14
26
  end
15
27
 
28
+ def use(middleware, *args, &block)
29
+ @middlewares << [middleware, args, block]
30
+ end
31
+
16
32
  def call(env)
33
+ @app ||= build_app
34
+ @app.call(env)
35
+ end
36
+
37
+ def build_app
38
+ builder = Rack::Builder.new
39
+ @middlewares.each do |middleware, args, block|
40
+ builder.use(middleware, *args, &block)
41
+ end
42
+ builder.run(method(:core_call))
43
+ builder.to_app
44
+ end
45
+
46
+ def core_call(env)
17
47
  request = Rack::Request.new(env)
18
48
  flash_message = request.cookies['eksa_flash']
19
49
  route = @routes[request.path_info]
@@ -38,7 +68,46 @@ module Eksa
38
68
  response.delete_cookie('eksa_flash') if flash_message
39
69
  response.finish
40
70
  else
41
- [404, { 'content-type' => 'text/html' }, ["<div style='font-family:sans-serif; text-align:center; padding-top:50px;'><h1>404</h1><p>Halaman tidak ditemukan di Eksa Framework.</p></div>"]]
71
+ html = <<~HTML
72
+ <!DOCTYPE html>
73
+ <html lang="id">
74
+ <head>
75
+ <meta charset="UTF-8">
76
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
77
+ <title>404 - Halaman Tidak Ditemukan</title>
78
+ <script src="https://cdn.tailwindcss.com"></script>
79
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css" rel="stylesheet">
80
+ <script src="https://unpkg.com/lucide@latest"></script>
81
+ <style>
82
+ body { background: radial-gradient(circle at top left, #1e1b4b 0%, #000000 100%); min-height: 100vh; color: white; font-family: sans-serif; display: flex; align-items: center; justify-content: center; overflow: hidden; margin: 0; }
83
+ .glass { background: rgba(255, 255, 255, 0.03); backdrop-filter: blur(20px); border: 1px solid rgba(255, 255, 255, 0.1); box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5); }
84
+ .glow { position: absolute; width: 400px; height: 400px; background: radial-gradient(circle, rgba(79, 70, 229, 0.15) 0%, transparent 70%); z-index: -1; filter: blur(40px); }
85
+ </style>
86
+ </head>
87
+ <body>
88
+ <div class="glow" style="top: 0; left: 0; transform: translate(-50%, -50%);"></div>
89
+ <div class="glow" style="bottom: 0; right: 0; transform: translate(50%, 50%);"></div>
90
+ <div class="glass" style="max-width: 512px; width: 100%; padding: 48px; border-radius: 40px; text-align: center; animation: zoomIn 0.8s;">
91
+ <div style="margin-bottom: 32px; position: relative; display: inline-block;">
92
+ <div style="position: absolute; inset: 0; background: rgba(79, 70, 229, 0.2); filter: blur(3xl); border-radius: 9999px;"></div>
93
+ <div style="position: relative; background: rgba(79, 70, 229, 0.2); padding: 24px; border-radius: 24px; border: 1px solid rgba(99, 102, 241, 0.3);">
94
+ <i data-lucide="ghost" style="width: 64px; height: 64px; color: #a5b4fc;"></i>
95
+ </div>
96
+ </div>
97
+ <h1 style="font-size: 80px; font-weight: 900; margin: 0; letter-spacing: -0.05em; color: white; opacity: 0.9;">404</h1>
98
+ <h2 style="font-size: 24px; font-weight: 700; margin: 16px 0; color: rgba(255, 255, 255, 0.9);">Oops! Halaman Hilang.</h2>
99
+ <p style="color: rgba(255, 255, 255, 0.5); margin-bottom: 40px; line-height: 1.6;">Sepertinya halaman yang Anda cari tidak ada atau sudah berpindah alamat. Jangan khawatir, kita bisa kembali.</p>
100
+ <div style="display: flex; flex-direction: column; gap: 16px; justify-content: center;">
101
+ <a href="/" style="padding: 16px 32px; background: white; color: black; font-weight: 700; border-radius: 16px; text-decoration: none; display: flex; items-center: center; justify-content: center; gap: 8px;">
102
+ <i data-lucide="home" style="width: 20px; height: 20px;"></i> Kembali ke Beranda
103
+ </a>
104
+ </div>
105
+ </div>
106
+ <script>lucide.createIcons();</script>
107
+ </body>
108
+ </html>
109
+ HTML
110
+ [404, { 'content-type' => 'text/html' }, [html]]
42
111
  end
43
112
  end
44
113
  end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+ describe Eksa::Application do
4
+ let(:app) do
5
+ Eksa::Application.new do |config|
6
+ config.config[:db_path] = ":memory:" # Use in-memory DB for tests
7
+ end
8
+ end
9
+
10
+ it "returns 404 for unknown routes" do
11
+ get '/unknown'
12
+ expect(last_response.status).to eq(404)
13
+ expect(last_response.body).to include("Halaman tidak ditemukan")
14
+ end
15
+
16
+ it "can add and reach routes" do
17
+ # Mock controller for testing
18
+ stub_const("TestController", Class.new(Eksa::Controller) do
19
+ def index
20
+ "Hello Test"
21
+ end
22
+ end)
23
+
24
+ app.add_route "/test", TestController, :index
25
+ get '/test'
26
+ expect(last_response.status).to eq(200)
27
+ expect(last_response.body).to eq("Hello Test")
28
+ end
29
+ end
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+ class RecurrentModel < Eksa::Model
4
+ def self.setup_schema
5
+ db.execute "CREATE TABLE IF NOT EXISTS tests (id INTEGER PRIMARY KEY)"
6
+ end
7
+ end
8
+
9
+ describe Eksa::Model do
10
+ it "does not cause infinite recursion when setup_schema calls db" do
11
+ expect { RecurrentModel.db }.not_to raise_error
12
+ end
13
+ end
@@ -0,0 +1,22 @@
1
+ require 'rack/test'
2
+ require 'rspec'
3
+
4
+ # Load the framework
5
+ require_relative '../lib/eksa'
6
+
7
+ # Set environment to test
8
+ ENV['RACK_ENV'] = 'test'
9
+
10
+ RSpec.configure do |config|
11
+ config.include Rack::Test::Methods
12
+
13
+ config.expect_with :rspec do |expectations|
14
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
15
+ end
16
+
17
+ config.mock_with :rspec do |mocks|
18
+ mocks.verify_partial_doubles = true
19
+ end
20
+
21
+ config.shared_context_metadata_behavior = :apply_to_host_groups
22
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: eksa-framework
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.1
4
+ version: 2.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - IshikawaUta
@@ -70,11 +70,12 @@ description: Framework MVC ringan dengan tema modern, sistem routing, dan auto-d
70
70
  email:
71
71
  - komikers09@gmail.com
72
72
  executables:
73
- - eksa-init
73
+ - eksa
74
74
  extensions: []
75
75
  extra_rdoc_files: []
76
76
  files:
77
77
  - Gemfile
78
+ - Gemfile.lock
78
79
  - LICENSE
79
80
  - README.md
80
81
  - app/controllers/pages_controller.rb
@@ -87,9 +88,8 @@ files:
87
88
  - app/views/kontak.html.erb
88
89
  - app/views/layout.html.erb
89
90
  - config.ru
90
- - db/eksa_app.db
91
91
  - db/setup.rb
92
- - exe/eksa-init
92
+ - exe/eksa
93
93
  - lib/eksa.rb
94
94
  - lib/eksa/controller.rb
95
95
  - lib/eksa/model.rb
@@ -97,10 +97,16 @@ files:
97
97
  - public/css/style.css
98
98
  - public/img/favicon.ico
99
99
  - public/img/logo.png
100
+ - spec/application_spec.rb
101
+ - spec/model_spec.rb
102
+ - spec/spec_helper.rb
100
103
  homepage: https://github.com/IshikawaUta/eksa-framework
101
104
  licenses:
102
105
  - MIT
103
- metadata: {}
106
+ metadata:
107
+ homepage_uri: https://github.com/IshikawaUta/eksa-framework
108
+ source_code_uri: https://github.com/IshikawaUta/eksa-framework
109
+ changelog_uri: https://github.com/IshikawaUta/eksa-framework/releases
104
110
  rdoc_options: []
105
111
  require_paths:
106
112
  - lib
data/db/eksa_app.db DELETED
Binary file
data/exe/eksa-init DELETED
@@ -1,43 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- require 'fileutils'
5
-
6
- gem_root = File.expand_path("../..", __FILE__)
7
- target_dir = Dir.pwd
8
-
9
- puts "\nโœจ Eksa Framework Generator โœจ"
10
- puts "-----------------------------"
11
- puts "๐Ÿš€ Menginisialisasi project di: #{target_dir}"
12
-
13
- items_to_init = ['app', 'lib', 'public', 'db', 'config.ru', 'Gemfile', 'README.md']
14
-
15
- items_to_init.each do |item|
16
- source = File.join(gem_root, item)
17
- destination = File.join(target_dir, item)
18
-
19
- if File.exist?(source)
20
- if File.directory?(source)
21
- FileUtils.cp_r(source, target_dir)
22
- puts " [OK] Folder '#{item}' berhasil disalin."
23
- else
24
- FileUtils.cp(source, destination)
25
- puts " [OK] File '#{item}' berhasil disalin."
26
- end
27
- else
28
- if item == 'db'
29
- FileUtils.mkdir_p(destination)
30
- puts " [OK] Folder 'db' baru berhasil dibuat."
31
- else
32
- puts " [!] Warning: Sumber '#{item}' tidak ditemukan."
33
- end
34
- end
35
- end
36
-
37
- puts "\nโœ… Inisialisasi Selesai!"
38
- puts "-----------------------------"
39
- puts "Instruksi selanjutnya:"
40
- puts "1. Jalankan: bundle install"
41
- puts "2. Jalankan server: rackup config.ru"
42
- puts "3. Buka browser: http://localhost:9292"
43
- puts "-----------------------------\n"