kabk 1.0.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.
Files changed (66) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +20 -0
  3. data/Gemfile +6 -0
  4. data/Gemfile.lock +105 -0
  5. data/Kabk_logo.svg +33 -0
  6. data/LICENSE +21 -0
  7. data/README.md +112 -0
  8. data/doc/Kabk/Adapters/Base.html +989 -0
  9. data/doc/Kabk/Adapters/SequelAdapter.html +1071 -0
  10. data/doc/Kabk/Adapters.html +120 -0
  11. data/doc/Kabk/ApiError.html +511 -0
  12. data/doc/Kabk/Auth/JwtStrategy.html +551 -0
  13. data/doc/Kabk/Auth.html +118 -0
  14. data/doc/Kabk/Concurrency.html +297 -0
  15. data/doc/Kabk/ConcurrencyConflictError.html +234 -0
  16. data/doc/Kabk/Error.html +140 -0
  17. data/doc/Kabk/ExportHandler.html +381 -0
  18. data/doc/Kabk/Field.html +2160 -0
  19. data/doc/Kabk/ForbiddenError.html +234 -0
  20. data/doc/Kabk/InvalidFieldError.html +145 -0
  21. data/doc/Kabk/InvalidOldPasswordError.html +234 -0
  22. data/doc/Kabk/NotFoundError.html +234 -0
  23. data/doc/Kabk/QueryBuilder.html +701 -0
  24. data/doc/Kabk/Registry.html +608 -0
  25. data/doc/Kabk/RelationHydrator.html +480 -0
  26. data/doc/Kabk/Resource.html +1986 -0
  27. data/doc/Kabk/ResourceBuilder.html +1002 -0
  28. data/doc/Kabk/RestEngine.html +887 -0
  29. data/doc/Kabk/SchemaRenderer.html +318 -0
  30. data/doc/Kabk/UnauthorizedError.html +234 -0
  31. data/doc/Kabk/UploadHandler.html +308 -0
  32. data/doc/Kabk/ValidationError.html +234 -0
  33. data/doc/Kabk/Validator.html +802 -0
  34. data/doc/Kabk.html +330 -0
  35. data/doc/_index.html +369 -0
  36. data/doc/class_list.html +54 -0
  37. data/doc/css/common.css +1 -0
  38. data/doc/css/full_list.css +206 -0
  39. data/doc/css/style.css +1089 -0
  40. data/doc/file.README.html +162 -0
  41. data/doc/file_list.html +59 -0
  42. data/doc/frames.html +22 -0
  43. data/doc/index.html +162 -0
  44. data/doc/js/app.js +801 -0
  45. data/doc/js/full_list.js +334 -0
  46. data/doc/js/jquery.js +4 -0
  47. data/doc/method_list.html +982 -0
  48. data/doc/top-level-namespace.html +112 -0
  49. data/docs/API_REFERENCE.md +255 -0
  50. data/lib/kabk/adapters/base.rb +53 -0
  51. data/lib/kabk/adapters/sequel_adapter.rb +138 -0
  52. data/lib/kabk/concurrency.rb +30 -0
  53. data/lib/kabk/errors.rb +68 -0
  54. data/lib/kabk/field.rb +97 -0
  55. data/lib/kabk/query_builder.rb +103 -0
  56. data/lib/kabk/registry.rb +41 -0
  57. data/lib/kabk/relation_hydrator.rb +107 -0
  58. data/lib/kabk/resource.rb +88 -0
  59. data/lib/kabk/resource_builder.rb +95 -0
  60. data/lib/kabk/rest_engine.rb +120 -0
  61. data/lib/kabk/schema_renderer.rb +69 -0
  62. data/lib/kabk/upload_handler.rb +24 -0
  63. data/lib/kabk/validator.rb +121 -0
  64. data/lib/kabk/version.rb +6 -0
  65. data/lib/kabk.rb +50 -0
  66. metadata +178 -0
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "registry"
4
+
5
+ module Kabk
6
+ # Renders the JSON Schema manifest for the protocol v1.6.0
7
+ class SchemaRenderer
8
+ # Initializes the schema renderer
9
+ #
10
+ # @param system_config [Hash, nil] Optional system config overrides (e.g., title, logo_url, endpoints)
11
+ # @param validation_schema_url [String, nil] Custom URL for the validation schema
12
+ def initialize(system_config: nil, validation_schema_url: nil)
13
+ @system_config = system_config ? default_system_config.merge(system_config) : default_system_config
14
+ @validation_schema_url = validation_schema_url || "/schemas/admin-protocol-1.6.0.json"
15
+ end
16
+
17
+ def render
18
+ {
19
+ "$schema_version": "1.6.0",
20
+ system: @system_config,
21
+ validation_schema_url: @validation_schema_url,
22
+ resources: Registry.instance.all.map(&:to_h)
23
+ }
24
+ end
25
+
26
+ private
27
+
28
+ def default_system_config
29
+ {
30
+ title: { fa: "پنل مدیریت سیمرغ", en: "Simurgh Panel" },
31
+ logo_url: "/simurgh-logo.svg",
32
+ default_locale: "en",
33
+ supported_locales: %w[en fa],
34
+ direction: "ltr",
35
+ endpoints: {
36
+ upload: "/api/admin/uploads"
37
+ },
38
+ custom_fonts: [
39
+ {
40
+ name: "Lalezar",
41
+ url: "https://fonts.gstatic.com/s/lalezar/v14/OpUp1a5dqj36gZ2zXh_WfQ.woff2",
42
+ format: "woff2",
43
+ label: { en: "Lalezar (Title)", fa: "لاله‌زار (عنوان)" }
44
+ },
45
+ {
46
+ name: "Sahel",
47
+ url: "https://cdn.jsdelivr.net/gh/rastikerdar/sahel-font@v3.4.0/dist/Sahel.woff2",
48
+ format: "woff2",
49
+ label: { en: "Sahel (Clean)", fa: "ساحل (خوانا)" }
50
+ }
51
+ ],
52
+ custom_font_sizes: [
53
+ {
54
+ size: "15px",
55
+ label: { en: "Medium Fine (15px)", fa: "متوسط ظریف (۱۵px)" }
56
+ },
57
+ {
58
+ size: "20px",
59
+ label: { en: "Sub-heading (20px)", fa: "زیرعنوان (۲۰px)" }
60
+ },
61
+ {
62
+ size: "32px",
63
+ label: { en: "Hero Display (32px)", fa: "تیتر برجسته (۳۲px)" }
64
+ }
65
+ ]
66
+ }
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kabk
4
+ # Handles generic file uploads and formats them according to Protocol v1.6.0
5
+ class UploadHandler
6
+ # @param url [String] Public URL of the uploaded file
7
+ # @param file_name [String] Original file name
8
+ # @param size [Integer] File size in bytes
9
+ # @param mime_type [String] File mime type
10
+ # @return [Hash] Formatted upload response
11
+ def self.format_response(url:, file_name:, size:, mime_type: nil)
12
+ {
13
+ success: true,
14
+ message: "File uploaded successfully",
15
+ data: {
16
+ url: url,
17
+ file_name: file_name,
18
+ size: size,
19
+ mime_type: mime_type || "application/octet-stream"
20
+ }
21
+ }
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "errors"
4
+
5
+ module Kabk
6
+ # Handles input sanitization (strong parameters) and validation
7
+ class Validator
8
+ # Sanitizes input parameters against defined fields and validates them
9
+ # @param resource [Kabk::Resource]
10
+ # @param params [Hash] Request payload
11
+ # @param is_update [Boolean] Is this a PUT/update request?
12
+ # @return [Hash] Sanitized parameters ready for the database adapter
13
+ def self.validate_and_sanitize!(resource, params, is_update: false)
14
+ sanitized = {}
15
+ errors = {}
16
+
17
+ resource.fields.each do |field|
18
+ next if field.primary_key || field.readonly
19
+
20
+ key_sym = field.name.to_sym
21
+ key_str = field.name.to_s
22
+ has_key = params.key?(key_sym) || params.key?(key_str)
23
+ value = params.key?(key_sym) ? params[key_sym] : params[key_str]
24
+
25
+ field_errors = validate_field(field, value, has_key, is_update)
26
+ errors[key_str] = field_errors unless field_errors.empty?
27
+
28
+ sanitized[key_sym] = value if has_key
29
+ end
30
+
31
+ # Ensure the concurrency field is included for OCC validation.
32
+ if resource.concurrency_field
33
+ c_field = resource.concurrency_field
34
+ sanitized[c_field.to_sym] = params[c_field.to_sym] || params[c_field.to_s] if params.key?(c_field.to_sym) || params.key?(c_field.to_s)
35
+ end
36
+
37
+ raise ValidationError.new(fields: errors) unless errors.empty?
38
+
39
+ sanitized
40
+ end
41
+
42
+ def self.validate_field(field, value, has_key, is_update)
43
+ rules = field.validation.is_a?(Hash) ? field.validation : nil
44
+
45
+ if field.required
46
+ return [resolve_message(rules, "This field is required")] if !has_key && !is_update
47
+ return [resolve_message(rules, "This field is required")] if has_key && (value.nil? || value.to_s.strip.empty?)
48
+ end
49
+
50
+ return [] unless has_key && !value.nil? && rules
51
+
52
+ collect_rule_errors(field, rules, value)
53
+ end
54
+
55
+ def self.collect_rule_errors(field, rules, value)
56
+ errs = []
57
+
58
+ if field.type.to_s == "string" && value.is_a?(String)
59
+ len_err = validate_string_length(rules, value)
60
+ errs << len_err if len_err
61
+ end
62
+
63
+ if (rules[:pattern] || rules["pattern"]) && value.is_a?(String)
64
+ pat_err = validate_pattern(rules, value)
65
+ errs << pat_err if pat_err
66
+ end
67
+
68
+ if field.type.to_s == "number"
69
+ num_err = validate_numeric_range(rules, value)
70
+ errs << num_err if num_err
71
+ end
72
+
73
+ errs
74
+ end
75
+
76
+ def self.validate_string_length(rules, value)
77
+ min_len = rules[:min_length] || rules["min_length"]
78
+ max_len = rules[:max_length] || rules["max_length"]
79
+
80
+ return resolve_message(rules, "Minimum length is #{min_len}") if min_len && value.length < min_len
81
+ return resolve_message(rules, "Maximum length is #{max_len}") if max_len && value.length > max_len
82
+
83
+ nil
84
+ end
85
+
86
+ def self.validate_pattern(rules, value)
87
+ pattern = rules[:pattern] || rules["pattern"]
88
+ return nil unless pattern
89
+
90
+ regex = pattern.is_a?(Regexp) ? pattern : Regexp.new(pattern.to_s)
91
+ return nil if regex.match?(value)
92
+
93
+ resolve_message(rules, "Does not match the required format")
94
+ end
95
+
96
+ def self.validate_numeric_range(rules, value)
97
+ min = rules[:min] || rules["min"]
98
+ max = rules[:max] || rules["max"]
99
+ return nil unless min || max
100
+
101
+ num = parse_numeric(value)
102
+ return nil unless num
103
+
104
+ return resolve_message(rules, "Minimum value is #{min}") if min && num < min
105
+ return resolve_message(rules, "Maximum value is #{max}") if max && num > max
106
+
107
+ nil
108
+ end
109
+
110
+ def self.parse_numeric(value)
111
+ return value if value.is_a?(Numeric)
112
+ return nil unless value.is_a?(String) && value =~ /\A-?\d+(\.\d+)?\z/
113
+
114
+ value.include?(".") ? value.to_f : value.to_i
115
+ end
116
+
117
+ def self.resolve_message(rules, default_message)
118
+ rules&.dig(:custom_message) || rules&.dig("custom_message") || default_message
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Kabk module namespace
4
+ module Kabk
5
+ VERSION = "1.0.0"
6
+ end
data/lib/kabk.rb ADDED
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "kabk/version"
4
+ require_relative "kabk/registry"
5
+ require_relative "kabk/resource"
6
+ require_relative "kabk/field"
7
+ require_relative "kabk/resource_builder"
8
+ require_relative "kabk/schema_renderer"
9
+ require_relative "kabk/errors"
10
+ require_relative "kabk/concurrency"
11
+ require_relative "kabk/validator"
12
+ require_relative "kabk/query_builder"
13
+ require_relative "kabk/relation_hydrator"
14
+ require_relative "kabk/rest_engine"
15
+ require_relative "kabk/upload_handler"
16
+ require_relative "kabk/adapters/base"
17
+ require_relative "kabk/adapters/sequel_adapter"
18
+
19
+ # Core namespace and entrypoint for Kabk
20
+ module Kabk
21
+ # Register a new model as a resource
22
+ #
23
+ # @param name [String, Symbol] The unique identifier for the resource
24
+ # @param table [Class] The Model class (e.g. Sequel::Model)
25
+ # @param adapter [Kabk::Adapters::Base] Optional explicit adapter instance
26
+ # @yield Block evaluated in the context of a ResourceBuilder
27
+ def self.register(name:, table:, adapter: nil, &block)
28
+ resource = Resource.new(name: name, model_class: table)
29
+ builder = ResourceBuilder.new(resource)
30
+ builder.instance_eval(&block) if block_given?
31
+
32
+ # Fallbacks per protocol
33
+ resource.plural_name ||= "#{name}s"
34
+ resource.title ||= resource.plural_name.capitalize
35
+ resource.icon ||= "Database"
36
+ resource.api_path ||= "/api/admin/#{resource.plural_name}"
37
+
38
+ # Determine and assign adapter
39
+ if adapter
40
+ resource.adapter = adapter
41
+ elsif defined?(Sequel::Model) && table < Sequel::Model
42
+ resource.adapter = Adapters::SequelAdapter.new(resource)
43
+ else
44
+ raise ArgumentError, "Could not determine appropriate adapter for #{table}. Please provide an explicit adapter."
45
+ end
46
+
47
+ Registry.instance.register(resource)
48
+ resource
49
+ end
50
+ end
metadata ADDED
@@ -0,0 +1,178 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: kabk
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Parham Taheri
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: concurrent-ruby
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '1.1'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '1.1'
26
+ - !ruby/object:Gem::Dependency
27
+ name: sequel
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '5.0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '5.0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: yard
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '0.9'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '0.9'
54
+ - !ruby/object:Gem::Dependency
55
+ name: webrick
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ - !ruby/object:Gem::Dependency
69
+ name: rubocop
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '1.21'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '1.21'
82
+ description: Framework-agnostic engine that provides resource registration, JSON schema
83
+ manifest generation, generic CRUD engine, server-side validation, and relation hydration.
84
+ email:
85
+ - Par_taheri@yahoo.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - CHANGELOG.md
91
+ - Gemfile
92
+ - Gemfile.lock
93
+ - Kabk_logo.svg
94
+ - LICENSE
95
+ - README.md
96
+ - doc/Kabk.html
97
+ - doc/Kabk/Adapters.html
98
+ - doc/Kabk/Adapters/Base.html
99
+ - doc/Kabk/Adapters/SequelAdapter.html
100
+ - doc/Kabk/ApiError.html
101
+ - doc/Kabk/Auth.html
102
+ - doc/Kabk/Auth/JwtStrategy.html
103
+ - doc/Kabk/Concurrency.html
104
+ - doc/Kabk/ConcurrencyConflictError.html
105
+ - doc/Kabk/Error.html
106
+ - doc/Kabk/ExportHandler.html
107
+ - doc/Kabk/Field.html
108
+ - doc/Kabk/ForbiddenError.html
109
+ - doc/Kabk/InvalidFieldError.html
110
+ - doc/Kabk/InvalidOldPasswordError.html
111
+ - doc/Kabk/NotFoundError.html
112
+ - doc/Kabk/QueryBuilder.html
113
+ - doc/Kabk/Registry.html
114
+ - doc/Kabk/RelationHydrator.html
115
+ - doc/Kabk/Resource.html
116
+ - doc/Kabk/ResourceBuilder.html
117
+ - doc/Kabk/RestEngine.html
118
+ - doc/Kabk/SchemaRenderer.html
119
+ - doc/Kabk/UnauthorizedError.html
120
+ - doc/Kabk/UploadHandler.html
121
+ - doc/Kabk/ValidationError.html
122
+ - doc/Kabk/Validator.html
123
+ - doc/_index.html
124
+ - doc/class_list.html
125
+ - doc/css/common.css
126
+ - doc/css/full_list.css
127
+ - doc/css/style.css
128
+ - doc/file.README.html
129
+ - doc/file_list.html
130
+ - doc/frames.html
131
+ - doc/index.html
132
+ - doc/js/app.js
133
+ - doc/js/full_list.js
134
+ - doc/js/jquery.js
135
+ - doc/method_list.html
136
+ - doc/top-level-namespace.html
137
+ - docs/API_REFERENCE.md
138
+ - lib/kabk.rb
139
+ - lib/kabk/adapters/base.rb
140
+ - lib/kabk/adapters/sequel_adapter.rb
141
+ - lib/kabk/concurrency.rb
142
+ - lib/kabk/errors.rb
143
+ - lib/kabk/field.rb
144
+ - lib/kabk/query_builder.rb
145
+ - lib/kabk/registry.rb
146
+ - lib/kabk/relation_hydrator.rb
147
+ - lib/kabk/resource.rb
148
+ - lib/kabk/resource_builder.rb
149
+ - lib/kabk/rest_engine.rb
150
+ - lib/kabk/schema_renderer.rb
151
+ - lib/kabk/upload_handler.rb
152
+ - lib/kabk/validator.rb
153
+ - lib/kabk/version.rb
154
+ homepage: https://github.com/par-taheri/Kabk
155
+ licenses:
156
+ - MIT
157
+ metadata:
158
+ homepage_uri: https://github.com/par-taheri/Kabk
159
+ source_code_uri: https://github.com/par-taheri/Kabk
160
+ changelog_uri: https://github.com/par-taheri/Kabk/blob/main/CHANGELOG.md
161
+ rdoc_options: []
162
+ require_paths:
163
+ - lib
164
+ required_ruby_version: !ruby/object:Gem::Requirement
165
+ requirements:
166
+ - - ">="
167
+ - !ruby/object:Gem::Version
168
+ version: 3.1.0
169
+ required_rubygems_version: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ requirements: []
175
+ rubygems_version: 4.0.16
176
+ specification_version: 4
177
+ summary: Core engine for Schema-Driven Dynamic Admin Specification (Protocol v1.6.0).
178
+ test_files: []