Roda Kabk Logo
Roda Kabk

Gem Version License: MIT

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 (requires old_password verification).

Resource Operations (Generic REST)

For any registered entity plural_name (e.g., /articles, /users):

  • GET /api/admin/:plural_name — List records (supports page, 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 in csv or xlsx format (?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.