openstax_api 0.1.0 → 0.2.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/controllers/openstax/api/v1/api_controller.rb +9 -145
- data/app/controllers/openstax/api/v1/oauth_based_api_controller.rb +0 -2
- data/app/models/openstax/api/api_user.rb +5 -4
- data/lib/openstax/api/apipie.rb +51 -0
- data/lib/{openstax_api → openstax/api}/constraints.rb +0 -0
- data/lib/{openstax_api → openstax/api}/doorkeeper_extensions.rb +0 -0
- data/lib/{openstax_api → openstax/api}/engine.rb +0 -0
- data/lib/openstax/api/representable_schema_printer.rb +83 -0
- data/lib/openstax/api/roar.rb +132 -0
- data/lib/{openstax_api → openstax/api}/route_extensions.rb +1 -1
- data/lib/{openstax_api → openstax/api}/version.rb +1 -1
- data/lib/openstax_api.rb +20 -3
- data/spec/app/models/openstax/api/api_user_spec.rb +1 -1
- data/spec/app/representers/openstax/api/v1/representable_schema_printer_spec.rb +1 -1
- data/spec/dummy/app/models/dummy_user.rb +2 -0
- data/spec/dummy/app/representers/{user_representer.rb → dummy_user_representer.rb} +1 -1
- data/spec/dummy/config/initializers/openstax_api.rb +3 -0
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/migrate/{1_create_users.rb → 1_create_dummy_users.rb} +2 -2
- data/spec/dummy/db/schema.rb +7 -7
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/log/development.log +101 -597
- data/spec/dummy/log/test.log +62 -3216
- data/spec/lib/openstax/api/apipie.rb +11 -0
- data/spec/lib/{openstax_api → openstax/api}/constraints_spec.rb +0 -0
- data/spec/lib/{openstax_api → openstax/api}/doorkeeper_extensions_spec.rb +0 -0
- data/spec/lib/openstax/api/roar.rb +11 -0
- data/spec/lib/{openstax_api → openstax/api}/route_extensions_spec.rb +0 -0
- metadata +42 -20
- data/app/representers/openstax/api/v1/representable_schema_printer.rb +0 -85
- data/spec/dummy/app/models/user.rb +0 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0182a7fe7cdaf9adbe7a8719473cd4546c2f3bcb
|
4
|
+
data.tar.gz: be4936d879e58990235b59857c65892fc1711456
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2948208fe965b6e01bc5e67615b48058d52998fb40e4f2db620e4ee646f6559b19c0725439956f2107dc171b698b718b9252703a267a484ff324dcb7208949aa
|
7
|
+
data.tar.gz: 433bd360c6317cde8864ad9933877605387eaf3a8d92ee3267836abdce5e143663b2cb65acdec7f7ce735b0b4adb266ab5f97e04020c14496f9c7daaed6b9b3a
|
@@ -1,4 +1,7 @@
|
|
1
1
|
require 'roar-rails'
|
2
|
+
require 'exception_notification'
|
3
|
+
require 'openstax/api/roar'
|
4
|
+
require 'openstax/api/apipie'
|
2
5
|
|
3
6
|
module OpenStax
|
4
7
|
module Api
|
@@ -6,7 +9,9 @@ module OpenStax
|
|
6
9
|
|
7
10
|
class ApiController < ::ApplicationController
|
8
11
|
|
9
|
-
include Roar::Rails::ControllerAdditions
|
12
|
+
include ::Roar::Rails::ControllerAdditions
|
13
|
+
include OpenStax::Api::Roar
|
14
|
+
include OpenStax::Api::Apipie
|
10
15
|
|
11
16
|
fine_print_skip_signatures(:general_terms_of_use,
|
12
17
|
:privacy_policy) \
|
@@ -19,18 +24,7 @@ module OpenStax
|
|
19
24
|
respond_to :json
|
20
25
|
rescue_from Exception, :with => :rescue_from_exception
|
21
26
|
|
22
|
-
|
23
|
-
return if Rails.env.test?
|
24
|
-
raise IllegalArgument, "must supply a :url parameter" if !options[:url_base]
|
25
|
-
|
26
|
-
url_base = options[:url_base].is_a?(Symbol) ?
|
27
|
-
UrlGenerator.new.send(options[:url_base], protocol: 'https') :
|
28
|
-
options[:url_base].to_s
|
29
|
-
|
30
|
-
"#{url_base}/#{options[:url_end] || ''}"
|
31
|
-
end
|
32
|
-
|
33
|
-
# TODO doorkeeper users (or rather users who have doorkeeper
|
27
|
+
# TODO: doorkeeper users (or rather users who have doorkeeper
|
34
28
|
# applications) need to agree to API terms of use (need to have agreed
|
35
29
|
# to it at one time, can't require them to agree when terms change since
|
36
30
|
# their apps are doing the talking) -- this needs more thought
|
@@ -39,13 +33,11 @@ module OpenStax
|
|
39
33
|
@current_user ||= doorkeeper_token ?
|
40
34
|
User.find(doorkeeper_token.resource_owner_id) :
|
41
35
|
super
|
42
|
-
# TODO maybe freak out if current user is anonymous (require we know
|
36
|
+
# TODO: maybe freak out if current user is anonymous (require we know
|
43
37
|
# who person/app is so we can do things like throttling, API terms
|
44
38
|
# agreement, etc)
|
45
39
|
end
|
46
40
|
|
47
|
-
|
48
|
-
|
49
41
|
protected
|
50
42
|
|
51
43
|
def rescue_from_exception(exception)
|
@@ -66,147 +58,19 @@ module OpenStax
|
|
66
58
|
end
|
67
59
|
|
68
60
|
if notify
|
69
|
-
# TODO: Not yet in OSU
|
70
|
-
=begin
|
71
61
|
ExceptionNotifier.notify_exception(
|
72
62
|
exception,
|
73
63
|
env: request.env,
|
74
64
|
data: { message: "An exception occurred" }
|
75
65
|
)
|
76
|
-
|
66
|
+
|
77
67
|
Rails.logger.error("An exception occurred: #{exception.message}\n\n#{exception.backtrace.join("\n")}") \
|
78
68
|
end
|
79
69
|
|
80
70
|
head error
|
81
71
|
end
|
82
72
|
|
83
|
-
def self.json_schema(representer, options={})
|
84
|
-
RepresentableSchemaPrinter.json(representer, options)
|
85
|
-
end
|
86
|
-
|
87
|
-
# A hack at a conversion from a Representer to a series of Apipie declarations
|
88
|
-
# Can call it like any Apipie DSL method,
|
89
|
-
#
|
90
|
-
# example "blah"
|
91
|
-
# representer Api::V1::ExerciseRepresenter
|
92
|
-
# def update ...
|
93
|
-
#
|
94
|
-
def self.representer(representer)
|
95
|
-
representer.representable_attrs.each do |attr|
|
96
|
-
schema_info = attr.options[:schema_info] || {}
|
97
|
-
param attr.name, (attr.options[:type] || Object), required: schema_info[:required]
|
98
|
-
end
|
99
|
-
end
|
100
73
|
|
101
|
-
def get_representer(represent_with, model=nil)
|
102
|
-
return nil if represent_with.nil?
|
103
|
-
if represent_with.is_a? Proc
|
104
|
-
represent_with.call(model)
|
105
|
-
else
|
106
|
-
represent_with
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
|
-
def rest_get(model_klass, id, represent_with=nil)
|
111
|
-
@model = model_klass.find(id)
|
112
|
-
raise SecurityTransgression unless current_user.can_read?(@model)
|
113
|
-
respond_with @model, represent_with: get_representer(represent_with, @model)
|
114
|
-
end
|
115
|
-
|
116
|
-
def rest_update(model_klass, id, represent_with=nil)
|
117
|
-
@model = model_klass.find(id)
|
118
|
-
raise SecurityTransgression unless current_user.can_update?(@model)
|
119
|
-
consume!(@model, represent_with: get_representer(represent_with, @model))
|
120
|
-
|
121
|
-
if @model.save
|
122
|
-
head :no_content
|
123
|
-
else
|
124
|
-
render json: @model.errors, status: :unprocessable_entity
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
|
-
def rest_create(model_klass)
|
129
|
-
@model = model_klass.new()
|
130
|
-
|
131
|
-
# Unlike the implications of the representable README, "consume!" can
|
132
|
-
# actually make changes to the database. See http://goo.gl/WVLBqA.
|
133
|
-
# We do want to consume before checking the permissions so we can know
|
134
|
-
# what we're dealing with, but if user doesn't have permission we don't
|
135
|
-
# want to have changed the DB. Wrap in a transaction to protect ourselves.
|
136
|
-
|
137
|
-
model_klass.transaction do
|
138
|
-
consume!(@model)
|
139
|
-
raise SecurityTransgression unless current_user.can_create?(@model)
|
140
|
-
end
|
141
|
-
|
142
|
-
if @model.save
|
143
|
-
respond_with @model
|
144
|
-
else
|
145
|
-
render json: @model.errors, status: :unprocessable_entity
|
146
|
-
end
|
147
|
-
end
|
148
|
-
|
149
|
-
def rest_destroy(model_klass, id)
|
150
|
-
@model = model_klass.find(id)
|
151
|
-
raise SecurityTransgression unless current_user.can_destroy?(@model)
|
152
|
-
|
153
|
-
if @model.destroy
|
154
|
-
head :no_content
|
155
|
-
else
|
156
|
-
render json: @model.errors, status: :unprocessable_entity
|
157
|
-
end
|
158
|
-
end
|
159
|
-
|
160
|
-
def standard_sort(model_klass)
|
161
|
-
# take array of all IDs or hash of id => position,
|
162
|
-
# regardless build up an array of all IDs in the right order and pass those to sort
|
163
|
-
|
164
|
-
new_positions = params['newPositions']
|
165
|
-
return head :no_content if new_positions.length == 0
|
166
|
-
|
167
|
-
# Can't have duplicate positions or IDs
|
168
|
-
unique_ids = new_positions.collect{|np| np['id']}.uniq
|
169
|
-
unique_positions = new_positions.collect{|np| np['position']}.uniq
|
170
|
-
|
171
|
-
return head :bad_request if unique_ids.length != new_positions.length
|
172
|
-
return head :bad_request if unique_positions.length != new_positions.length
|
173
|
-
|
174
|
-
first = model_klass.where(:id => new_positions[0]['id']).first
|
175
|
-
|
176
|
-
return head :not_found if first.blank?
|
177
|
-
|
178
|
-
originalOrdered = first.me_and_peers.ordered.all
|
179
|
-
|
180
|
-
originalOrdered.each do |item|
|
181
|
-
raise SecurityTransgression unless item.send(:container_column) == originalOrdered[0].send(:container_column) \
|
182
|
-
if item.respond_to?(:container_column)
|
183
|
-
raise SecurityTransgression unless current_user.can_sort?(item)
|
184
|
-
end
|
185
|
-
|
186
|
-
originalOrderedIds = originalOrdered.collect{|sc| sc.id}
|
187
|
-
|
188
|
-
newOrderedIds = Array.new(originalOrderedIds.size)
|
189
|
-
|
190
|
-
new_positions.each do |newPosition|
|
191
|
-
id = newPosition['id'].to_i
|
192
|
-
newOrderedIds[newPosition['position']] = id
|
193
|
-
originalOrderedIds.delete(id)
|
194
|
-
end
|
195
|
-
|
196
|
-
ptr = 0
|
197
|
-
for oldId in originalOrderedIds
|
198
|
-
while !newOrderedIds[ptr].nil?; ptr += 1; end
|
199
|
-
newOrderedIds[ptr] = oldId
|
200
|
-
end
|
201
|
-
|
202
|
-
begin
|
203
|
-
model_klass.sort!(newOrderedIds)
|
204
|
-
rescue Exception => e
|
205
|
-
return head :internal_server_error
|
206
|
-
end
|
207
|
-
|
208
|
-
head :no_content
|
209
|
-
end
|
210
74
|
|
211
75
|
end
|
212
76
|
|
@@ -11,6 +11,8 @@
|
|
11
11
|
# This API class gives us a way to abstract out these cases and also
|
12
12
|
# gives us accessors to get the Application and User objects, if available.
|
13
13
|
|
14
|
+
require 'openstax_utilities'
|
15
|
+
|
14
16
|
module OpenStax
|
15
17
|
module Api
|
16
18
|
class ApiUser
|
@@ -25,10 +27,11 @@ module OpenStax
|
|
25
27
|
# procs that can get it for us. This could save us some queries.
|
26
28
|
|
27
29
|
if doorkeeper_token
|
30
|
+
user_class = OpenStax::Api.configuration.user_class_name.classify.constantize
|
28
31
|
@application_proc = lambda { doorkeeper_token.application }
|
29
32
|
@user_proc = lambda {
|
30
33
|
doorkeeper_token.resource_owner_id ?
|
31
|
-
|
34
|
+
user_class.find(doorkeeper_token.resource_owner_id) :
|
32
35
|
nil
|
33
36
|
}
|
34
37
|
else
|
@@ -37,9 +40,7 @@ module OpenStax
|
|
37
40
|
end
|
38
41
|
end
|
39
42
|
|
40
|
-
# Returns a Doorkeeper::Application or nil
|
41
|
-
# TODO should we have a NoApplication like NoUser (or maybe should
|
42
|
-
# NoUser just be replaced with nil)
|
43
|
+
# Returns a Doorkeeper::Application or nil
|
43
44
|
def application
|
44
45
|
@application ||= @application_proc.call
|
45
46
|
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# Copyright 2011-2014 Rice University. Licensed under the Affero General Public
|
2
|
+
# License version 3 or later. See the COPYRIGHT file for details.
|
3
|
+
|
4
|
+
require 'openstax/api/representable_schema_printer'
|
5
|
+
|
6
|
+
module OpenStax
|
7
|
+
module Api
|
8
|
+
|
9
|
+
module Apipie
|
10
|
+
|
11
|
+
def self.included(base)
|
12
|
+
base.send :extend, ClassMethods
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
|
17
|
+
def api_example(options={})
|
18
|
+
return if Rails.env.test?
|
19
|
+
raise IllegalArgument, "must supply a :url parameter" if !options[:url_base]
|
20
|
+
|
21
|
+
url_base = options[:url_base].is_a?(Symbol) ?
|
22
|
+
UrlGenerator.new.send(options[:url_base], protocol: 'https') :
|
23
|
+
options[:url_base].to_s
|
24
|
+
|
25
|
+
"#{url_base}/#{options[:url_end] || ''}"
|
26
|
+
end
|
27
|
+
|
28
|
+
def json_schema(representer, options={})
|
29
|
+
RepresentableSchemaPrinter.json(representer, options)
|
30
|
+
end
|
31
|
+
|
32
|
+
# A hack at a conversion from a Representer to a series of Apipie declarations
|
33
|
+
# Can call it like any Apipie DSL method,
|
34
|
+
#
|
35
|
+
# example "blah"
|
36
|
+
# representer Api::V1::ExerciseRepresenter
|
37
|
+
# def update ...
|
38
|
+
#
|
39
|
+
def representer(representer)
|
40
|
+
representer.representable_attrs.each do |attr|
|
41
|
+
schema_info = attr.options[:schema_info] || {}
|
42
|
+
param attr.name, (attr.options[:type] || Object), required: schema_info[:required]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
File without changes
|
File without changes
|
File without changes
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module OpenStax
|
2
|
+
module Api
|
3
|
+
class RepresentableSchemaPrinter
|
4
|
+
|
5
|
+
def self.json(representer, options={})
|
6
|
+
options[:include] ||= [:readable, :writeable]
|
7
|
+
options[:indent] ||= ' '
|
8
|
+
|
9
|
+
definitions = {}
|
10
|
+
|
11
|
+
schema = json_schema(representer, definitions, options)
|
12
|
+
schema[:definitions] = definitions
|
13
|
+
|
14
|
+
json_string = JSON.pretty_generate(schema, {indent: options[:indent]})
|
15
|
+
|
16
|
+
"\nSchema {##{SecureRandom.hex(4)} .schema}\n------\n" +
|
17
|
+
"<pre class='code'>\n#{json_string}\n</pre>\n"
|
18
|
+
end
|
19
|
+
|
20
|
+
protected
|
21
|
+
|
22
|
+
def self.json_schema(representer, definitions, options={})
|
23
|
+
schema = {
|
24
|
+
# id: schema_id(representer),
|
25
|
+
# title: schema_title(representer),
|
26
|
+
type: "object",
|
27
|
+
properties: {},
|
28
|
+
required: []
|
29
|
+
# :$schema => "http://json-schema.org/draft-04/schema#"
|
30
|
+
}
|
31
|
+
|
32
|
+
representer.representable_attrs.each do |attr|
|
33
|
+
schema_info = attr.options[:schema_info] || {}
|
34
|
+
|
35
|
+
schema[:required].push(attr.name) if schema_info[:required]
|
36
|
+
|
37
|
+
next unless [options[:include]].flatten.any?{|inc| attr.send(inc.to_s+"?") || schema_info[:required]}
|
38
|
+
|
39
|
+
attr_info = {}
|
40
|
+
|
41
|
+
if attr.options[:collection]
|
42
|
+
attr_info[:type] = "array"
|
43
|
+
else
|
44
|
+
attr_info[:type] = attr.options[:type].to_s.downcase if attr.options[:type]
|
45
|
+
end
|
46
|
+
|
47
|
+
schema_info.each do |key, value|
|
48
|
+
next if [:required].include?(key)
|
49
|
+
value = value.to_s.downcase if key == :type
|
50
|
+
attr_info[key] = value
|
51
|
+
end
|
52
|
+
|
53
|
+
decorator = attr.options[:decorator].try(:is_a?, Proc) ? nil : attr.options[:decorator]
|
54
|
+
|
55
|
+
if decorator
|
56
|
+
relative_schema_id(decorator).tap do |id|
|
57
|
+
attr_info[:$ref] = "#/definitions/#{id}"
|
58
|
+
definitions[id] ||= json_schema(decorator, definitions, options)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
schema[:properties][attr.name.to_sym] = attr_info
|
63
|
+
end
|
64
|
+
|
65
|
+
schema
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.schema_title(representer)
|
69
|
+
representer.name.gsub(/Representer/,'')
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.schema_id(representer)
|
73
|
+
"http://#{OpenStax::Api::Engine::MAIN_APP_NAME.to_s}.openstax.org/" +
|
74
|
+
"#{schema_title(representer).downcase.gsub(/::/,'/')}"
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.relative_schema_id(representer)
|
78
|
+
representer.name.gsub(/Representer/,'').downcase.gsub(/::/,'/')
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
# Copyright 2011-2014 Rice University. Licensed under the Affero General Public
|
2
|
+
# License version 3 or later. See the COPYRIGHT file for details.
|
3
|
+
|
4
|
+
module OpenStax
|
5
|
+
module Api
|
6
|
+
|
7
|
+
module Roar
|
8
|
+
|
9
|
+
def get_representer(represent_with, model=nil)
|
10
|
+
return nil if represent_with.nil?
|
11
|
+
if represent_with.is_a? Proc
|
12
|
+
represent_with.call(model)
|
13
|
+
else
|
14
|
+
represent_with
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def standard_read(model_klass, id, represent_with=nil)
|
19
|
+
@model = model_klass.find(id)
|
20
|
+
raise SecurityTransgression unless current_user.can_read?(@model)
|
21
|
+
respond_with @model, represent_with: get_representer(represent_with, @model)
|
22
|
+
end
|
23
|
+
|
24
|
+
def standard_update(model_klass, id, represent_with=nil)
|
25
|
+
@model = model_klass.find(id)
|
26
|
+
raise SecurityTransgression unless current_user.can_update?(@model)
|
27
|
+
consume!(@model, represent_with: get_representer(represent_with, @model))
|
28
|
+
|
29
|
+
if @model.save
|
30
|
+
head :no_content
|
31
|
+
else
|
32
|
+
render json: @model.errors, status: :unprocessable_entity
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def standard_create(model_klass, represent_with=nil)
|
37
|
+
standard_nested_create(model_klass, nil, nil, represent_with)
|
38
|
+
end
|
39
|
+
|
40
|
+
def standard_nested_create(model_klass, container_association=nil, container_id=nil, represent_with=nil)
|
41
|
+
@model = model_klass.new()
|
42
|
+
|
43
|
+
if container_association && container_id
|
44
|
+
foreign_key = model_klass.reflect_on_association(container_association).association_foreign_key
|
45
|
+
@model.send(foreign_key + '=', container_id)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Unlike the implications of the representable README, "consume!" can
|
49
|
+
# actually make changes to the database. See http://goo.gl/WVLBqA.
|
50
|
+
# We do want to consume before checking the permissions so we can know
|
51
|
+
# what we're dealing with, but if user doesn't have permission we don't
|
52
|
+
# want to have changed the DB. Wrap in a transaction to protect ourselves.
|
53
|
+
|
54
|
+
model_klass.transaction do
|
55
|
+
consume!(@model, represent_with: get_representer(represent_with, @model))
|
56
|
+
yield @model if block_given?
|
57
|
+
raise SecurityTransgression unless current_user.can_create?(@model)
|
58
|
+
end
|
59
|
+
|
60
|
+
if @model.save
|
61
|
+
respond_with @model, represent_with: get_representer(represent_with, @model), status: :created
|
62
|
+
else
|
63
|
+
render json: @model.errors, status: :unprocessable_entity
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def standard_destroy(model_klass, id)
|
68
|
+
@model = model_klass.find(id)
|
69
|
+
raise SecurityTransgression unless current_user.can_destroy?(@model)
|
70
|
+
|
71
|
+
if @model.destroy
|
72
|
+
head :no_content
|
73
|
+
else
|
74
|
+
render json: @model.errors, status: :unprocessable_entity
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def standard_sort(model_klass)
|
79
|
+
# Take array of all IDs or hash of id => position,
|
80
|
+
# Regardless, build up an array of all IDs in the right order and pass those to sort
|
81
|
+
|
82
|
+
new_positions = params['newPositions']
|
83
|
+
return head :no_content if new_positions.length == 0
|
84
|
+
|
85
|
+
# Can't have duplicate positions or IDs
|
86
|
+
unique_ids = new_positions.collect{|np| np['id']}.uniq
|
87
|
+
unique_positions = new_positions.collect{|np| np['position']}.uniq
|
88
|
+
|
89
|
+
return head :bad_request if unique_ids.length != new_positions.length
|
90
|
+
return head :bad_request if unique_positions.length != new_positions.length
|
91
|
+
|
92
|
+
first = model_klass.where(:id => new_positions[0]['id']).first
|
93
|
+
|
94
|
+
return head :not_found if first.blank?
|
95
|
+
|
96
|
+
originalOrdered = first.me_and_peers.ordered.all
|
97
|
+
|
98
|
+
originalOrdered.each do |item|
|
99
|
+
raise SecurityTransgression unless item.send(:container_column) == originalOrdered[0].send(:container_column) \
|
100
|
+
if item.respond_to?(:container_column)
|
101
|
+
raise SecurityTransgression unless current_user.can_sort?(item)
|
102
|
+
end
|
103
|
+
|
104
|
+
originalOrderedIds = originalOrdered.collect{|sc| sc.id}
|
105
|
+
|
106
|
+
newOrderedIds = Array.new(originalOrderedIds.size)
|
107
|
+
|
108
|
+
new_positions.each do |newPosition|
|
109
|
+
id = newPosition['id'].to_i
|
110
|
+
newOrderedIds[newPosition['position']] = id
|
111
|
+
originalOrderedIds.delete(id)
|
112
|
+
end
|
113
|
+
|
114
|
+
ptr = 0
|
115
|
+
for oldId in originalOrderedIds
|
116
|
+
while !newOrderedIds[ptr].nil?; ptr += 1; end
|
117
|
+
newOrderedIds[ptr] = oldId
|
118
|
+
end
|
119
|
+
|
120
|
+
begin
|
121
|
+
model_klass.sort!(newOrderedIds)
|
122
|
+
rescue Exception => e
|
123
|
+
return head :internal_server_error
|
124
|
+
end
|
125
|
+
|
126
|
+
head :no_content
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
end
|
data/lib/openstax_api.rb
CHANGED
@@ -1,8 +1,25 @@
|
|
1
|
-
require '
|
2
|
-
require '
|
3
|
-
require '
|
1
|
+
require 'openstax/api/engine'
|
2
|
+
require 'openstax/api/doorkeeper_extensions'
|
3
|
+
require 'openstax/api/route_extensions'
|
4
4
|
|
5
5
|
module OpenStax
|
6
6
|
module Api
|
7
|
+
|
8
|
+
def self.configure
|
9
|
+
yield configuration
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.configuration
|
13
|
+
@configuration ||= Configuration.new
|
14
|
+
end
|
15
|
+
|
16
|
+
class Configuration
|
17
|
+
attr_accessor :user_class_name
|
18
|
+
|
19
|
+
def initialize
|
20
|
+
@user_class_name = 'User'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
7
24
|
end
|
8
25
|
end
|
@@ -3,7 +3,7 @@ require 'spec_helper'
|
|
3
3
|
module OpenStax
|
4
4
|
module Api
|
5
5
|
describe ApiUser do
|
6
|
-
let(:user) {
|
6
|
+
let(:user) { DummyUser.create }
|
7
7
|
let(:application) { double('Doorkeeper::Application') }
|
8
8
|
let(:doorkeeper_token) { double('Doorkeeper::AccessToken') }
|
9
9
|
let(:non_doorkeeper_user_proc) { lambda { user } }
|
@@ -5,7 +5,7 @@ module OpenStax
|
|
5
5
|
module V1
|
6
6
|
describe RepresentableSchemaPrinter do
|
7
7
|
it 'must print model schemas' do
|
8
|
-
schema = RepresentableSchemaPrinter.json(
|
8
|
+
schema = RepresentableSchemaPrinter.json(DummyUserRepresenter)
|
9
9
|
expect(schema).to include('Schema')
|
10
10
|
expect(schema).to include('.schema')
|
11
11
|
expect(schema).to include('------')
|
Binary file
|
data/spec/dummy/db/schema.rb
CHANGED
@@ -13,6 +13,13 @@
|
|
13
13
|
|
14
14
|
ActiveRecord::Schema.define(:version => 1) do
|
15
15
|
|
16
|
+
create_table "dummy_users", :force => true do |t|
|
17
|
+
t.string "username"
|
18
|
+
t.string "password_hash"
|
19
|
+
t.datetime "created_at", :null => false
|
20
|
+
t.datetime "updated_at", :null => false
|
21
|
+
end
|
22
|
+
|
16
23
|
create_table "oauth_access_grants", :force => true do |t|
|
17
24
|
t.integer "resource_owner_id", :null => false
|
18
25
|
t.integer "application_id", :null => false
|
@@ -52,11 +59,4 @@ ActiveRecord::Schema.define(:version => 1) do
|
|
52
59
|
|
53
60
|
add_index "oauth_applications", ["uid"], :name => "index_oauth_applications_on_uid", :unique => true
|
54
61
|
|
55
|
-
create_table "users", :force => true do |t|
|
56
|
-
t.string "username"
|
57
|
-
t.string "password_hash"
|
58
|
-
t.datetime "created_at", :null => false
|
59
|
-
t.datetime "updated_at", :null => false
|
60
|
-
end
|
61
|
-
|
62
62
|
end
|
data/spec/dummy/db/test.sqlite3
CHANGED
Binary file
|