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.
@@ -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
- Eksa::Model.database_path = @config[:db_path]
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
- response.delete_cookie('eksa_flash') if flash_message
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.3.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
- description: Framework MVC ringan dengan tema modern, sistem routing, dan auto-database
97
- SQLite.
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