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
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
<div class="glass max-w-4xl mx-auto rounded-3xl p-10 text-white animate__animated animate__zoomIn animate__faster relative overflow-hidden">
|
|
2
|
+
<div class="absolute inset-0 bg-yellow-500/10 blur-3xl rounded-full translate-x-1/2 -translate-y-1/2"></div>
|
|
3
|
+
|
|
4
|
+
<div class="relative z-10 flex items-center justify-between mb-8 pb-6 border-b border-white/10">
|
|
5
|
+
<div class="flex items-center gap-4">
|
|
6
|
+
<div class="w-14 h-14 bg-yellow-500/20 rounded-2xl flex items-center justify-center border border-yellow-500/30 shadow-xl shadow-yellow-500/20">
|
|
7
|
+
<i data-lucide="edit-3" class="w-7 h-7 text-yellow-300"></i>
|
|
8
|
+
</div>
|
|
9
|
+
<div>
|
|
10
|
+
<h1 class="text-3xl font-extrabold tracking-tight">Edit <span class="text-yellow-300">Post</span></h1>
|
|
11
|
+
<p class="text-white/50 text-sm mt-1">Mengedit <%= @post.filename %></p>
|
|
12
|
+
</div>
|
|
13
|
+
</div>
|
|
14
|
+
<a href="/cms" class="px-4 py-2 bg-white/5 hover:bg-white/10 rounded-xl transition border border-white/10 text-sm font-medium flex items-center gap-2">
|
|
15
|
+
<i data-lucide="arrow-left" class="w-4 h-4"></i> Kembali
|
|
16
|
+
</a>
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
<form action="/cms/update/<%= @post.slug %>" method="post" class="relative z-10 space-y-6">
|
|
20
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
21
|
+
<div class="space-y-2">
|
|
22
|
+
<label class="block text-xs font-bold text-yellow-300 uppercase tracking-widest">Judul Postingan</label>
|
|
23
|
+
<input type="text" name="title" value="<%= @post.title %>" required
|
|
24
|
+
class="w-full bg-black/40 border border-white/10 rounded-xl px-4 py-3 text-white focus:outline-none focus:border-yellow-500 focus:ring-1 focus:ring-yellow-500 transition">
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<div class="space-y-2">
|
|
28
|
+
<label class="block text-xs font-bold text-yellow-300 uppercase tracking-widest">Kategori</label>
|
|
29
|
+
<input type="text" name="category" value="<%= @post.category %>" required
|
|
30
|
+
class="w-full bg-black/40 border border-white/10 rounded-xl px-4 py-3 text-white focus:outline-none focus:border-yellow-500 focus:ring-1 focus:ring-yellow-500 transition">
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<div class="space-y-2">
|
|
34
|
+
<label class="block text-xs font-bold text-yellow-300 uppercase tracking-widest">Penulis (Author)</label>
|
|
35
|
+
<input type="text" name="author" value="<%= @post.author %>" required
|
|
36
|
+
class="w-full bg-black/40 border border-white/10 rounded-xl px-4 py-3 text-white focus:outline-none focus:border-yellow-500 focus:ring-1 focus:ring-yellow-500 transition">
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<div class="space-y-2">
|
|
40
|
+
<label class="block text-xs font-bold text-yellow-300 uppercase tracking-widest">Image Thumbnail URL</label>
|
|
41
|
+
<input type="text" name="image" value="<%= @post.image %>"
|
|
42
|
+
class="w-full bg-black/40 border border-white/10 rounded-xl px-4 py-3 text-white focus:outline-none focus:border-yellow-500 focus:ring-1 focus:ring-yellow-500 transition"
|
|
43
|
+
placeholder="Contoh: header.jpg atau kosongkan">
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<div class="space-y-2 pt-4">
|
|
48
|
+
<label class="block text-xs font-bold text-yellow-300 uppercase tracking-widest">Konten (Markdown)</label>
|
|
49
|
+
<div class="relative group">
|
|
50
|
+
<textarea name="content" required rows="15"
|
|
51
|
+
class="w-full bg-black/40 border border-white/10 rounded-xl px-5 py-4 text-white focus:outline-none focus:border-yellow-500 focus:ring-1 focus:ring-yellow-500 font-mono text-sm leading-relaxed transition"><%= @post.content.strip %></textarea>
|
|
52
|
+
<!-- Decorative subtle icon -->
|
|
53
|
+
<i data-lucide="file-text" class="absolute bottom-4 right-4 w-12 h-12 text-white/5 pointer-events-none transition group-hover:text-yellow-500/10"></i>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<div class="pt-6 border-t border-white/5 flex justify-end">
|
|
58
|
+
<button type="submit" class="bg-yellow-500 hover:bg-yellow-400 text-yellow-950 font-bold py-3 px-8 rounded-xl transition duration-300 shadow-xl shadow-yellow-500/20 flex items-center gap-2">
|
|
59
|
+
<i data-lucide="save" class="w-5 h-5"></i> Simpan Perubahan
|
|
60
|
+
</button>
|
|
61
|
+
</div>
|
|
62
|
+
</form>
|
|
63
|
+
</div>
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
<div class="glass rounded-3xl p-10 text-white animate__animated animate__fadeIn">
|
|
2
|
+
<div class="flex items-center justify-between mb-10 pb-6 border-b border-white/10">
|
|
3
|
+
<div class="flex items-center gap-4">
|
|
4
|
+
<div class="p-4 bg-indigo-500 rounded-2xl shadow-xl">
|
|
5
|
+
<i data-lucide="layout-dashboard" class="w-8 h-8"></i>
|
|
6
|
+
</div>
|
|
7
|
+
<div>
|
|
8
|
+
<h1 class="text-4xl font-extrabold tracking-tight">CMS <span class="text-indigo-300">Dashboard</span></h1>
|
|
9
|
+
<p class="text-white/50">Kelola visibilitas postingan blog Anda</p>
|
|
10
|
+
</div>
|
|
11
|
+
</div>
|
|
12
|
+
<div class="flex items-center gap-4">
|
|
13
|
+
<span class="px-4 py-2 bg-white/5 rounded-xl border border-white/10 text-sm font-medium text-white/80">
|
|
14
|
+
<i data-lucide="user" class="w-4 h-4 inline-block mr-1"></i> Admin
|
|
15
|
+
</span>
|
|
16
|
+
<a href="/auth/logout" class="px-4 py-2 bg-red-500/20 hover:bg-red-500/40 text-red-300 rounded-xl border border-red-500/30 transition shadow-lg text-sm font-bold flex items-center gap-2">
|
|
17
|
+
<i data-lucide="log-out" class="w-4 h-4"></i> Logout
|
|
18
|
+
</a>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<div class="overflow-hidden rounded-2xl border border-white/10 bg-black/20">
|
|
23
|
+
<table class="w-full text-left border-collapse">
|
|
24
|
+
<thead>
|
|
25
|
+
<tr class="bg-white/5 text-indigo-200 text-xs uppercase tracking-widest">
|
|
26
|
+
<th class="p-4 font-bold border-b border-white/10">Postingan</th>
|
|
27
|
+
<th class="p-4 font-bold border-b border-white/10">Kategori</th>
|
|
28
|
+
<th class="p-4 font-bold border-b border-white/10">Status</th>
|
|
29
|
+
<th class="p-4 font-bold border-b border-white/10 text-right">Aksi</th>
|
|
30
|
+
</tr>
|
|
31
|
+
</thead>
|
|
32
|
+
<tbody class="divide-y divide-white/5">
|
|
33
|
+
<% if @posts.empty? %>
|
|
34
|
+
<tr>
|
|
35
|
+
<td colspan="4" class="p-8 text-center text-white/40">
|
|
36
|
+
<i data-lucide="folder-open" class="w-8 h-8 mx-auto mb-2 opacity-50"></i>
|
|
37
|
+
Belum ada postingan markdown di _posts/
|
|
38
|
+
</td>
|
|
39
|
+
</tr>
|
|
40
|
+
<% else %>
|
|
41
|
+
<% @posts.each do |post| %>
|
|
42
|
+
<tr class="hover:bg-white/5 transition">
|
|
43
|
+
<td class="p-4">
|
|
44
|
+
<p class="font-bold text-lg"><%= post.title %></p>
|
|
45
|
+
<p class="text-xs text-white/40 font-mono"><%= post.filename %></p>
|
|
46
|
+
</td>
|
|
47
|
+
<td class="p-4">
|
|
48
|
+
<span class="text-[10px] font-bold text-indigo-300 uppercase tracking-widest px-2 py-1 bg-indigo-500/20 rounded-md border border-indigo-500/30">
|
|
49
|
+
<%= post.category %>
|
|
50
|
+
</span>
|
|
51
|
+
</td>
|
|
52
|
+
<td class="p-4">
|
|
53
|
+
<% if post.published? %>
|
|
54
|
+
<span class="inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-xs font-bold bg-emerald-500/20 text-emerald-300 border border-emerald-500/30">
|
|
55
|
+
<span class="w-2 h-2 rounded-full bg-emerald-400"></span> Aktif
|
|
56
|
+
</span>
|
|
57
|
+
<% else %>
|
|
58
|
+
<span class="inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-xs font-bold bg-gray-500/20 text-gray-400 border border-gray-500/30">
|
|
59
|
+
<span class="w-2 h-2 rounded-full bg-gray-400"></span> Draft
|
|
60
|
+
</span>
|
|
61
|
+
<% end %>
|
|
62
|
+
</td>
|
|
63
|
+
<td class="p-4 text-right space-x-2">
|
|
64
|
+
<a href="/cms/edit/<%= post.slug %>" class="inline-flex items-center justify-center p-2 rounded-lg bg-yellow-500/20 hover:bg-yellow-500/40 text-yellow-300 transition border border-yellow-500/30" title="Edit Post">
|
|
65
|
+
<i data-lucide="edit" class="w-4 h-4"></i>
|
|
66
|
+
</a>
|
|
67
|
+
<a href="/posts/<%= post.slug %>" target="_blank" class="inline-flex items-center justify-center p-2 rounded-lg bg-white/5 hover:bg-white/10 text-white/70 transition border border-white/10" title="Lihat Post">
|
|
68
|
+
<i data-lucide="external-link" class="w-4 h-4"></i>
|
|
69
|
+
</a>
|
|
70
|
+
<a href="/cms/toggle/<%= post.slug %>" class="inline-flex items-center justify-center p-2 rounded-lg bg-indigo-500/20 hover:bg-indigo-500/40 text-indigo-300 transition border border-indigo-500/30" title="Toggle Status">
|
|
71
|
+
<i data-lucide="<%= post.published? ? 'eye-off' : 'eye' %>" class="w-4 h-4"></i>
|
|
72
|
+
</a>
|
|
73
|
+
<a href="/cms/delete/<%= post.slug %>" onclick="return confirm('Yakin ingin menghapus postingan ini selamanya?')" class="inline-flex items-center justify-center p-2 rounded-lg bg-red-500/20 hover:bg-red-500/40 text-red-300 transition border border-red-500/30" title="Hapus Permanen">
|
|
74
|
+
<i data-lucide="trash-2" class="w-4 h-4"></i>
|
|
75
|
+
</a>
|
|
76
|
+
</td>
|
|
77
|
+
</tr>
|
|
78
|
+
<% end %>
|
|
79
|
+
<% end %>
|
|
80
|
+
</tbody>
|
|
81
|
+
</table>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
data/lib/eksa.rb
CHANGED
|
@@ -1,26 +1,65 @@
|
|
|
1
1
|
require 'rack'
|
|
2
|
+
require 'rack/session'
|
|
2
3
|
require 'json'
|
|
3
4
|
require_relative 'eksa/version'
|
|
4
5
|
require_relative 'eksa/controller'
|
|
5
6
|
require_relative 'eksa/model'
|
|
6
7
|
require_relative 'eksa/markdown_post'
|
|
8
|
+
require_relative 'eksa/user'
|
|
9
|
+
require_relative 'eksa/database'
|
|
10
|
+
require_relative 'eksa/auth_controller'
|
|
11
|
+
require_relative 'eksa/cms_controller'
|
|
7
12
|
|
|
8
13
|
module Eksa
|
|
9
14
|
class Application
|
|
10
|
-
attr_reader :config, :middlewares
|
|
15
|
+
attr_reader :config, :middlewares, :features
|
|
11
16
|
|
|
12
17
|
def initialize
|
|
13
18
|
@routes = {}
|
|
14
19
|
@middlewares = []
|
|
20
|
+
@features = load_feature_flags
|
|
15
21
|
@config = {
|
|
16
|
-
db_path: File.expand_path("./db/eksa_app.db")
|
|
22
|
+
db_path: File.expand_path("./db/eksa_app.db"),
|
|
23
|
+
session_secret: ENV['SESSION_SECRET'] || 'eksa_super_secret_key_change_me_in_production_make_it_sixty_four_bytes_or_more'
|
|
17
24
|
}
|
|
18
25
|
yield self if block_given?
|
|
19
26
|
configure_framework
|
|
20
27
|
end
|
|
21
28
|
|
|
22
29
|
def configure_framework
|
|
23
|
-
|
|
30
|
+
# Setup Session Middleware for Authentication
|
|
31
|
+
use Rack::Session::Cookie, secret: @config[:session_secret], key: 'eksa.session'
|
|
32
|
+
|
|
33
|
+
auto_mount_features
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def load_feature_flags
|
|
37
|
+
config_path = File.expand_path('./.eksa.json')
|
|
38
|
+
if File.exist?(config_path)
|
|
39
|
+
JSON.parse(File.read(config_path))
|
|
40
|
+
else
|
|
41
|
+
{ 'cms' => false, 'auth' => false }
|
|
42
|
+
end
|
|
43
|
+
rescue JSON::ParserError
|
|
44
|
+
{ 'cms' => false, 'auth' => false }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def auto_mount_features
|
|
48
|
+
if @features['auth']
|
|
49
|
+
add_route "/auth/login", Eksa::AuthController, :login
|
|
50
|
+
add_route "/auth/register", Eksa::AuthController, :register
|
|
51
|
+
add_route "/auth/logout", Eksa::AuthController, :logout
|
|
52
|
+
add_route "/auth/process_login", Eksa::AuthController, :process_login
|
|
53
|
+
add_route "/auth/process_register", Eksa::AuthController, :process_register
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
if @features['cms']
|
|
57
|
+
add_route "/cms", Eksa::CmsController, :index
|
|
58
|
+
add_route "/cms/edit/:slug", Eksa::CmsController, :edit
|
|
59
|
+
add_route "/cms/update/:slug", Eksa::CmsController, :update_post
|
|
60
|
+
add_route "/cms/toggle/:slug", Eksa::CmsController, :toggle_status
|
|
61
|
+
add_route "/cms/delete/:slug", Eksa::CmsController, :delete_post
|
|
62
|
+
end
|
|
24
63
|
end
|
|
25
64
|
|
|
26
65
|
def add_route(path, controller_class, action)
|
|
@@ -82,6 +121,9 @@ module Eksa
|
|
|
82
121
|
else
|
|
83
122
|
response = Rack::Response.new
|
|
84
123
|
if controller_instance.status == 302
|
|
124
|
+
if controller_instance.flash[:notice] && !controller_instance.flash[:notice].empty?
|
|
125
|
+
response.set_cookie('eksa_flash', value: controller_instance.flash[:notice], path: '/')
|
|
126
|
+
end
|
|
85
127
|
response.redirect(controller_instance.redirect_url, 302)
|
|
86
128
|
else
|
|
87
129
|
response.write(response_data)
|
|
@@ -89,7 +131,9 @@ module Eksa
|
|
|
89
131
|
end
|
|
90
132
|
end
|
|
91
133
|
|
|
92
|
-
|
|
134
|
+
if flash_message && controller_instance.status != 302
|
|
135
|
+
response.delete_cookie('eksa_flash', path: '/')
|
|
136
|
+
end
|
|
93
137
|
response.finish
|
|
94
138
|
else
|
|
95
139
|
html = <<~HTML
|
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: 3.
|
|
4
|
+
version: 3.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- IshikawaUta
|
|
@@ -93,8 +93,64 @@ dependencies:
|
|
|
93
93
|
- - "~>"
|
|
94
94
|
- !ruby/object:Gem::Version
|
|
95
95
|
version: '1.1'
|
|
96
|
-
|
|
97
|
-
|
|
96
|
+
- !ruby/object:Gem::Dependency
|
|
97
|
+
name: bcrypt
|
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
|
99
|
+
requirements:
|
|
100
|
+
- - "~>"
|
|
101
|
+
- !ruby/object:Gem::Version
|
|
102
|
+
version: '3.1'
|
|
103
|
+
type: :runtime
|
|
104
|
+
prerelease: false
|
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
106
|
+
requirements:
|
|
107
|
+
- - "~>"
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: '3.1'
|
|
110
|
+
- !ruby/object:Gem::Dependency
|
|
111
|
+
name: rack-session
|
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
|
113
|
+
requirements:
|
|
114
|
+
- - "~>"
|
|
115
|
+
- !ruby/object:Gem::Version
|
|
116
|
+
version: '2.0'
|
|
117
|
+
type: :runtime
|
|
118
|
+
prerelease: false
|
|
119
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
120
|
+
requirements:
|
|
121
|
+
- - "~>"
|
|
122
|
+
- !ruby/object:Gem::Version
|
|
123
|
+
version: '2.0'
|
|
124
|
+
- !ruby/object:Gem::Dependency
|
|
125
|
+
name: mongo
|
|
126
|
+
requirement: !ruby/object:Gem::Requirement
|
|
127
|
+
requirements:
|
|
128
|
+
- - "~>"
|
|
129
|
+
- !ruby/object:Gem::Version
|
|
130
|
+
version: '2.19'
|
|
131
|
+
type: :runtime
|
|
132
|
+
prerelease: false
|
|
133
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
134
|
+
requirements:
|
|
135
|
+
- - "~>"
|
|
136
|
+
- !ruby/object:Gem::Version
|
|
137
|
+
version: '2.19'
|
|
138
|
+
- !ruby/object:Gem::Dependency
|
|
139
|
+
name: dotenv
|
|
140
|
+
requirement: !ruby/object:Gem::Requirement
|
|
141
|
+
requirements:
|
|
142
|
+
- - "~>"
|
|
143
|
+
- !ruby/object:Gem::Version
|
|
144
|
+
version: '2.8'
|
|
145
|
+
type: :runtime
|
|
146
|
+
prerelease: false
|
|
147
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
148
|
+
requirements:
|
|
149
|
+
- - "~>"
|
|
150
|
+
- !ruby/object:Gem::Version
|
|
151
|
+
version: '2.8'
|
|
152
|
+
description: Framework MVC ringan dengan tema modern, sistem routing, dan dukungan
|
|
153
|
+
multi-database (SQLite & MongoDB).
|
|
98
154
|
email:
|
|
99
155
|
- komikers09@gmail.com
|
|
100
156
|
executables:
|
|
@@ -102,6 +158,8 @@ executables:
|
|
|
102
158
|
extensions: []
|
|
103
159
|
extra_rdoc_files: []
|
|
104
160
|
files:
|
|
161
|
+
- ".eksa.json"
|
|
162
|
+
- ".env.example"
|
|
105
163
|
- Gemfile
|
|
106
164
|
- Gemfile.lock
|
|
107
165
|
- LICENSE
|
|
@@ -120,13 +178,24 @@ files:
|
|
|
120
178
|
- app/views/posts/index.html.erb
|
|
121
179
|
- app/views/posts/show.html.erb
|
|
122
180
|
- config.ru
|
|
181
|
+
- db/eksa_app.db
|
|
123
182
|
- db/setup.rb
|
|
124
183
|
- exe/eksa
|
|
125
184
|
- lib/eksa.rb
|
|
185
|
+
- lib/eksa/auth_controller.rb
|
|
186
|
+
- lib/eksa/cms_controller.rb
|
|
126
187
|
- lib/eksa/controller.rb
|
|
188
|
+
- lib/eksa/database.rb
|
|
189
|
+
- lib/eksa/database/mongo_adapter.rb
|
|
190
|
+
- lib/eksa/database/sqlite_adapter.rb
|
|
127
191
|
- lib/eksa/markdown_post.rb
|
|
128
192
|
- lib/eksa/model.rb
|
|
193
|
+
- lib/eksa/user.rb
|
|
129
194
|
- lib/eksa/version.rb
|
|
195
|
+
- lib/eksa/views/auth/login.html.erb
|
|
196
|
+
- lib/eksa/views/auth/register.html.erb
|
|
197
|
+
- lib/eksa/views/cms/edit.html.erb
|
|
198
|
+
- lib/eksa/views/cms/index.html.erb
|
|
130
199
|
- public/css/style.css
|
|
131
200
|
- public/img/favicon.ico
|
|
132
201
|
- public/img/logo.jpg
|