graph_starter 0.15.6 → 0.16.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.
- checksums.yaml +4 -4
- data/app/assets/javascripts/graph_starter/application.coffee.erb +7 -0
- data/app/controllers/graph_starter/assets_controller.rb +21 -7
- data/app/controllers/graph_starter/categories_controller.rb +6 -64
- data/app/helpers/graph_starter/application_helper.rb +1 -1
- data/app/models/concerns/graph_starter/authorizable.rb +2 -2
- data/app/models/graph_starter/asset.rb +57 -17
- data/app/views/graph_starter/assets/_admin_buttons.html.slim +4 -3
- data/app/views/graph_starter/assets/_associations.html.slim +18 -17
- data/app/views/graph_starter/assets/_extra_admin_buttons.html +0 -0
- data/app/views/graph_starter/assets/_form.html.slim +5 -0
- data/app/views/graph_starter/assets/index.json.jbuilder +1 -0
- data/app/views/graph_starter/assets/show.html.slim +1 -0
- data/app/views/graph_starter/assets/show.json.jbuilder +17 -13
- data/app/views/layouts/graph_starter/application.html.slim +3 -2
- data/config/routes.rb +2 -1
- data/lib/graph_starter/query_authorizer.rb +19 -14
- data/lib/graph_starter/version.rb +1 -1
- data/spec/asset_authorization_spec.rb +136 -9
- data/spec/asset_slug_spec.rb +42 -34
- data/spec/factories.rb +11 -0
- data/spec/spec_helper.rb +5 -3
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 62e9a2ebc655144abef8886b72a43477b9c93ec5
|
4
|
+
data.tar.gz: 9e40816fa0f5026366513ed85a34932595c9afc5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 021ca13a9adbd7dab7651a9b2a3aebc75ff7e29284f505f620a1b58ad57cd44698008b32a98737e2a8efe7f46505f1fbb003d08bc70d152d89a0e7eb7acd97fa
|
7
|
+
data.tar.gz: 4d2a06c175bd8e901479b301b0618bed55bc23fc07025d911aa9ecec57a571dff25dd271a40a831ef209fd9ebb1ff2b884af74e4596ba80156ee3d515e16f0a4
|
@@ -29,6 +29,13 @@ ready = ->
|
|
29
29
|
$('.message > .close').on 'click', ->
|
30
30
|
$(@).closest('.message').transition('fade')
|
31
31
|
|
32
|
+
|
33
|
+
# Make sure page is in the right place when an anchor is clicked
|
34
|
+
# See: https://github.com/twbs/bootstrap/issues/1768
|
35
|
+
shiftWindow = -> scrollBy(0, -50)
|
36
|
+
shiftWindow() if location.hash
|
37
|
+
window.addEventListener('hashchange', shiftWindow)
|
38
|
+
|
32
39
|
$(document).ready ready
|
33
40
|
$(document).on 'page:load', ready
|
34
41
|
|
@@ -6,6 +6,7 @@ module GraphStarter
|
|
6
6
|
def index
|
7
7
|
@all_assets = asset_set(:asset, nil)
|
8
8
|
@assets = asset_set.to_a
|
9
|
+
@title = model_class.name.tableize.humanize
|
9
10
|
|
10
11
|
@category_images = Asset.where(id: @assets.map(&:categories).flatten.map(&:id))
|
11
12
|
.query_as(:asset)
|
@@ -38,15 +39,22 @@ module GraphStarter
|
|
38
39
|
render json: {results: results_data}.to_json
|
39
40
|
end
|
40
41
|
|
41
|
-
def
|
42
|
+
def require_model_class
|
42
43
|
# For cases where the route picked up more than it should have and we try to constantize something wrong
|
43
44
|
begin
|
44
45
|
model_class
|
45
46
|
rescue NameError
|
46
|
-
|
47
|
+
render text: 'Not found', status: :not_found
|
48
|
+
false
|
47
49
|
end
|
48
50
|
|
51
|
+
end
|
52
|
+
|
53
|
+
def show
|
54
|
+
return if !require_model_class
|
55
|
+
|
49
56
|
@asset = asset
|
57
|
+
@title = @asset.title
|
50
58
|
|
51
59
|
if @asset
|
52
60
|
View.record_view(@session_node,
|
@@ -60,6 +68,7 @@ module GraphStarter
|
|
60
68
|
|
61
69
|
def edit
|
62
70
|
@asset, @access_level = asset_with_access_level
|
71
|
+
@title = @asset.title.to_s + ' - Edit'
|
63
72
|
|
64
73
|
render file: 'public/404.html', status: :not_found, layout: false if !@asset
|
65
74
|
end
|
@@ -68,10 +77,12 @@ module GraphStarter
|
|
68
77
|
@asset = asset
|
69
78
|
@asset.update(params[params[:model_slug].singularize])
|
70
79
|
|
71
|
-
if
|
72
|
-
@asset.
|
73
|
-
|
74
|
-
@asset.
|
80
|
+
if params[:image].present?
|
81
|
+
if @asset.class.has_image?
|
82
|
+
@asset.image = Image.create(source: params[:image])
|
83
|
+
elsif @asset.class.has_images?
|
84
|
+
@asset.images << Image.create(source: params[:image])
|
85
|
+
end
|
75
86
|
end
|
76
87
|
|
77
88
|
redirect_to action: :edit
|
@@ -82,6 +93,8 @@ module GraphStarter
|
|
82
93
|
end
|
83
94
|
|
84
95
|
def create
|
96
|
+
return if !require_model_class
|
97
|
+
|
85
98
|
@asset = model_class.create(params[params[:model_slug].singularize])
|
86
99
|
|
87
100
|
if @asset.persisted?
|
@@ -120,6 +133,7 @@ module GraphStarter
|
|
120
133
|
associations.compact!
|
121
134
|
|
122
135
|
scope = model_class_scope(var)
|
136
|
+
scope = scope.for_category(var, params[:category]) if params[:category].present?
|
123
137
|
scope = yield scope if block_given?
|
124
138
|
|
125
139
|
scope = scope.limit(limit)
|
@@ -130,7 +144,7 @@ module GraphStarter
|
|
130
144
|
end
|
131
145
|
|
132
146
|
def asset
|
133
|
-
apply_associations(model_class_scope.where(uuid: params[:id])).to_a[0]
|
147
|
+
apply_associations(model_class_scope.as(:asset).where('asset.uuid = {id} OR asset.slug = {id}', id: params[:id])).to_a[0]
|
134
148
|
end
|
135
149
|
|
136
150
|
def asset_with_access_level
|
@@ -1,79 +1,21 @@
|
|
1
1
|
module GraphStarter
|
2
2
|
class CategoriesController < ApplicationController
|
3
|
-
before_action :set_category, only: [:show, :edit, :update, :destroy]
|
4
|
-
|
5
|
-
# GET /categories
|
6
|
-
# GET /categories.json
|
7
|
-
def index
|
8
|
-
@categories = Category.all
|
9
|
-
end
|
10
|
-
|
11
|
-
# GET /categories/1
|
12
|
-
# GET /categories/1.json
|
13
3
|
def show
|
14
|
-
|
15
|
-
|
16
|
-
# GET /categories/new
|
17
|
-
def new
|
18
|
-
@category = Category.new
|
19
|
-
end
|
20
|
-
|
21
|
-
# GET /categories/1/edit
|
22
|
-
def edit
|
23
|
-
end
|
24
|
-
|
25
|
-
# POST /categories
|
26
|
-
# POST /categories.json
|
27
|
-
def create
|
28
|
-
@category = Category.new(category_params)
|
29
|
-
|
30
|
-
respond_to do |format|
|
31
|
-
if @category.save
|
32
|
-
format.html { redirect_to @category, notice: 'Category was successfully created.' }
|
33
|
-
format.json { render :show, status: :created, location: @category }
|
34
|
-
else
|
35
|
-
format.html { render :new }
|
36
|
-
format.json { render json: @category.errors, status: :unprocessable_entity }
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
# PATCH/PUT /categories/1
|
42
|
-
# PATCH/PUT /categories/1.json
|
43
|
-
def update
|
44
|
-
respond_to do |format|
|
45
|
-
if @category.update(category_params)
|
46
|
-
format.html { redirect_to @category, notice: 'Category was successfully updated.' }
|
47
|
-
format.json { render :show, status: :ok, location: @category }
|
48
|
-
else
|
49
|
-
format.html { render :edit }
|
50
|
-
format.json { render json: @category.errors, status: :unprocessable_entity }
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
4
|
+
@category = category
|
54
5
|
|
55
|
-
|
56
|
-
# DELETE /categories/1.json
|
57
|
-
def destroy
|
58
|
-
@category.destroy
|
59
|
-
respond_to do |format|
|
60
|
-
format.html do
|
61
|
-
redirect_to categories_url, notice: 'Category was successfully destroyed.'
|
62
|
-
end
|
63
|
-
format.json { head :no_content }
|
64
|
-
end
|
6
|
+
render json: @category
|
65
7
|
end
|
66
8
|
|
67
9
|
private
|
68
10
|
|
69
11
|
# Use callbacks to share common setup or constraints between actions.
|
70
|
-
def
|
71
|
-
|
12
|
+
def category
|
13
|
+
Asset.find_by(slug: slug)
|
72
14
|
end
|
73
15
|
|
74
16
|
# Never trust parameters from the scary internet, only allow the white list through.
|
75
|
-
def
|
76
|
-
params
|
17
|
+
def slug
|
18
|
+
params.require(:slug)
|
77
19
|
end
|
78
20
|
end
|
79
21
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module GraphStarter
|
2
2
|
module ApplicationHelper
|
3
3
|
def asset_path(asset, options = {})
|
4
|
-
graph_starter.asset_path({id: asset, model_slug: asset.class.model_slug}.merge(options))
|
4
|
+
graph_starter.asset_path({id: asset.slug, model_slug: asset.class.model_slug}.merge(options))
|
5
5
|
end
|
6
6
|
|
7
7
|
def engine_view(&b)
|
@@ -3,8 +3,8 @@ module GraphStarter
|
|
3
3
|
extend ActiveSupport::Concern
|
4
4
|
|
5
5
|
included do
|
6
|
-
property :private, type: ActiveAttr::Typecasting::Boolean, default:
|
7
|
-
validates :private, inclusion: {in: [true, false]}
|
6
|
+
property :private, type: ActiveAttr::Typecasting::Boolean, default: nil
|
7
|
+
validates :private, inclusion: {in: [true, false, nil]}
|
8
8
|
|
9
9
|
if GraphStarter.configuration.user_class
|
10
10
|
has_many :in, :allowed_users, rel_class: :'GraphStarter::CanAccess', model_class: GraphStarter.configuration.user_class
|
@@ -12,30 +12,34 @@ module GraphStarter
|
|
12
12
|
|
13
13
|
property :summary
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
# subclass.before_validation :place_slug
|
20
|
-
# subclass.validates :slug, presence: true
|
21
|
-
# subclass.constraint :slug, type: :unique
|
22
|
-
# end
|
15
|
+
property :slug
|
16
|
+
before_validation :place_slug
|
17
|
+
validates :slug, presence: true, uniqueness: true
|
18
|
+
constraint :slug, type: :unique
|
23
19
|
|
24
20
|
def place_slug
|
25
21
|
return if self.slug.present?
|
26
22
|
|
27
|
-
|
28
|
-
self.slug = self.class.unique_slug_from(name_value)
|
29
|
-
name_value.to_slug.normalize.to_s if name_value
|
23
|
+
self.slug = self.class.unique_slug_from(safe_title)
|
30
24
|
end
|
31
25
|
|
32
26
|
def self.unique_slug_from(string)
|
33
|
-
|
27
|
+
if string.present?
|
28
|
+
slug = string.to_slug.normalize.to_s
|
29
|
+
while where(slug: slug).count > 0
|
30
|
+
if slug.match(/-\d+$/)
|
31
|
+
slug.gsub!(/-(\d+)$/) { "-#{$1.to_i + 1}" }
|
32
|
+
else
|
33
|
+
slug += '-2'
|
34
|
+
end
|
35
|
+
end
|
36
|
+
slug
|
37
|
+
end
|
34
38
|
end
|
35
39
|
|
36
40
|
|
37
41
|
if GraphStarter.configuration.user_class
|
38
|
-
|
42
|
+
has_many :in, :creators, type: :CREATED, model_class: GraphStarter.configuration.user_class
|
39
43
|
|
40
44
|
has_many :in, :viewer_sessions, rel_class: :'GraphStarter::View', model_class: 'GraphStarter::Session'
|
41
45
|
|
@@ -115,6 +119,17 @@ module GraphStarter
|
|
115
119
|
end
|
116
120
|
|
117
121
|
|
122
|
+
def self.for_category(node_var, category_slug)
|
123
|
+
string = category_associations.map do |association_name|
|
124
|
+
association = GraphGist.associations[association_name]
|
125
|
+
|
126
|
+
"(#{node_var}#{association.arrow_cypher}(:#{association.target_class.mapped_label_name} {slug: {category_slug}}))"
|
127
|
+
end.join(' OR ')
|
128
|
+
|
129
|
+
all.where(string, category_slug: category_slug)
|
130
|
+
end
|
131
|
+
|
132
|
+
|
118
133
|
def self.enumerable_property(property_name, values)
|
119
134
|
fail "values needs to be an Array, was #{values.inspect}" if !values.is_a?(Array)
|
120
135
|
|
@@ -219,6 +234,25 @@ module GraphStarter
|
|
219
234
|
end
|
220
235
|
|
221
236
|
|
237
|
+
def self.hidden_json_properties(*property_names)
|
238
|
+
if property_names.empty?
|
239
|
+
@hidden_json_properties || []
|
240
|
+
else
|
241
|
+
@hidden_json_properties = property_names.map(&:to_sym)
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
|
246
|
+
def self.json_methods(*method_names)
|
247
|
+
if method_names.empty?
|
248
|
+
@json_methods || []
|
249
|
+
else
|
250
|
+
@json_methods = method_names.map(&:to_sym)
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
|
255
|
+
|
222
256
|
def rating_level_for(user)
|
223
257
|
rating = rating_for(user)
|
224
258
|
rating && rating.level
|
@@ -296,13 +330,19 @@ module GraphStarter
|
|
296
330
|
id: id,
|
297
331
|
title: title,
|
298
332
|
name: title,
|
333
|
+
slug: slug,
|
299
334
|
model_slug: self.class.model_slug,
|
300
335
|
summary: summary,
|
301
|
-
categories: categories
|
336
|
+
categories: categories # DEPRECATED
|
302
337
|
}.tap do |result|
|
303
338
|
result[:image_urls] = image_array.map(&:source_url) if image_array
|
304
339
|
result[:images] = images.map {|image| image.source.url } if self.class.has_images?
|
305
340
|
result[:image] = image.source_url if self.class.has_image? && image
|
341
|
+
|
342
|
+
self.class.category_associations.each do |association_name|
|
343
|
+
result[association_name] = send(association_name)
|
344
|
+
result[association_name].uniq! if result[association_name] && result[association_name].respond_to?(:to_a)
|
345
|
+
end
|
306
346
|
end
|
307
347
|
|
308
348
|
options[:root] ? {self.class.model_slug.singularize => data} : data
|
@@ -336,7 +376,7 @@ module GraphStarter
|
|
336
376
|
def self.authorized_for(user)
|
337
377
|
require 'graph_starter/query_authorizer'
|
338
378
|
|
339
|
-
query,
|
379
|
+
query, var, sec_var = if category_associations.size > 0
|
340
380
|
query = all(:asset).query
|
341
381
|
|
342
382
|
category_associations
|
@@ -346,14 +386,14 @@ module GraphStarter
|
|
346
386
|
query = query.optional_match("(asset)-[:#{relationship_types_cypher}]-(category:Asset)")
|
347
387
|
|
348
388
|
[query,
|
349
|
-
|
389
|
+
:asset, [:category]]
|
350
390
|
else
|
351
391
|
[all(:asset),
|
352
392
|
:asset]
|
353
393
|
end
|
354
394
|
|
355
395
|
::GraphStarter::QueryAuthorizer.new(query, asset: GraphStarter.configuration.scope_filters[self.name.to_sym])
|
356
|
-
.authorized_query(
|
396
|
+
.authorized_query(var, sec_var, user)
|
357
397
|
.with('DISTINCT asset AS asset, level')
|
358
398
|
.break
|
359
399
|
.proxy_as(self, :asset)
|
@@ -11,8 +11,9 @@
|
|
11
11
|
|
12
12
|
.ui.divider
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
|
14
|
+
.ui.buttons
|
15
|
+
a.ui.labeled.icon.red.button.right.floated href="#{graph_starter.destroy_asset_path(id: asset)}"
|
16
|
+
i.delete.icon
|
17
|
+
| Delete
|
17
18
|
|
18
19
|
|
@@ -1,21 +1,22 @@
|
|
1
1
|
- as_cards = false if !defined?(as_cards)
|
2
2
|
|
3
3
|
- asset.class.authorized_associations.each do |name, association|
|
4
|
-
-
|
5
|
-
|
6
|
-
.
|
7
|
-
.
|
8
|
-
.
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
.
|
18
|
-
.
|
19
|
-
|
20
|
-
|
4
|
+
- if association.target_class.ancestors.include?(GraphStarter::Asset)
|
5
|
+
- result = asset.send(name)
|
6
|
+
- if result.present?
|
7
|
+
.item
|
8
|
+
.content
|
9
|
+
.ui.horizontal.divider = name.to_s.humanize
|
10
|
+
.description
|
11
|
+
- if as_cards
|
12
|
+
.ui.link.cards
|
13
|
+
- Array(result).each do |object|
|
14
|
+
= render partial: '/graph_starter/assets/card', locals: {asset: object}
|
15
|
+
- else
|
16
|
+
.ui.middle.aligned.big.divided.list
|
17
|
+
- Array(result).each do |object|
|
18
|
+
.item
|
19
|
+
.content
|
20
|
+
= render partial: '/graph_starter/assets/icon', locals: {asset: object}
|
21
|
+
= link_to object.title, asset_path(object)
|
21
22
|
|
File without changes
|
@@ -7,6 +7,11 @@
|
|
7
7
|
|
8
8
|
= file_field_tag 'image'
|
9
9
|
|
10
|
+
.field
|
11
|
+
label Summary
|
12
|
+
|
13
|
+
= f.text_area :summary
|
14
|
+
|
10
15
|
- @asset.class.authorized_properties_and_levels(current_user).each do |property, level|
|
11
16
|
.field
|
12
17
|
- editable_properties = GraphStarter.configuration.editable_properties[@asset.class.name.to_sym]
|
@@ -0,0 +1 @@
|
|
1
|
+
json.array! @assets
|
@@ -26,6 +26,7 @@ javascript:
|
|
26
26
|
div class="#{right_width} wide column" id="right-column"
|
27
27
|
- if app_user_is_admin?
|
28
28
|
= render partial: '/graph_starter/assets/admin_buttons', locals: {asset: @asset}
|
29
|
+
= render partial: '/graph_starter/assets/extra_admin_buttons', locals: {asset: @asset}
|
29
30
|
|
30
31
|
- if !asset_presenter.left_sidebar_exists?
|
31
32
|
.ui.items
|
@@ -1,25 +1,29 @@
|
|
1
1
|
@asset.class.authorized_properties(current_user).each do |property|
|
2
|
+
next if @asset.class.hidden_json_properties.include?(property.name.to_sym)
|
3
|
+
|
2
4
|
json.set! property.name, @asset.read_attribute(property.name)
|
3
5
|
end
|
4
6
|
|
7
|
+
@asset.class.json_methods.each do |method_name|
|
8
|
+
json.set! method_name, @asset.send(method_name)
|
9
|
+
end
|
10
|
+
|
5
11
|
json.summary @asset.summary
|
6
12
|
|
7
13
|
json.image_urls @asset.reload.image_array.map(&:source_url)
|
8
14
|
|
9
15
|
json.categories @asset.categories
|
10
16
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
json.set! name, value
|
23
|
-
end
|
17
|
+
@asset.class.authorized_associations.each do |name, association|
|
18
|
+
value = case association.type
|
19
|
+
when :has_many
|
20
|
+
@asset.send(name).to_a
|
21
|
+
when :has_one
|
22
|
+
@asset.send(name)
|
23
|
+
else
|
24
|
+
fail "Invalid association: #{association.inspect}"
|
25
|
+
end
|
26
|
+
|
27
|
+
json.set! name, value
|
24
28
|
end
|
25
29
|
|
@@ -5,7 +5,8 @@ doctype html
|
|
5
5
|
html
|
6
6
|
|
7
7
|
head
|
8
|
-
|
8
|
+
- app_name = Rails.application.class.to_s.split("::").first.tableize.singularize.humanize
|
9
|
+
title = @title ? "#{@title} - #{app_name}" : app_name
|
9
10
|
= stylesheet_link_tag 'graph_starter/application', media: 'all', 'data-turbolinks-track' => true
|
10
11
|
|
11
12
|
= render partial: 'layouts/graph_starter/twitter_meta_tags'
|
@@ -29,7 +30,7 @@ html
|
|
29
30
|
|
30
31
|
= render partial: 'layouts/graph_starter/menu'
|
31
32
|
|
32
|
-
#main
|
33
|
+
#main class="#{@no_ui_container ? '' : 'ui container'}"
|
33
34
|
- if notice.present?
|
34
35
|
p.ui.green.message
|
35
36
|
i.close.icon
|
data/config/routes.rb
CHANGED
@@ -12,23 +12,25 @@ module GraphStarter
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def authorized_pluck(variable, user)
|
15
|
-
authorized_query(variable, user).pluck(variable)
|
15
|
+
authorized_query(variable, [], user).pluck(variable)
|
16
16
|
end
|
17
17
|
|
18
|
-
def authorized_query(
|
19
|
-
|
18
|
+
def authorized_query(variable, secondary_variables, user)
|
19
|
+
result_query = query.with(variable, *secondary_variables)
|
20
20
|
|
21
|
-
result_query =
|
21
|
+
result_query = authorized_user_query(result_query, user, variable, secondary_variables)
|
22
22
|
|
23
|
-
|
23
|
+
# result_query.print_cypher
|
24
|
+
# puts result_query.pluck('*').inspect
|
25
|
+
|
26
|
+
list_variables = secondary_variables.map { |variable| "#{variable}_list" }
|
24
27
|
|
25
28
|
# Collapse 2D array of all possible levels into one column of levels
|
26
29
|
result_query
|
27
|
-
.
|
30
|
+
.where_not("'denied' IN level_collection")
|
28
31
|
.unwind(level: :level_collection).break
|
29
|
-
.with(
|
30
|
-
.with('
|
31
|
-
.with("CASE WHEN 'write' IN levels THEN 'write' ELSE 'read' END AS level", *variables)
|
32
|
+
.with('collect(level) AS levels', variable, *list_variables)
|
33
|
+
.with("CASE WHEN 'write' IN levels THEN 'write' ELSE 'read' END AS level", variable, *list_variables)
|
32
34
|
end
|
33
35
|
|
34
36
|
private
|
@@ -56,14 +58,17 @@ module GraphStarter
|
|
56
58
|
end
|
57
59
|
end
|
58
60
|
|
59
|
-
def authorized_user_query(query, user,
|
61
|
+
def authorized_user_query(query, user, variable, secondary_variables, user_variable = :user)
|
62
|
+
variables = [variable] + secondary_variables
|
60
63
|
collect_levels_string = variables.flat_map do |variable|
|
61
64
|
filter = scope_filter(variable)
|
62
65
|
|
63
66
|
filter_string = filter ? ' AND ' + filter.call(variable) : ''
|
64
|
-
|
67
|
+
secondary = secondary_variables.include?(variable)
|
68
|
+
# First lines gives write or read access based on properties of the asset and wether or not user is admin
|
69
|
+
["CASE WHEN (user.admin #{secondary ? nil : "OR #{variable}_created_rel IS NOT NULL"}) THEN 'write' WHEN (#{variable} IS NOT NULL AND (#{secondary ? '' : "#{variable}.private IS NULL OR"} #{variable}.private = false)) #{filter_string} THEN 'read' END",
|
65
70
|
"#{variable}_direct_access_rel.level",
|
66
|
-
"#{variable}_indirect_can_access_rel.level"]
|
71
|
+
(secondary ? nil : "#{variable}_indirect_can_access_rel.level")]
|
67
72
|
end.compact.join(', ')
|
68
73
|
|
69
74
|
result_query = variables.flat_map { |v| user_authorization_paths(v, user_variable) }
|
@@ -71,8 +76,8 @@ module GraphStarter
|
|
71
76
|
result.optional_match(clause).break
|
72
77
|
end.with('*')
|
73
78
|
|
74
|
-
|
75
|
-
|
79
|
+
list_variables = secondary_variables.map { |variable| "collect(#{variable}) AS #{variable}_list" }
|
80
|
+
result_query.with("FILTER(i IN REDUCE(a = [], sub_a IN collect([#{collect_levels_string}]) | a + sub_a) WHERE i IS NOT NULL) AS level_collection", variable, *list_variables)
|
76
81
|
end
|
77
82
|
|
78
83
|
def scope_filter(variable)
|
@@ -5,14 +5,16 @@ describe 'Asset authorization' do
|
|
5
5
|
|
6
6
|
let(:current_user) { nil }
|
7
7
|
let(:asset_attributes) { {} }
|
8
|
-
let(:asset) {
|
8
|
+
let!(:asset) { create(:person, asset_attributes) }
|
9
9
|
|
10
|
-
|
10
|
+
let(:category_attributes) { {} }
|
11
|
+
let!(:category) { create(:company, category_attributes) }
|
12
|
+
|
13
|
+
subject { Person.authorized_for(current_user).to_a }
|
11
14
|
|
12
15
|
it { should include(asset) }
|
13
16
|
|
14
|
-
|
15
|
-
let(:asset_attributes) { {private: true} }
|
17
|
+
let_context(asset_attributes: {private: true}) do
|
16
18
|
it { should_not include(asset) }
|
17
19
|
|
18
20
|
context 'user is logged in' do
|
@@ -21,8 +23,15 @@ describe 'Asset authorization' do
|
|
21
23
|
|
22
24
|
it { should_not include(asset) }
|
23
25
|
|
26
|
+
context 'current_user is the asset creator' do
|
27
|
+
before { asset.creators << current_user }
|
28
|
+
|
29
|
+
it { should include(asset) }
|
30
|
+
end
|
31
|
+
|
24
32
|
context 'current_user is admin' do
|
25
33
|
let(:current_user_attributes) { {admin: true} }
|
34
|
+
|
26
35
|
it { should include(asset) }
|
27
36
|
end
|
28
37
|
|
@@ -31,16 +40,134 @@ describe 'Asset authorization' do
|
|
31
40
|
it { should include(asset) }
|
32
41
|
end
|
33
42
|
|
34
|
-
context '
|
35
|
-
|
43
|
+
context 'asset has the category' do
|
44
|
+
before { asset.employer = category }
|
45
|
+
|
46
|
+
it { should_not include(asset) }
|
47
|
+
|
48
|
+
context 'current_user is admin' do
|
49
|
+
let(:current_user_attributes) { {admin: true} }
|
50
|
+
|
51
|
+
it { should include(asset) }
|
52
|
+
end
|
53
|
+
|
54
|
+
context 'current_user is the category creator' do
|
55
|
+
before { category.creators << current_user }
|
36
56
|
|
37
|
-
|
38
|
-
asset.employer = category
|
39
|
-
category.allowed_users = current_user
|
57
|
+
it { should_not include(asset) }
|
40
58
|
end
|
41
59
|
|
60
|
+
context 'current_user has access to the category' do
|
61
|
+
before { category.allowed_users = current_user }
|
62
|
+
|
63
|
+
it { should include(asset) }
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
let_context(asset_attributes: {private: nil}) do
|
70
|
+
it { should include(asset) }
|
71
|
+
|
72
|
+
context 'user is logged in' do
|
73
|
+
let(:current_user_attributes) { {} }
|
74
|
+
let(:current_user) { User.create(current_user_attributes) }
|
75
|
+
|
76
|
+
it { should include(asset) }
|
77
|
+
|
78
|
+
context 'current_user is the asset creator' do
|
79
|
+
before { asset.creators << current_user }
|
80
|
+
|
42
81
|
it { should include(asset) }
|
43
82
|
end
|
83
|
+
|
84
|
+
context 'current_user is admin' do
|
85
|
+
let(:current_user_attributes) { {admin: true} }
|
86
|
+
|
87
|
+
it { should include(asset) }
|
88
|
+
end
|
89
|
+
|
90
|
+
context 'user has access to asset' do
|
91
|
+
before { asset.allowed_users = current_user }
|
92
|
+
it { should include(asset) }
|
93
|
+
end
|
94
|
+
|
95
|
+
context 'asset has the category' do
|
96
|
+
before { asset.employer = category }
|
97
|
+
|
98
|
+
it { should include(asset) }
|
99
|
+
|
100
|
+
context 'current_user is admin' do
|
101
|
+
let(:current_user_attributes) { {admin: true} }
|
102
|
+
|
103
|
+
it { should include(asset) }
|
104
|
+
end
|
105
|
+
|
106
|
+
context 'current_user is the category creator' do
|
107
|
+
before { category.creators << current_user }
|
108
|
+
|
109
|
+
it { should include(asset) }
|
110
|
+
end
|
111
|
+
|
112
|
+
context 'current_user has access to the category' do
|
113
|
+
before { category.allowed_users = current_user }
|
114
|
+
|
115
|
+
it { should include(asset) }
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
let_context(asset_attributes: {private: false}) do
|
122
|
+
it { should include(asset) }
|
123
|
+
|
124
|
+
context 'user is logged in' do
|
125
|
+
let(:current_user_attributes) { {} }
|
126
|
+
let(:current_user) { User.create(current_user_attributes) }
|
127
|
+
|
128
|
+
it { should include(asset) }
|
129
|
+
|
130
|
+
context 'current_user is the asset creator' do
|
131
|
+
before { asset.creators << current_user }
|
132
|
+
|
133
|
+
it { should include(asset) }
|
134
|
+
end
|
135
|
+
|
136
|
+
context 'current_user is admin' do
|
137
|
+
let(:current_user_attributes) { {admin: true} }
|
138
|
+
|
139
|
+
it { should include(asset) }
|
140
|
+
end
|
141
|
+
|
142
|
+
context 'user has access to asset' do
|
143
|
+
before { asset.allowed_users = current_user }
|
144
|
+
it { should include(asset) }
|
145
|
+
end
|
146
|
+
|
147
|
+
context 'asset has the category' do
|
148
|
+
before { asset.employer = category }
|
149
|
+
|
150
|
+
it { should include(asset) }
|
151
|
+
|
152
|
+
context 'current_user is admin' do
|
153
|
+
let(:current_user_attributes) { {admin: true} }
|
154
|
+
|
155
|
+
it { should include(asset) }
|
156
|
+
end
|
157
|
+
|
158
|
+
context 'current_user is the category creator' do
|
159
|
+
before { category.creators << current_user }
|
160
|
+
|
161
|
+
it { should include(asset) }
|
162
|
+
end
|
163
|
+
|
164
|
+
context 'current_user has access to the category' do
|
165
|
+
before { category.allowed_users = current_user }
|
166
|
+
|
167
|
+
it { should include(asset) }
|
168
|
+
end
|
169
|
+
end
|
44
170
|
end
|
45
171
|
end
|
172
|
+
|
46
173
|
end
|
data/spec/asset_slug_spec.rb
CHANGED
@@ -1,35 +1,43 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
1
|
+
require './spec/rails_helper'
|
2
|
+
|
3
|
+
describe 'Asset slugs' do
|
4
|
+
class Foo < GraphStarter::Asset
|
5
|
+
property :title
|
6
|
+
|
7
|
+
name_property :title
|
8
|
+
end
|
9
|
+
|
10
|
+
before { clear_db }
|
11
|
+
|
12
|
+
describe '.unique_slug_from' do
|
13
|
+
subject { GraphStarter::Asset.unique_slug_from(string) }
|
14
|
+
|
15
|
+
context 'Without assets with slugs' do
|
16
|
+
let_context string: 'Test title' do
|
17
|
+
it { should eq 'test-title' }
|
18
|
+
end
|
19
|
+
|
20
|
+
let_context string: 'Gölcük, Turkey Number 2!' do
|
21
|
+
it { should eq 'golcuk-turkey-number-2' }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context 'With existing asset slug' do
|
26
|
+
before { Foo.create(title: 'Gölcük, Turkey', slug: 'golcuk-turkey') }
|
27
|
+
|
28
|
+
let_context string: 'Gölcük, Turkey!' do
|
29
|
+
it { should eq 'golcuk-turkey-2' }
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'With another existing asset slug' do
|
33
|
+
before { Foo.create(title: 'Gölcük, Turkey!', slug: 'golcuk-turkey-2') }
|
34
|
+
|
35
|
+
let_context string: 'Gölcük, Turkey!!' do
|
36
|
+
it { should eq 'golcuk-turkey-3' }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
35
43
|
|
data/spec/factories.rb
ADDED
data/spec/spec_helper.rb
CHANGED
@@ -8,11 +8,10 @@ module LetContextHelpers
|
|
8
8
|
# When a String is specified that becomes the context description
|
9
9
|
# If String isn't specified, Hash#inspect becomes the context description
|
10
10
|
def let_context(*args, &block)
|
11
|
-
classes = args.map(&:class)
|
12
11
|
context_string, hash =
|
13
|
-
case
|
12
|
+
case args.map(&:class)
|
14
13
|
when [String, Hash] then ["#{args[0]} #{args[1]}", args[1]]
|
15
|
-
when [Hash] then args
|
14
|
+
when [Hash] then [args[0].inspect, args[0]]
|
16
15
|
end
|
17
16
|
|
18
17
|
context(context_string) do
|
@@ -23,6 +22,7 @@ module LetContextHelpers
|
|
23
22
|
end
|
24
23
|
end
|
25
24
|
|
25
|
+
require 'factory_girl'
|
26
26
|
|
27
27
|
# This file was generated by the `rails generate rspec:install` command. Conventionally, all
|
28
28
|
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
@@ -68,6 +68,8 @@ RSpec.configure do |config|
|
|
68
68
|
|
69
69
|
config.extend LetContextHelpers
|
70
70
|
|
71
|
+
config.include FactoryGirl::Syntax::Methods
|
72
|
+
|
71
73
|
# The settings below are suggested to provide a good initial experience
|
72
74
|
# with RSpec, but feel free to customize to your heart's content.
|
73
75
|
=begin
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: graph_starter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.16.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brian Underwood
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-03-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -195,6 +195,7 @@ files:
|
|
195
195
|
- app/views/graph_starter/assets/_body.html.slim
|
196
196
|
- app/views/graph_starter/assets/_card.html.slim
|
197
197
|
- app/views/graph_starter/assets/_cards.html.slim
|
198
|
+
- app/views/graph_starter/assets/_extra_admin_buttons.html
|
198
199
|
- app/views/graph_starter/assets/_extra_card_content.html.slim
|
199
200
|
- app/views/graph_starter/assets/_form.html.slim
|
200
201
|
- app/views/graph_starter/assets/_icon.html.slim
|
@@ -205,6 +206,7 @@ files:
|
|
205
206
|
- app/views/graph_starter/assets/edit.html.slim
|
206
207
|
- app/views/graph_starter/assets/home.html.slim
|
207
208
|
- app/views/graph_starter/assets/index.html.slim
|
209
|
+
- app/views/graph_starter/assets/index.json.jbuilder
|
208
210
|
- app/views/graph_starter/assets/new.html.slim
|
209
211
|
- app/views/graph_starter/assets/show.html.slim
|
210
212
|
- app/views/graph_starter/assets/show.json.jbuilder
|
@@ -276,6 +278,7 @@ files:
|
|
276
278
|
- spec/dummy/public/422.html
|
277
279
|
- spec/dummy/public/500.html
|
278
280
|
- spec/dummy/public/favicon.ico
|
281
|
+
- spec/factories.rb
|
279
282
|
- spec/rails_helper.rb
|
280
283
|
- spec/spec_helper.rb
|
281
284
|
homepage: http://github.com/neo4j-examples/graph_starter
|
@@ -343,5 +346,6 @@ test_files:
|
|
343
346
|
- spec/dummy/public/favicon.ico
|
344
347
|
- spec/dummy/Rakefile
|
345
348
|
- spec/dummy/README.rdoc
|
349
|
+
- spec/factories.rb
|
346
350
|
- spec/rails_helper.rb
|
347
351
|
- spec/spec_helper.rb
|