Roda Kabk
Kabk carries the Ruby to Simorgh
roda-kabk is the official Roda plugin for Kabk — seamlessly binding Roda applications to the Simurgh Panel protocol (v1.6.0) for schema-driven dynamic REST APIs without duplicating backend logic.
Features
- Embedded Simurgh Panel UI: Bundles and serves the pre-built single-page admin dashboard SPA directly at
mount_at(/api/admin) with embedded schema injection. - Generic REST Engine: Auto-generates full CRUD, search, multi-field filtering, sorting, and pagination for registered Sequel models.
- Pluggable File Uploads: Seamless integration with Shrine, ActiveStorage, or custom storage handlers via configurable procs.
- Authentication & Security: Integrated JWT authentication (
login,refresh,me,change-password) and Role-Based Access Control (RBAC). - Optimistic Concurrency Control (OCC): Built-in support for preventing concurrent record overwrite conflicts (
updated_at/version). - Data Export: Exposes CSV and XLSX export endpoints out of the box (
/export).
Installation
Add this line to your application's Gemfile:
gem 'roda-kabk'
And then execute:
bundle install
Quick Start
require 'roda'
require 'sequel'
require 'kabk'
require 'roda/plugins/kabk'
# 1. Setup DB and Sequel Model
DB = Sequel.sqlite
DB.create_table :articles do
primary_key :id
String :title, null: false
String :content
DateTime :updated_at
end
class Article < Sequel::Model; end
# 2. Register Entity with Kabk
Kabk.register(name: "article", table: Article) do
title "Articles"
concurrency_field "updated_at"
field :id, type: :number, form_type: :number, primary_key: true
field :title, type: :string, form_type: :text, required: true
field :content, type: :string, form_type: :textarea
field :updated_at, type: :datetime, form_type: :datetime, readonly: true, calendar: :jalali
end
# 3. Mount Kabk Plugin in Roda
class App < Roda
plugin :kabk,
mount_at: "/api/admin",
serve_ui: true,
auth_strategy: Kabk::Auth::JwtStrategy.new(secret: ENV.fetch("JWT_SECRET", "super_secret_key")),
login_handler: ->(username, password) {
# Fetch user from DB and verify password
# return user_data hash if valid, nil otherwise
user = User.first(email: username)
return nil unless user && user.authenticate(password)
{ id: user.id, username: user.email, role: user.role }
},
change_password_handler: ->(user_context, old_pw, new_pw) {
# Verify old password and save new password
user = User[user_context[:id]]
raise Kabk::InvalidOldPasswordError.new(fields: { old_password: ["Incorrect password"] }) unless user.authenticate(old_pw)
user.update(password: new_pw)
},
upload_handler: ->(file_param, req) {
# Integration point for Shrine / ActiveStorage
{
url: "/uploads/#{file_param[:filename]}",
file_name: file_param[:filename],
size: file_param[:tempfile].size,
mime_type: file_param[:type]
}
}
route do |r|
# Exposes Dashboard UI, Auth, CRUD, Export, and Upload endpoints under /api/admin
r.kabk
end
end
Configuration Reference
The plugin :kabk method accepts the following options:
| Option | Type | Default | Description |
|---|---|---|---|
mount_at |
String |
"/api/admin" |
Base URI path under which all Kabk admin endpoints and UI are mounted. |
serve_ui |
Boolean |
true |
Serves the bundled Simurgh Panel single-page dashboard UI directly at mount_at. |
system_config |
Hash / NilClass |
nil |
Custom system branding and localization settings (overrides default locale/title). |
auth_strategy |
Kabk::Auth::JwtStrategy / NilClass |
nil |
Authentication strategy object. Set to nil to disable auth for development. |
upload_handler |
Proc / Callable |
nil |
Custom file upload handler block/callable. Processes uploaded file params. |
File Upload Integration (Shrine Example)
You can connect Shrine directly using the upload_handler option:
plugin :kabk,
mount_at: "/api/admin",
upload_handler: ->(file_param, req) {
# file_param contains Rack tempfile params: { tempfile: #<File>, filename: "...", type: "..." }
uploader = ImageUploader.upload(file_param[:tempfile], :store)
{
url: uploader.url,
file_name: file_param[:filename],
size: file_param[:tempfile].size,
mime_type: file_param[:type]
}
}
If no upload_handler is provided and no file is attached, the endpoint returns an HTTP 400 Bad Request with error code BAD_REQUEST.
Endpoints Reference
All endpoints are exposed under the configured mount_at prefix (e.g. /api/admin):
Authentication
POST /api/admin/auth/login— Authenticates user credentials & returns JWT access/refresh tokens.POST /api/admin/auth/refresh— Generates new access token from refresh token.GET /api/admin/auth/me— Returns current authenticated user context.POST /api/admin/auth/logout— Revokes active session.POST /api/admin/auth/change-password— Updates user password (requiresold_passwordverification).
Resource Operations (Generic REST)
For any registered entity plural_name (e.g., /articles, /users):
GET /api/admin/:plural_name— List records (supportspage,per_page,sort,search,filter[field]).GET /api/admin/:plural_name/:id— Retrieve single record details.POST /api/admin/:plural_name— Create new record.PUT /api/admin/:plural_name/:id— Update record (with Optimistic Concurrency Control).DELETE /api/admin/:plural_name/:id— Delete record.GET /api/admin/:plural_name/export— Export data incsvorxlsxformat (?format=xlsx).
Media Uploads
POST /api/admin/uploads— Upload file via multipart/form-data.
Testing
Run the test suite using rspec:
bundle exec rspec
License
The gem is available as open source under the terms of the MIT License.