graph_starter 0.15.6 → 0.16.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|