proxes 0.9.4 → 0.9.7
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/.gitignore +2 -0
- data/Dockerfile +2 -2
- data/Gemfile.deploy +1 -0
- data/Gemfile.deploy.lock +46 -28
- data/Gemfile.dev +3 -1
- data/config/settings.yml +7 -0
- data/docker-compose.yml +6 -3
- data/lib/ditty/components/proxes.rb +3 -0
- data/lib/proxes/controllers/search.rb +45 -0
- data/lib/proxes/controllers/status.rb +1 -1
- data/lib/proxes/forwarder.rb +0 -7
- data/lib/proxes/middleware/error_handling.rb +25 -37
- data/lib/proxes/middleware/security.rb +0 -1
- data/lib/proxes/models/permission.rb +18 -2
- data/lib/proxes/policies/request_policy.rb +1 -1
- data/lib/proxes/request/stats.rb +4 -0
- data/lib/proxes/services/es.rb +0 -4
- data/lib/proxes/services/listener.rb +29 -0
- data/lib/proxes/services/search.rb +43 -0
- data/lib/proxes/version.rb +1 -1
- data/package.json +1 -1
- data/proxes.gemspec +3 -3
- data/public/css/typeahead.css +94 -0
- data/public/js/bundle.js +34695 -21264
- data/public/js/typeahead.bundle.min.js +8 -0
- data/src/scripts/app.js +8 -7
- data/views/search/index.haml +95 -0
- metadata +22 -16
- data/config/logger.yml +0 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0ef233fd8fd3cf4118e820483e703279eac3921ae2c92dee336f802c0c6f5df2
|
|
4
|
+
data.tar.gz: 8e7fb52b8b0e152de449d490545d96944502cce8db7491878d90e9042b572c1b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4301310adb06293c72b22595985959883c46a316a702995074d5f74b30535c54541ab185725f20edc2b8ba54dcd19d31076bac1e3427b78ae93f0f701b8bd371
|
|
7
|
+
data.tar.gz: a40f54478277272699c56a5067dd4cf13c8f59feebed6e6cc8d98ec06678e030b6ef1a683c4f3c676f74e7613afb04b8d1e3a8b4bf9d604015d096c58f0014e6
|
data/.gitignore
CHANGED
data/Dockerfile
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
FROM ruby:2.4-
|
|
1
|
+
FROM ruby:2.4-alpine3.4
|
|
2
2
|
MAINTAINER Jurgens du Toit <jurgens@datatools.io>
|
|
3
3
|
|
|
4
4
|
EXPOSE 9292
|
|
@@ -25,7 +25,7 @@ RUN apk add --update \
|
|
|
25
25
|
|
|
26
26
|
ADD views /usr/src/app/views
|
|
27
27
|
COPY config.ru /usr/src/app/
|
|
28
|
-
COPY config/
|
|
28
|
+
COPY config/settings.yml /usr/src/app/config/
|
|
29
29
|
COPY config/puma.rb /usr/src/app/config/
|
|
30
30
|
COPY Gemfile.deploy /usr/src/app/Gemfile
|
|
31
31
|
COPY Gemfile.deploy.lock /usr/src/app/Gemfile.lock
|
data/Gemfile.deploy
CHANGED
data/Gemfile.deploy.lock
CHANGED
|
@@ -1,58 +1,70 @@
|
|
|
1
1
|
GEM
|
|
2
2
|
remote: https://rubygems.org/
|
|
3
3
|
specs:
|
|
4
|
-
activesupport (5.
|
|
4
|
+
activesupport (5.2.0)
|
|
5
5
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
|
6
|
-
i18n (
|
|
6
|
+
i18n (>= 0.7, < 2)
|
|
7
7
|
minitest (~> 5.1)
|
|
8
8
|
tzinfo (~> 1.1)
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
ansi (1.5.0)
|
|
10
|
+
ast (2.4.0)
|
|
11
|
+
backports (3.11.3)
|
|
12
|
+
bcrypt (3.1.12)
|
|
11
13
|
bcrypt-ruby (3.1.5)
|
|
12
14
|
bcrypt (>= 3.1.3)
|
|
13
15
|
concurrent-ruby (1.0.5)
|
|
14
|
-
connection_pool (2.2.
|
|
15
|
-
ditty (0.
|
|
16
|
+
connection_pool (2.2.2)
|
|
17
|
+
ditty (0.6.0)
|
|
16
18
|
activesupport (>= 3)
|
|
17
19
|
bcrypt (~> 3.1)
|
|
18
20
|
haml (~> 5.0)
|
|
19
21
|
logger (~> 1.0)
|
|
22
|
+
mail (>= 1.7)
|
|
23
|
+
oga (>= 2.14)
|
|
20
24
|
omniauth (~> 1.0)
|
|
21
25
|
omniauth-identity (~> 1.0)
|
|
22
26
|
pundit (~> 1.0)
|
|
23
27
|
rack-contrib (~> 1.0)
|
|
24
28
|
rake (~> 12.0)
|
|
25
|
-
sequel (
|
|
26
|
-
sinatra (
|
|
29
|
+
sequel (>= 4.0)
|
|
30
|
+
sinatra (>= 2.0)
|
|
27
31
|
sinatra-contrib (~> 2.0)
|
|
28
32
|
sinatra-flash (~> 0.3)
|
|
33
|
+
thor (>= 0.20)
|
|
29
34
|
tilt (>= 2)
|
|
35
|
+
will_paginate (>= 3.1)
|
|
30
36
|
wisper (~> 2.0)
|
|
31
|
-
dotenv (2.
|
|
32
|
-
elasticsearch (6.0
|
|
33
|
-
elasticsearch-api (= 6.0
|
|
34
|
-
elasticsearch-transport (= 6.0
|
|
35
|
-
elasticsearch-api (6.0
|
|
37
|
+
dotenv (2.5.0)
|
|
38
|
+
elasticsearch (6.1.0)
|
|
39
|
+
elasticsearch-api (= 6.1.0)
|
|
40
|
+
elasticsearch-transport (= 6.1.0)
|
|
41
|
+
elasticsearch-api (6.1.0)
|
|
36
42
|
multi_json
|
|
37
|
-
elasticsearch-transport (6.0
|
|
43
|
+
elasticsearch-transport (6.1.0)
|
|
38
44
|
faraday
|
|
39
45
|
multi_json
|
|
40
|
-
faraday (0.
|
|
46
|
+
faraday (0.15.2)
|
|
41
47
|
multipart-post (>= 1.2, < 3)
|
|
42
48
|
haml (5.0.4)
|
|
43
49
|
temple (>= 0.8.0)
|
|
44
50
|
tilt
|
|
45
51
|
hashie (3.5.7)
|
|
46
52
|
highline (1.7.10)
|
|
47
|
-
i18n (0.
|
|
53
|
+
i18n (1.0.1)
|
|
48
54
|
concurrent-ruby (~> 1.0)
|
|
49
55
|
logger (1.2.8)
|
|
56
|
+
mail (2.7.0)
|
|
57
|
+
mini_mime (>= 0.1.1)
|
|
58
|
+
mini_mime (1.0.0)
|
|
50
59
|
minitest (5.11.3)
|
|
51
60
|
multi_json (1.13.1)
|
|
52
61
|
multipart-post (2.0.0)
|
|
53
|
-
mustermann (1.0.
|
|
62
|
+
mustermann (1.0.2)
|
|
54
63
|
net-http-persistent (3.0.0)
|
|
55
64
|
connection_pool (~> 2.2)
|
|
65
|
+
oga (2.15)
|
|
66
|
+
ast
|
|
67
|
+
ruby-ll (~> 2.1)
|
|
56
68
|
omniauth (1.8.1)
|
|
57
69
|
hashie (>= 3.4.6, < 3.6.0)
|
|
58
70
|
rack (>= 1.6.2, < 3)
|
|
@@ -62,7 +74,7 @@ GEM
|
|
|
62
74
|
bcrypt-ruby (~> 3.0)
|
|
63
75
|
omniauth (~> 1.0)
|
|
64
76
|
pg (1.0.0)
|
|
65
|
-
proxes (0.9.
|
|
77
|
+
proxes (0.9.4)
|
|
66
78
|
activesupport (>= 3)
|
|
67
79
|
bcrypt (~> 3.1)
|
|
68
80
|
ditty (>= 0.2)
|
|
@@ -84,35 +96,41 @@ GEM
|
|
|
84
96
|
sinatra-flash (~> 0.3)
|
|
85
97
|
tilt (>= 2)
|
|
86
98
|
wisper (~> 2.0)
|
|
87
|
-
puma (3.
|
|
99
|
+
puma (3.12.0)
|
|
88
100
|
pundit (1.1.0)
|
|
89
101
|
activesupport (>= 3.0.0)
|
|
90
|
-
rack (2.0.
|
|
102
|
+
rack (2.0.5)
|
|
91
103
|
rack-contrib (1.2.0)
|
|
92
104
|
rack (>= 0.9.1)
|
|
93
|
-
rack-protection (2.0.
|
|
105
|
+
rack-protection (2.0.3)
|
|
94
106
|
rack
|
|
95
|
-
rake (12.3.
|
|
107
|
+
rake (12.3.1)
|
|
108
|
+
ruby-ll (2.1.2)
|
|
109
|
+
ansi
|
|
110
|
+
ast
|
|
96
111
|
sequel (4.49.0)
|
|
97
|
-
sinatra (2.0.
|
|
112
|
+
sinatra (2.0.3)
|
|
98
113
|
mustermann (~> 1.0)
|
|
99
114
|
rack (~> 2.0)
|
|
100
|
-
rack-protection (= 2.0.
|
|
115
|
+
rack-protection (= 2.0.3)
|
|
101
116
|
tilt (~> 2.0)
|
|
102
|
-
sinatra-contrib (2.0.
|
|
103
|
-
|
|
117
|
+
sinatra-contrib (2.0.3)
|
|
118
|
+
activesupport (>= 4.0.0)
|
|
119
|
+
backports (>= 2.8.2)
|
|
104
120
|
multi_json
|
|
105
121
|
mustermann (~> 1.0)
|
|
106
|
-
rack-protection (= 2.0.
|
|
107
|
-
sinatra (= 2.0.
|
|
122
|
+
rack-protection (= 2.0.3)
|
|
123
|
+
sinatra (= 2.0.3)
|
|
108
124
|
tilt (>= 1.3, < 3)
|
|
109
125
|
sinatra-flash (0.3.0)
|
|
110
126
|
sinatra (>= 1.0.0)
|
|
111
127
|
temple (0.8.0)
|
|
128
|
+
thor (0.20.0)
|
|
112
129
|
thread_safe (0.3.6)
|
|
113
130
|
tilt (2.0.8)
|
|
114
131
|
tzinfo (1.2.5)
|
|
115
132
|
thread_safe (~> 0.1)
|
|
133
|
+
will_paginate (3.1.6)
|
|
116
134
|
wisper (2.0.0)
|
|
117
135
|
|
|
118
136
|
PLATFORMS
|
data/Gemfile.dev
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
source 'https://rubygems.org'
|
|
2
3
|
|
|
3
4
|
# Specify your gem's dependencies in proxes.gemspec
|
|
4
5
|
gemspec
|
|
5
6
|
|
|
7
|
+
gem 'ditty', path: '../ditty'
|
|
6
8
|
gem 'dotenv'
|
|
7
9
|
gem 'pry-byebug'
|
|
8
10
|
gem 'puma'
|
|
9
11
|
gem 'rerun', git: 'https://github.com/alexch/rerun.git', branch: 'master'
|
|
10
12
|
gem 'simplecov'
|
|
11
|
-
gem '
|
|
13
|
+
gem 'pg'
|
data/config/settings.yml
ADDED
data/docker-compose.yml
CHANGED
|
@@ -2,16 +2,19 @@ version: '3'
|
|
|
2
2
|
services:
|
|
3
3
|
db:
|
|
4
4
|
image: postgres
|
|
5
|
-
|
|
5
|
+
container_name: postgres
|
|
6
|
+
elasticsearch:
|
|
6
7
|
image: elasticsearch
|
|
8
|
+
container_name: elasticsearch
|
|
7
9
|
web:
|
|
8
10
|
image: eagerelk/proxes:latest
|
|
11
|
+
container_name: web-proxes
|
|
9
12
|
command: web-proxes
|
|
10
13
|
ports:
|
|
11
14
|
- '9292:9292'
|
|
12
15
|
environment:
|
|
13
16
|
- DATABASE_URL=postgres://postgres:@db/postgres
|
|
14
|
-
- ELASTICSEARCH_URL=http://
|
|
17
|
+
- ELASTICSEARCH_URL=http://elasticsearch:9200
|
|
15
18
|
depends_on:
|
|
16
19
|
- db
|
|
17
|
-
-
|
|
20
|
+
- elasticsearch
|
|
@@ -8,6 +8,7 @@ module Ditty
|
|
|
8
8
|
controllers = File.expand_path('../../../proxes/controllers', __FILE__)
|
|
9
9
|
Dir.glob("#{controllers}/*.rb").each { |f| require f }
|
|
10
10
|
require 'proxes/models/permission'
|
|
11
|
+
require 'proxes/services/listener'
|
|
11
12
|
end
|
|
12
13
|
|
|
13
14
|
def self.migrations
|
|
@@ -25,6 +26,7 @@ module Ditty
|
|
|
25
26
|
def self.routes
|
|
26
27
|
load
|
|
27
28
|
{
|
|
29
|
+
'/search' => ::ProxES::Search,
|
|
28
30
|
'/status' => ::ProxES::Status,
|
|
29
31
|
'/permissions' => ::ProxES::Permissions
|
|
30
32
|
}
|
|
@@ -34,6 +36,7 @@ module Ditty
|
|
|
34
36
|
load
|
|
35
37
|
[
|
|
36
38
|
{ order: 0, link: '/status/check', text: 'Status Check', target: ::ProxES::Status, icon: 'dashboard' },
|
|
39
|
+
{ order: 1, link: '/search', text: 'Search', target: ::ProxES::Status, icon: 'search' },
|
|
37
40
|
{ order: 15, link: '/permissions/', text: 'Permissions', target: ::ProxES::Permission, icon: 'check-square' }
|
|
38
41
|
]
|
|
39
42
|
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'active_support/core_ext/object/blank'
|
|
4
|
+
require 'ditty/controllers/application'
|
|
5
|
+
require 'proxes/services/search'
|
|
6
|
+
|
|
7
|
+
module ProxES
|
|
8
|
+
class Search < Ditty::Application
|
|
9
|
+
set base_path: "#{settings.map_path}/search"
|
|
10
|
+
|
|
11
|
+
get '/' do
|
|
12
|
+
page = (params['page'] || 1).to_i
|
|
13
|
+
size = (params['count'] || 25).to_i
|
|
14
|
+
from = ((page - 1) * size)
|
|
15
|
+
params['q'] = '*' if params['q'].blank?
|
|
16
|
+
result = ProxES::Services::Search.search(params['q'], index: params['indices'], from: from, size: size)
|
|
17
|
+
haml :"#{view_location}/index",
|
|
18
|
+
locals: {
|
|
19
|
+
title: 'Search',
|
|
20
|
+
indices: ProxES::Services::Search.indices,
|
|
21
|
+
fields: ProxES::Services::Search.fields(params['indices']),
|
|
22
|
+
result: result
|
|
23
|
+
}
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
get '/fields/?:indices?/?' do
|
|
27
|
+
json ProxES::Services::Search.fields params['indices']
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
get '/indices/?' do
|
|
31
|
+
json ProxES::Services::Search.indices
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
get '/values/:field/?:indices?/?' do |field|
|
|
35
|
+
size = params['size'] || 25
|
|
36
|
+
json ProxES::Services::Search.values(field, size: size, index: params['indices'])
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def find_template(views, name, engine, &block)
|
|
40
|
+
super(views, name, engine, &block) # Root
|
|
41
|
+
super(::Ditty::ProxES.view_folder, name, engine, &block) # This Component
|
|
42
|
+
super(::Ditty::App.view_folder, name, engine, &block) # Ditty
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -19,7 +19,7 @@ module ProxES
|
|
|
19
19
|
get '/check' do
|
|
20
20
|
checks = []
|
|
21
21
|
begin
|
|
22
|
-
health =
|
|
22
|
+
health = client.cluster.health level: 'cluster'
|
|
23
23
|
checks << { text: 'Cluster Reachable', passed: true, value: health['cluster_name'] }
|
|
24
24
|
checks << { text: 'Cluster Health', passed: health['status'] == 'green', value: health['status'] }
|
|
25
25
|
|
data/lib/proxes/forwarder.rb
CHANGED
|
@@ -10,13 +10,6 @@ module ProxES
|
|
|
10
10
|
include ProxES::Services::ES
|
|
11
11
|
|
|
12
12
|
def call(env)
|
|
13
|
-
forward(env)
|
|
14
|
-
rescue SocketError
|
|
15
|
-
headers = { 'Content-Type' => 'application/json' }
|
|
16
|
-
[500, headers, ['{"error":"Could not connect to Elasticsearch"}']]
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def forward(env)
|
|
20
13
|
source = Rack::Request.new(env)
|
|
21
14
|
response = conn.send(source.request_method.downcase) do |req|
|
|
22
15
|
source_body = body_from(source)
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require '
|
|
3
|
+
require 'wisper'
|
|
4
|
+
require 'proxes/request'
|
|
5
|
+
require 'ditty/services/logger'
|
|
4
6
|
|
|
5
7
|
module ProxES
|
|
6
8
|
module Middleware
|
|
@@ -8,7 +10,6 @@ module ProxES
|
|
|
8
10
|
attr_reader :logger
|
|
9
11
|
|
|
10
12
|
include Wisper::Publisher
|
|
11
|
-
include Ditty::Helpers::Wisper
|
|
12
13
|
|
|
13
14
|
def initialize(app, logger = nil)
|
|
14
15
|
@app = app
|
|
@@ -16,47 +17,29 @@ module ProxES
|
|
|
16
17
|
end
|
|
17
18
|
|
|
18
19
|
def call(env)
|
|
19
|
-
request = Request.from_env(env)
|
|
20
|
-
|
|
21
|
-
unless (200..299).cover?
|
|
22
|
-
|
|
23
|
-
:es_request_failed,
|
|
24
|
-
user: request.user,
|
|
25
|
-
details: "#{request.request_method.upcase} #{request.fullpath} (#{request.class.name})"
|
|
26
|
-
)
|
|
27
|
-
end
|
|
28
|
-
[code, headers, body]
|
|
20
|
+
request = ProxES::Request.from_env(env)
|
|
21
|
+
response = @app.call env
|
|
22
|
+
broadcast(:es_request_failed, request, response) unless (200..299).cover?(response[0])
|
|
23
|
+
response
|
|
29
24
|
rescue Errno::EHOSTUNREACH
|
|
30
25
|
error 'Could not reach Elasticsearch at ' + ENV['ELASTICSEARCH_URL']
|
|
31
|
-
rescue Errno::ECONNREFUSED, Faraday::ConnectionFailed
|
|
26
|
+
rescue Errno::ECONNREFUSED, Faraday::ConnectionFailed, SocketError
|
|
32
27
|
error 'Elasticsearch not listening at ' + ENV['ELASTICSEARCH_URL']
|
|
33
|
-
rescue Pundit::NotAuthorizedError, Ditty::Helpers::NotAuthenticated
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
user = request.user ? request.user.email : 'unauthenticated request'
|
|
40
|
-
logger.error "Access denied for #{user} by security layer: #{request.detail}"
|
|
41
|
-
|
|
42
|
-
failed request, 'Not Authorized', :es_request_denied, 401
|
|
28
|
+
rescue Pundit::NotAuthorizedError, Ditty::Helpers::NotAuthenticated => e
|
|
29
|
+
broadcast(:es_request_denied, request, e)
|
|
30
|
+
log_not_authorized request
|
|
31
|
+
raise e if env['APP_ENV'] == 'development'
|
|
32
|
+
request.html? && request.user.nil? ? login_and_redirect(request) : error('Not Authorized', 401)
|
|
43
33
|
rescue StandardError => e
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
logger.error e
|
|
49
|
-
|
|
50
|
-
failed request, 'Forbidden', :es_request_denied, 403
|
|
34
|
+
broadcast(:es_request_denied, request, e)
|
|
35
|
+
log_not_authorized request
|
|
36
|
+
raise e if env['APP_ENV'] == 'development'
|
|
37
|
+
error 'Forbidden', 403
|
|
51
38
|
end
|
|
52
39
|
|
|
53
|
-
def
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
user: request.user,
|
|
57
|
-
details: "#{request.request_method.upcase} #{request.fullpath} (#{request.class.name})"
|
|
58
|
-
)
|
|
59
|
-
error message, code
|
|
40
|
+
def log_not_authorized(request)
|
|
41
|
+
user = request.user ? request.user.email : 'unauthenticated request'
|
|
42
|
+
logger.error "Access denied for #{user} by security layer: #{request.detail}"
|
|
60
43
|
end
|
|
61
44
|
|
|
62
45
|
# Response Helpers
|
|
@@ -66,6 +49,11 @@ module ProxES
|
|
|
66
49
|
[code, headers, ['{"error":"' + message + '"}']]
|
|
67
50
|
end
|
|
68
51
|
|
|
52
|
+
def login_and_redirect(request)
|
|
53
|
+
request.session['omniauth.origin'] = request.url unless request.url == '/_proxes/auth/identity'
|
|
54
|
+
redirect '/_proxes/auth/identity'
|
|
55
|
+
end
|
|
56
|
+
|
|
69
57
|
def redirect(destination, code = 302)
|
|
70
58
|
[code, { 'Location' => destination }, []]
|
|
71
59
|
end
|
|
@@ -24,8 +24,18 @@ module ProxES
|
|
|
24
24
|
validates_includes self.class.verbs, :verb
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
class << self
|
|
28
|
+
def verbs
|
|
29
|
+
%w[GET POST PUT DELETE HEAD OPTIONS TRACE INDEX]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def from_audit_log(audit_log)
|
|
33
|
+
match = audit_log.details.match(/^(\w)+ (\S+)/)
|
|
34
|
+
{
|
|
35
|
+
verb: match[1],
|
|
36
|
+
path: match[2]
|
|
37
|
+
}
|
|
38
|
+
end
|
|
29
39
|
end
|
|
30
40
|
end
|
|
31
41
|
end
|
|
@@ -35,3 +45,9 @@ module Ditty
|
|
|
35
45
|
one_to_many :permissions, class: ::ProxES::Permission
|
|
36
46
|
end
|
|
37
47
|
end
|
|
48
|
+
|
|
49
|
+
module Ditty
|
|
50
|
+
class Role < ::Sequel::Model
|
|
51
|
+
one_to_many :permissions, class: ::ProxES::Permission
|
|
52
|
+
end
|
|
53
|
+
end
|