eksa-framework 3.3.3 → 3.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/.eksa.json +13 -0
- data/.env.example +8 -0
- data/Gemfile +5 -1
- data/Gemfile.lock +20 -0
- data/README.md +52 -51
- data/_posts/2026-03-15-welcome-to-eksa-framework.md +132 -129
- data/app/views/about.html.erb +30 -10
- data/app/views/docs.html.erb +58 -80
- data/app/views/edit.html.erb +1 -1
- data/app/views/index.html.erb +2 -2
- data/db/eksa_app.db +0 -0
- data/db/setup.rb +4 -4
- data/exe/eksa +204 -1
- data/lib/eksa/auth_controller.rb +50 -0
- data/lib/eksa/cms_controller.rb +110 -0
- data/lib/eksa/controller.rb +32 -6
- data/lib/eksa/database/mongo_adapter.rb +154 -0
- data/lib/eksa/database/sqlite_adapter.rb +26 -0
- data/lib/eksa/database.rb +47 -0
- data/lib/eksa/markdown_post.rb +11 -4
- data/lib/eksa/model.rb +2 -16
- data/lib/eksa/user.rb +47 -0
- data/lib/eksa/version.rb +1 -1
- data/lib/eksa/views/auth/login.html.erb +44 -0
- data/lib/eksa/views/auth/register.html.erb +53 -0
- data/lib/eksa/views/cms/edit.html.erb +63 -0
- data/lib/eksa/views/cms/index.html.erb +83 -0
- data/lib/eksa.rb +48 -4
- metadata +72 -3
data/app/views/docs.html.erb
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
</div>
|
|
7
7
|
<h1 class="text-4xl font-extrabold tracking-tight">Dokumentasi <span class="text-indigo-300">Eksa</span></h1>
|
|
8
8
|
</div>
|
|
9
|
-
<p class="text-white/60 text-lg">Panduan profesional instalasi dan pengembangan aplikasi menggunakan Eksa Framework.</p>
|
|
9
|
+
<p class="text-white/60 text-lg">Panduan profesional instalasi dan pengembangan aplikasi menggunakan Eksa Framework v3.5.0.</p>
|
|
10
10
|
</div>
|
|
11
11
|
|
|
12
12
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-8 mb-12">
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
<h3 class="font-bold text-indigo-300 uppercase text-xs tracking-widest">Memulai</h3>
|
|
15
15
|
<ul class="space-y-2 text-sm">
|
|
16
16
|
<li><a href="#instalasi" class="hover:text-indigo-300 transition flex items-center gap-2"><i data-lucide="download" class="w-3 h-3"></i> Instalasi Gem</a></li>
|
|
17
|
+
<li><a href="#keamanan" class="hover:text-indigo-300 transition flex items-center gap-2"><i data-lucide="shield" class="w-3 h-3"></i> Setup Keamanan</a></li>
|
|
17
18
|
<li><a href="#struktur" class="hover:text-indigo-300 transition flex items-center gap-2"><i data-lucide="folder" class="w-3 h-3"></i> Struktur Project</a></li>
|
|
18
19
|
</ul>
|
|
19
20
|
</div>
|
|
@@ -21,15 +22,16 @@
|
|
|
21
22
|
<h3 class="font-bold text-indigo-300 uppercase text-xs tracking-widest">Pengembangan</h3>
|
|
22
23
|
<ul class="space-y-2 text-sm">
|
|
23
24
|
<li><a href="#routing" class="hover:text-indigo-300 transition flex items-center gap-2"><i data-lucide="git-branch" class="w-3 h-3"></i> Routing System</a></li>
|
|
24
|
-
<li><a href="#
|
|
25
|
+
<li><a href="#database" class="hover:text-indigo-300 transition flex items-center gap-2"><i data-lucide="database" class="w-3 h-3"></i> Agnostic Database</a></li>
|
|
26
|
+
<li><a href="#jit" class="hover:text-indigo-300 transition flex items-center gap-2"><i data-lucide="zap" class="w-3 h-3"></i> JIT Schema</a></li>
|
|
25
27
|
</ul>
|
|
26
28
|
</div>
|
|
27
29
|
<div class="space-y-4">
|
|
28
30
|
<h3 class="font-bold text-indigo-300 uppercase text-xs tracking-widest">Fitur Core</h3>
|
|
29
31
|
<ul class="space-y-2 text-sm">
|
|
30
|
-
<li><a href="#database" class="hover:text-indigo-300 transition flex items-center gap-2"><i data-lucide="database" class="w-3 h-3"></i> Auto-Migration</a></li>
|
|
31
|
-
<li><a href="#flash" class="hover:text-indigo-300 transition flex items-center gap-2"><i data-lucide="bell" class="w-3 h-3"></i> Flash UI</a></li>
|
|
32
32
|
<li><a href="#blog" class="hover:text-indigo-300 transition flex items-center gap-2"><i data-lucide="pen-tool" class="w-3 h-3"></i> Markdown Blog</a></li>
|
|
33
|
+
<li><a href="#cms" class="hover:text-indigo-300 transition flex items-center gap-2"><i data-lucide="layout-dashboard" class="w-3 h-3"></i> CMS & Auth</a></li>
|
|
34
|
+
<li><a href="#seo" class="hover:text-indigo-300 transition flex items-center gap-2"><i data-lucide="search" class="w-3 h-3"></i> SEO & JSON-LD</a></li>
|
|
33
35
|
</ul>
|
|
34
36
|
</div>
|
|
35
37
|
</div>
|
|
@@ -48,123 +50,99 @@
|
|
|
48
50
|
<p class="text-indigo-300">eksa init</p>
|
|
49
51
|
</div>
|
|
50
52
|
<div>
|
|
51
|
-
<p class="text-white/30"># 3.
|
|
52
|
-
<p class="text-indigo-300">bundle install</p>
|
|
53
|
-
</div>
|
|
54
|
-
<div>
|
|
55
|
-
<p class="text-white/30"># 4. Jalankan server</p>
|
|
56
|
-
<p class="text-indigo-300">eksa run</p>
|
|
53
|
+
<p class="text-white/30"># 3. Jalankan server</p>
|
|
54
|
+
<p class="text-indigo-300">bundle install && eksa run</p>
|
|
57
55
|
</div>
|
|
58
56
|
</div>
|
|
59
57
|
</section>
|
|
60
58
|
|
|
61
|
-
<section id="
|
|
59
|
+
<section id="keamanan" class="mb-12 scroll-mt-24">
|
|
62
60
|
<h2 class="text-2xl font-bold mb-4 flex items-center gap-2">
|
|
63
|
-
<i data-lucide="
|
|
61
|
+
<i data-lucide="shield-check" class="w-6 h-6 text-indigo-400"></i> Setup Keamanan (.env)
|
|
64
62
|
</h2>
|
|
65
|
-
<
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
</
|
|
70
|
-
<
|
|
71
|
-
<code class="text-indigo-300 font-bold">db/</code>
|
|
72
|
-
<p class="text-xs text-white/50 mt-1">Lokasi penyimpanan database SQLite (Terpisah dari app).</p>
|
|
73
|
-
</div>
|
|
74
|
-
<div class="bg-white/5 p-4 rounded-xl border border-white/10">
|
|
75
|
-
<code class="text-indigo-300 font-bold">lib/eksa/</code>
|
|
76
|
-
<p class="text-xs text-white/50 mt-1">Mesin inti (Core Engine) dari Eksa Framework.</p>
|
|
77
|
-
</div>
|
|
78
|
-
<div class="bg-white/5 p-4 rounded-xl border border-white/10">
|
|
79
|
-
<code class="text-indigo-300 font-bold">spec/</code>
|
|
80
|
-
<p class="text-xs text-white/50 mt-1">Folder testing otomatis menggunakan RSpec (disertakan saat <code class="text-indigo-200">init</code>).</p>
|
|
81
|
-
</div>
|
|
82
|
-
<div class="bg-white/5 p-4 rounded-xl border border-white/10">
|
|
83
|
-
<code class="text-indigo-300 font-bold">_posts/</code>
|
|
84
|
-
<p class="text-xs text-white/50 mt-1">Pusat file markdown untuk konten blog aplikasi.</p>
|
|
85
|
-
</div>
|
|
86
|
-
<div class="bg-white/5 p-4 rounded-xl border border-white/10">
|
|
87
|
-
<code class="text-indigo-300 font-bold">exe/</code>
|
|
88
|
-
<p class="text-xs text-white/50 mt-1">Executable files seperti CLI <code class="text-indigo-200">eksa</code>.</p>
|
|
89
|
-
</div>
|
|
63
|
+
<p class="text-white/60 mb-4 text-sm">Gunakan file <code class="text-indigo-200">.env</code> untuk menyimpan kredensial sensitif seperti URI MongoDB Atlas dan Secret Session.</p>
|
|
64
|
+
<div class="bg-black/40 rounded-2xl p-6 font-mono text-sm border border-white/10 space-y-2">
|
|
65
|
+
<p class="text-white/30"># Salin template example</p>
|
|
66
|
+
<p class="text-indigo-300">cp .env.example .env</p>
|
|
67
|
+
<p class="text-white/30 mt-4"># Isi EKSA_MONGODB_URI di dalam .env</p>
|
|
68
|
+
<p class="text-indigo-300">EKSA_MONGODB_URI="mongodb+srv://..."</p>
|
|
90
69
|
</div>
|
|
91
70
|
</section>
|
|
92
71
|
|
|
93
|
-
<section id="
|
|
72
|
+
<section id="database" class="mb-12 scroll-mt-24">
|
|
94
73
|
<h2 class="text-2xl font-bold mb-4 flex items-center gap-2">
|
|
95
|
-
<i data-lucide="
|
|
74
|
+
<i data-lucide="database" class="w-6 h-6 text-indigo-400"></i> Agnostic Database Engine
|
|
96
75
|
</h2>
|
|
97
|
-
<p class="text-white/60 mb-4 text-sm">
|
|
98
|
-
<div class="bg-black/40 rounded-2xl p-6 font-mono text-sm border border-white/10">
|
|
99
|
-
<
|
|
100
|
-
|
|
101
|
-
|
|
76
|
+
<p class="text-white/60 mb-4 text-sm">Pindah dari SQLite lokal ke cloud MongoDB Atlas secara instan melalui CLI.</p>
|
|
77
|
+
<div class="bg-black/40 rounded-2xl p-6 font-mono text-sm border border-white/10 space-y-4">
|
|
78
|
+
<div>
|
|
79
|
+
<p class="text-white/30"># Ganti tipe database</p>
|
|
80
|
+
<p class="text-indigo-300">eksa db switch mongo</p>
|
|
81
|
+
</div>
|
|
82
|
+
<div>
|
|
83
|
+
<p class="text-white/30"># Migrasi data otomatis (SQLite -> Mongo)</p>
|
|
84
|
+
<p class="text-indigo-300">eksa db migrate --from sqlite --to mongo</p>
|
|
85
|
+
</div>
|
|
102
86
|
</div>
|
|
103
87
|
</section>
|
|
104
88
|
|
|
105
|
-
<section id="
|
|
89
|
+
<section id="jit" class="mb-12 scroll-mt-24">
|
|
106
90
|
<h2 class="text-2xl font-bold mb-4 flex items-center gap-2">
|
|
107
|
-
<i data-lucide="
|
|
91
|
+
<i data-lucide="zap" class="w-6 h-6 text-indigo-400"></i> Just-In-Time Schema Initialization
|
|
108
92
|
</h2>
|
|
109
|
-
<p class="text-white/60 mb-4 text-sm">
|
|
93
|
+
<p class="text-white/60 mb-4 text-sm">Anda tidak perlu lagi menjalankan migrasi manual. Eksa akan mendeteksi dan membuat tabel/koleksi secara otomatis tepat saat model pertama kali diakses.</p>
|
|
110
94
|
<div class="bg-black/40 rounded-2xl p-6 font-mono text-sm border border-white/10">
|
|
111
|
-
<p class="text-
|
|
112
|
-
<p class="text-
|
|
113
|
-
<p class="text-indigo-300">
|
|
95
|
+
<p class="text-white/30"># Definisikan saja di model</p>
|
|
96
|
+
<p class="text-indigo-300">class Post < Eksa::Model</p>
|
|
97
|
+
<p class="text-indigo-300 italic ml-4">def self.setup_schema</p>
|
|
98
|
+
<p class="text-indigo-300 italic ml-8">db.execute "CREATE TABLE IF NOT EXISTS ..."</p>
|
|
99
|
+
<p class="text-indigo-300 italic ml-4">end</p>
|
|
100
|
+
<p class="text-indigo-300">main</p>
|
|
114
101
|
</div>
|
|
115
|
-
<p class="text-xs text-white/40 mt-3 italic">* Database tersimpan secara persisten di folder /db/eksa_app.db</p>
|
|
116
102
|
</section>
|
|
117
103
|
|
|
118
|
-
<section id="
|
|
104
|
+
<section id="blog" class="mb-12 scroll-mt-24">
|
|
119
105
|
<h2 class="text-2xl font-bold mb-4 flex items-center gap-2">
|
|
120
|
-
<i data-lucide="
|
|
106
|
+
<i data-lucide="pen-tool" class="w-6 h-6 text-indigo-400"></i> Markdown Blog Engine
|
|
121
107
|
</h2>
|
|
122
|
-
<p class="text-white/60 mb-4 text-sm">
|
|
108
|
+
<p class="text-white/60 mb-4 text-sm">Kelola konten blog dengan file Markdown. Mendukung Front Matter YAML, Syntax Highlighting, dan SEO otomatis.</p>
|
|
123
109
|
<div class="bg-black/40 rounded-2xl p-6 font-mono text-sm border border-white/10">
|
|
124
|
-
<p class="text-white/30">#
|
|
125
|
-
<p class="text-indigo-300">
|
|
126
|
-
</div>
|
|
127
|
-
<div class="mt-4 p-4 bg-emerald-500/10 border border-emerald-500/20 rounded-xl flex items-center gap-3">
|
|
128
|
-
<i data-lucide="info" class="w-5 h-5 text-emerald-400"></i>
|
|
129
|
-
<p class="text-xs text-emerald-200/80">Sistem Flash Eksa menggunakan cookie sementara yang otomatis dihapus setelah notifikasi muncul (Standar Rack 3).</p>
|
|
110
|
+
<p class="text-white/30"># Generate post baru</p>
|
|
111
|
+
<p class="text-indigo-300">eksa g post "Judul Postingan" --category "Tech" --image "url.jpg"</p>
|
|
130
112
|
</div>
|
|
131
113
|
</section>
|
|
132
114
|
|
|
133
|
-
<section id="
|
|
115
|
+
<section id="cms" class="mb-12 scroll-mt-24">
|
|
134
116
|
<h2 class="text-2xl font-bold mb-4 flex items-center gap-2">
|
|
135
|
-
<i data-lucide="
|
|
117
|
+
<i data-lucide="layout-dashboard" class="w-6 h-6 text-indigo-400"></i> CMS Dashboard & Auth
|
|
136
118
|
</h2>
|
|
137
|
-
<p class="text-white/60 mb-4 text-sm">
|
|
119
|
+
<p class="text-white/60 mb-4 text-sm">Gunakan panel admin terintegrasi untuk mengelola postingan, status publikasi, dan keamanan akun.</p>
|
|
138
120
|
<div class="bg-black/40 rounded-2xl p-6 font-mono text-sm border border-white/10">
|
|
139
|
-
<p class="text-white/30">#
|
|
140
|
-
<p class="text-indigo-300">eksa
|
|
141
|
-
<p class="text-
|
|
142
|
-
<p class="text-white/
|
|
143
|
-
|
|
144
|
-
<div class="mt-4 p-4 bg-indigo-500/10 border border-indigo-500/20 rounded-xl flex items-center gap-3">
|
|
145
|
-
<i data-lucide="zap" class="w-5 h-5 text-indigo-400"></i>
|
|
146
|
-
<p class="text-xs text-indigo-200/80">Sudah terintegrasi dengan Prism.js untuk highlighting kode otomatis dan tombol copy!</p>
|
|
121
|
+
<p class="text-white/30"># Aktivasi fitur</p>
|
|
122
|
+
<p class="text-indigo-300">eksa feature enable auth</p>
|
|
123
|
+
<p class="text-indigo-300">eksa feature enable cms</p>
|
|
124
|
+
<p class="text-white/30 mt-4"># Akses Dashboard</p>
|
|
125
|
+
<p class="text-white/70">http://localhost:9292/cms</p>
|
|
147
126
|
</div>
|
|
148
127
|
</section>
|
|
149
128
|
|
|
150
|
-
<section id="
|
|
129
|
+
<section id="seo" class="mb-12 scroll-mt-24">
|
|
151
130
|
<h2 class="text-2xl font-bold mb-4 flex items-center gap-2">
|
|
152
|
-
<i data-lucide="
|
|
131
|
+
<i data-lucide="search" class="w-6 h-6 text-indigo-400"></i> SEO & JSON-LD (Structured Data)
|
|
153
132
|
</h2>
|
|
154
|
-
<
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
<p class="text-indigo-300">gem push eksa-framework-3.3.3.gem</p>
|
|
133
|
+
<p class="text-white/60 mb-4 text-sm">Eksa v3.5.0 secara otomatis menyematkan data terstruktur untuk ranking pencarian yang lebih baik.</p>
|
|
134
|
+
<div class="bg-white/5 p-4 rounded-xl border border-white/10 flex items-center gap-3">
|
|
135
|
+
<i data-lucide="check-circle-2" class="w-5 h-5 text-emerald-400"></i>
|
|
136
|
+
<p class="text-xs text-white/70">Mendukung <code class="text-indigo-300">sitemap.xml</code>, <code class="text-indigo-300">robots.txt</code>, dan skema <code class="text-indigo-300">BlogPosting</code> secara dinamis.</p>
|
|
159
137
|
</div>
|
|
160
138
|
</section>
|
|
161
139
|
|
|
162
140
|
<div class="mt-12 pt-8 border-t border-white/10 text-center">
|
|
163
141
|
<div class="flex justify-center gap-4 mb-4">
|
|
164
142
|
<span class="bg-indigo-500/20 text-indigo-300 text-[10px] px-3 py-1 rounded-full border border-indigo-500/30 uppercase tracking-tighter">Rack 3.0 Compatible</span>
|
|
165
|
-
<span class="bg-emerald-500/20 text-emerald-300 text-[10px] px-3 py-1 rounded-full border border-emerald-500/30 uppercase tracking-tighter">
|
|
143
|
+
<span class="bg-emerald-500/20 text-emerald-300 text-[10px] px-3 py-1 rounded-full border border-emerald-500/30 uppercase tracking-tighter">Production Ready</span>
|
|
166
144
|
</div>
|
|
167
|
-
<p class="text-white/40 text-sm italic">Eksa Framework v3.
|
|
145
|
+
<p class="text-white/40 text-sm italic">Eksa Framework v3.5.0 Professional Documentation</p>
|
|
168
146
|
<a href="/" class="inline-block mt-6 text-indigo-300 hover:text-white transition font-bold flex items-center justify-center gap-2">
|
|
169
147
|
<i data-lucide="arrow-left" class="w-4 h-4"></i> Kembali ke Beranda
|
|
170
148
|
</a>
|
data/app/views/edit.html.erb
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
</div>
|
|
11
11
|
|
|
12
12
|
<p class="text-white/60 mb-8">
|
|
13
|
-
Lakukan perubahan pada konten atau pengirim. Perubahan akan langsung diperbarui di database
|
|
13
|
+
Lakukan perubahan pada konten atau pengirim. Perubahan akan langsung diperbarui di database.
|
|
14
14
|
</p>
|
|
15
15
|
|
|
16
16
|
<form action="/edit?id=<%= @id %>" method="post" class="space-y-6">
|
data/app/views/index.html.erb
CHANGED
|
@@ -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
|
-
v3.
|
|
5
|
+
v3.5.0 Alpha
|
|
6
6
|
</span>
|
|
7
7
|
<h1 class="text-4xl font-extrabold tracking-tight">
|
|
8
8
|
Halo, <span class="text-indigo-300"><%= @nama %></span>!
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
class="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 focus:outline-none focus:ring-2 focus:ring-indigo-500/50 transition placeholder-white/20 h-32 resize-none"></textarea>
|
|
41
41
|
</div>
|
|
42
42
|
<button type="submit" class="w-full bg-indigo-600 hover:bg-indigo-500 text-white font-bold py-3 rounded-xl transition duration-300 flex items-center justify-center gap-2 shadow-lg shadow-indigo-900/20">
|
|
43
|
-
<i data-lucide="send" class="w-4 h-4"></i> Simpan ke
|
|
43
|
+
<i data-lucide="send" class="w-4 h-4"></i> Simpan ke Database
|
|
44
44
|
</button>
|
|
45
45
|
</form>
|
|
46
46
|
</div>
|
data/db/eksa_app.db
ADDED
|
Binary file
|
data/db/setup.rb
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
require_relative '../lib/eksa'
|
|
2
2
|
|
|
3
|
-
db =
|
|
3
|
+
db = Eksa::Database.adapter
|
|
4
4
|
|
|
5
5
|
db.execute <<-SQL
|
|
6
6
|
CREATE TABLE IF NOT EXISTS pesan (
|
|
7
|
-
id INTEGER PRIMARY KEY,
|
|
7
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
8
8
|
konten TEXT,
|
|
9
9
|
pengirim TEXT
|
|
10
10
|
);
|
|
11
11
|
SQL
|
|
12
12
|
|
|
13
|
-
puts "Database Eksa berhasil disiapkan!"
|
|
13
|
+
puts "✅ Database Eksa berhasil disiapkan (Engine: #{Eksa::Database.adapter.class})!"
|
data/exe/eksa
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
require 'fileutils'
|
|
5
|
+
require 'json'
|
|
5
6
|
|
|
6
7
|
def usage
|
|
7
8
|
puts "\n✨ Eksa Framework CLI ✨"
|
|
@@ -11,6 +12,10 @@ def usage
|
|
|
11
12
|
puts " eksa g controller NAME - Generate controller baru"
|
|
12
13
|
puts " eksa g model NAME - Generate model baru"
|
|
13
14
|
puts " eksa g post TITLE - Post baru [args: --category, --author, --image]"
|
|
15
|
+
puts " eksa feature ACTION F - Toggle fitur: action=enable/disable, F=cms/auth"
|
|
16
|
+
puts " eksa reset-password USR PWD - Reset password akun admin"
|
|
17
|
+
puts " eksa db switch TYPE [URI] - Ganti DB: TYPE=sqlite/mongo"
|
|
18
|
+
puts " eksa db migrate --from TYPE --to TYPE - Pindahkan data antar DB"
|
|
14
19
|
puts " eksa run - Jalankan server aplikasi"
|
|
15
20
|
puts "-----------------------------\n"
|
|
16
21
|
end
|
|
@@ -21,7 +26,7 @@ def init_project
|
|
|
21
26
|
|
|
22
27
|
puts "🚀 Menginisialisasi project di: #{target_dir}"
|
|
23
28
|
|
|
24
|
-
items_to_init = ['app', 'lib', 'public', 'db', 'spec', '_posts', 'config.ru', 'Gemfile', 'README.md']
|
|
29
|
+
items_to_init = ['app', 'lib', 'public', 'db', 'spec', '_posts', 'config.ru', 'Gemfile', 'README.md', '.env.example']
|
|
25
30
|
|
|
26
31
|
items_to_init.each do |item|
|
|
27
32
|
source = File.join(gem_root, item)
|
|
@@ -142,6 +147,159 @@ def generate_post(title, options = {})
|
|
|
142
147
|
puts " [OK] Created #{filename}"
|
|
143
148
|
end
|
|
144
149
|
|
|
150
|
+
def toggle_feature(action, feature)
|
|
151
|
+
valid_features = ['cms', 'auth']
|
|
152
|
+
|
|
153
|
+
unless valid_features.include?(feature)
|
|
154
|
+
puts "❌ Error: Fitur tidak dikenali. Pilihan: #{valid_features.join(', ')}"
|
|
155
|
+
return
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
unless ['enable', 'disable'].include?(action)
|
|
159
|
+
puts "❌ Error: Aksi tidak dikenali. Gunakan 'enable' atau 'disable'."
|
|
160
|
+
return
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
config_path = File.join(Dir.pwd, '.eksa.json')
|
|
164
|
+
|
|
165
|
+
begin
|
|
166
|
+
config = File.exist?(config_path) ? JSON.parse(File.read(config_path)) : { 'cms' => false, 'auth' => false }
|
|
167
|
+
rescue JSON::ParserError
|
|
168
|
+
config = { 'cms' => false, 'auth' => false }
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
config[feature] = (action == 'enable')
|
|
172
|
+
|
|
173
|
+
File.write(config_path, JSON.pretty_generate(config))
|
|
174
|
+
|
|
175
|
+
status_color = action == 'enable' ? "✅ Aktif" : "🚫 Nonaktif"
|
|
176
|
+
puts "⚙️ Fitur '#{feature}' berhasil di #{status_color}."
|
|
177
|
+
|
|
178
|
+
if action == 'enable' && feature == 'cms'
|
|
179
|
+
puts " Tip: Pastikan Anda juga mengaktifkan 'auth' agar CMS Anda aman."
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def reset_password(username, password)
|
|
184
|
+
require 'bcrypt'
|
|
185
|
+
require_relative '../lib/eksa/database'
|
|
186
|
+
|
|
187
|
+
begin
|
|
188
|
+
db = Eksa::Database.adapter
|
|
189
|
+
user = db.execute("SELECT id FROM eksa_users WHERE username = ? LIMIT 1", [username]).first
|
|
190
|
+
rescue => e
|
|
191
|
+
puts "❌ Error: Gagal mengakses database: #{e.message}"
|
|
192
|
+
return
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
if user
|
|
196
|
+
hash = BCrypt::Password.create(password)
|
|
197
|
+
db.execute("UPDATE eksa_users SET password_hash = ? WHERE username = ?", [hash, username])
|
|
198
|
+
puts "✅ Akses dipulihkan! Password untuk admin '#{username}' berhasil diubah."
|
|
199
|
+
else
|
|
200
|
+
puts "❌ Error: User '#{username}' tidak ditemukan dalam database."
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def switch_database(type, uri = nil)
|
|
205
|
+
valid_types = ['sqlite', 'mongo', 'mongodb']
|
|
206
|
+
unless valid_types.include?(type.downcase)
|
|
207
|
+
puts "❌ Error: Tipe database tidak dikenali. Pilih: sqlite atau mongo."
|
|
208
|
+
return
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
config_path = File.join(Dir.pwd, '.eksa.json')
|
|
212
|
+
begin
|
|
213
|
+
config = File.exist?(config_path) ? JSON.parse(File.read(config_path)) : {}
|
|
214
|
+
rescue
|
|
215
|
+
config = {}
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
config['database'] ||= {}
|
|
219
|
+
config['database']['type'] = type.downcase == 'mongo' ? 'mongodb' : 'sqlite'
|
|
220
|
+
|
|
221
|
+
if type.downcase == 'mongo' || type.downcase == 'mongodb'
|
|
222
|
+
if uri
|
|
223
|
+
config['database']['mongodb'] ||= {}
|
|
224
|
+
config['database']['mongodb']['uri'] = uri
|
|
225
|
+
elsif !ENV['EKSA_MONGODB_URI'] && !ENV['MONGODB_URI'] && !config.dig('database', 'mongodb', 'uri')
|
|
226
|
+
puts "⚠️ Peringatan: Anda memilih MongoDB tapi belum memberikan URI."
|
|
227
|
+
puts " Gunakan: export EKSA_MONGODB_URI=\"YOUR_URI\""
|
|
228
|
+
puts " Atau: eksa db switch mongo \"YOUR_URI\""
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
File.write(config_path, JSON.pretty_generate(config))
|
|
233
|
+
puts "⚙️ Database berhasil diubah ke: #{config['database']['type'].upcase}."
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def migrate_database(from_type, to_type)
|
|
237
|
+
require_relative '../lib/eksa/database/sqlite_adapter'
|
|
238
|
+
require_relative '../lib/eksa/database/mongo_adapter'
|
|
239
|
+
require 'json'
|
|
240
|
+
|
|
241
|
+
config_path = File.join(Dir.pwd, '.eksa.json')
|
|
242
|
+
config = File.exist?(config_path) ? JSON.parse(File.read(config_path)) : {}
|
|
243
|
+
|
|
244
|
+
puts "🚀 Memulai migrasi data dari #{from_type.upcase} ke #{to_type.upcase}..."
|
|
245
|
+
|
|
246
|
+
source_adapter = create_specific_adapter(from_type, config)
|
|
247
|
+
target_adapter = create_specific_adapter(to_type, config)
|
|
248
|
+
|
|
249
|
+
# Migration logic for User model (the only one currently using Eksa::Model)
|
|
250
|
+
puts " 📦 Memindahkan tabel 'eksa_users'..."
|
|
251
|
+
|
|
252
|
+
begin
|
|
253
|
+
# Source data
|
|
254
|
+
users = source_adapter.execute("SELECT id, username, password_hash FROM eksa_users")
|
|
255
|
+
|
|
256
|
+
# Target setup
|
|
257
|
+
target_adapter.execute("CREATE TABLE IF NOT EXISTS eksa_users (id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE, password_hash TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP)")
|
|
258
|
+
|
|
259
|
+
count = 0
|
|
260
|
+
users.each do |user|
|
|
261
|
+
# user is [id, username, password_hash]
|
|
262
|
+
# Check if user already exists in target
|
|
263
|
+
existing = target_adapter.execute("SELECT id FROM eksa_users WHERE username = ?", [user[1]])
|
|
264
|
+
|
|
265
|
+
if existing.empty?
|
|
266
|
+
target_adapter.execute("INSERT INTO eksa_users (username, password_hash) VALUES (?, ?)", [user[1], user[2]])
|
|
267
|
+
count += 1
|
|
268
|
+
else
|
|
269
|
+
puts " ⚠️ User '#{user[1]}' sudah ada di target, melewati..."
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
puts " ✅ Berhasil memindahkan #{count} user."
|
|
274
|
+
puts "\n🎉 Migrasi Selesai!"
|
|
275
|
+
rescue => e
|
|
276
|
+
puts "❌ Terjadi kesalahan saat migrasi: #{e.message}"
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def create_specific_adapter(type, config)
|
|
281
|
+
case type.downcase
|
|
282
|
+
when 'mongo', 'mongodb'
|
|
283
|
+
require_relative '../lib/eksa/database/mongo_adapter'
|
|
284
|
+
mongo_config = config.dig('database', 'mongodb') || {}
|
|
285
|
+
|
|
286
|
+
# Load .env for CLI
|
|
287
|
+
begin
|
|
288
|
+
require 'dotenv'
|
|
289
|
+
Dotenv.load if File.exist?('.env')
|
|
290
|
+
rescue LoadError
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
env_uri = ENV['EKSA_MONGODB_URI'] || ENV['MONGODB_URI']
|
|
294
|
+
mongo_config['uri'] = env_uri if env_uri
|
|
295
|
+
|
|
296
|
+
Eksa::MongoAdapter.new(mongo_config)
|
|
297
|
+
else
|
|
298
|
+
require_relative '../lib/eksa/database/sqlite_adapter'
|
|
299
|
+
Eksa::SqliteAdapter.new(config.dig('database', 'sqlite'))
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
|
|
145
303
|
def start_server
|
|
146
304
|
if File.exist?('config.ru')
|
|
147
305
|
puts "🚀 Memulai Eksa Framework Server..."
|
|
@@ -179,6 +337,51 @@ when 'g', 'generate'
|
|
|
179
337
|
else
|
|
180
338
|
usage
|
|
181
339
|
end
|
|
340
|
+
when 'feature'
|
|
341
|
+
action = ARGV.shift
|
|
342
|
+
feature = ARGV.shift
|
|
343
|
+
if action && feature
|
|
344
|
+
require 'json'
|
|
345
|
+
toggle_feature(action, feature)
|
|
346
|
+
else
|
|
347
|
+
usage
|
|
348
|
+
end
|
|
349
|
+
when 'reset-password'
|
|
350
|
+
username = ARGV.shift
|
|
351
|
+
password = ARGV.shift
|
|
352
|
+
if username && password
|
|
353
|
+
reset_password(username, password)
|
|
354
|
+
else
|
|
355
|
+
puts "❌ Error: Argumen tidak lengkap. Gunakan: eksa reset-password USERNAME NEW_PASSWORD"
|
|
356
|
+
end
|
|
357
|
+
when 'db'
|
|
358
|
+
subcommand = ARGV.shift
|
|
359
|
+
case subcommand
|
|
360
|
+
when 'switch'
|
|
361
|
+
type = ARGV.shift
|
|
362
|
+
uri = ARGV.shift
|
|
363
|
+
if type
|
|
364
|
+
switch_database(type, uri)
|
|
365
|
+
else
|
|
366
|
+
puts "❌ Error: Tipe database harus diisi. Gunakan: eksa db switch [sqlite|mongo]"
|
|
367
|
+
end
|
|
368
|
+
when 'migrate'
|
|
369
|
+
from = nil
|
|
370
|
+
to = nil
|
|
371
|
+
while (arg = ARGV.shift)
|
|
372
|
+
case arg
|
|
373
|
+
when '--from' then from = ARGV.shift
|
|
374
|
+
when '--to' then to = ARGV.shift
|
|
375
|
+
end
|
|
376
|
+
end
|
|
377
|
+
if from && to
|
|
378
|
+
migrate_database(from, to)
|
|
379
|
+
else
|
|
380
|
+
puts "❌ Error: Argumen tidak lengkap. Gunakan: eksa db migrate --from TYPE --to TYPE"
|
|
381
|
+
end
|
|
382
|
+
else
|
|
383
|
+
usage
|
|
384
|
+
end
|
|
182
385
|
else
|
|
183
386
|
usage
|
|
184
387
|
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
module Eksa
|
|
2
|
+
class AuthController < Eksa::Controller
|
|
3
|
+
def login
|
|
4
|
+
return redirect_to "/cms" if current_user
|
|
5
|
+
render_internal 'auth/login'
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def register
|
|
9
|
+
return redirect_to "/cms" if current_user
|
|
10
|
+
@admin_exists = Eksa::User.all.any?
|
|
11
|
+
render_internal 'auth/register'
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def process_login
|
|
15
|
+
username = params['username']
|
|
16
|
+
password = params['password']
|
|
17
|
+
|
|
18
|
+
user = Eksa::User.authenticate(username, password)
|
|
19
|
+
if user
|
|
20
|
+
session['user_id'] = user[:id]
|
|
21
|
+
redirect_to "/cms", notice: "Selamat datang kembali, #{username}!"
|
|
22
|
+
else
|
|
23
|
+
redirect_to "/auth/login", notice: "Username atau password salah."
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def process_register
|
|
28
|
+
if Eksa::User.all.any?
|
|
29
|
+
return redirect_to "/auth/login", notice: "Registrasi ditutup. Hanya satu admin yang diizinkan."
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
username = params['username']
|
|
33
|
+
password = params['password']
|
|
34
|
+
|
|
35
|
+
if username && !username.empty? && password && password.length >= 6
|
|
36
|
+
Eksa::User.create(username, password)
|
|
37
|
+
user = Eksa::User.authenticate(username, password)
|
|
38
|
+
session['user_id'] = user[:id]
|
|
39
|
+
redirect_to "/cms", notice: "Akun berhasil dibuat. Selamat datang!"
|
|
40
|
+
else
|
|
41
|
+
redirect_to "/auth/register", notice: "Data tidak valid. Password minimal 6 karakter."
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def logout
|
|
46
|
+
session.delete('user_id')
|
|
47
|
+
redirect_to "/", notice: "Anda telah berhasil logout."
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|