parse-stack-next 4.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. checksums.yaml +7 -0
  2. data/.bundle/config +2 -0
  3. data/.env.sample +112 -0
  4. data/.env.test +10 -0
  5. data/.github/workflows/ruby.yml +36 -0
  6. data/.gitignore +49 -0
  7. data/.ruby-version +1 -0
  8. data/.solargraph.yml +22 -0
  9. data/CHANGELOG.md +5816 -0
  10. data/Gemfile +30 -0
  11. data/Gemfile.lock +175 -0
  12. data/LICENSE.txt +23 -0
  13. data/Makefile +63 -0
  14. data/README.md +5655 -0
  15. data/Rakefile +573 -0
  16. data/bin/console +38 -0
  17. data/bin/parse-console +136 -0
  18. data/bin/server +17 -0
  19. data/bin/setup +7 -0
  20. data/config/parse-config.json +12 -0
  21. data/docs/TEST_SERVER.md +271 -0
  22. data/docs/_config.yml +1 -0
  23. data/docs/mcp_guide.md +3484 -0
  24. data/docs/mongodb_direct_guide.md +1348 -0
  25. data/docs/mongodb_index_optimization_guide.md +631 -0
  26. data/examples/transaction_example.rb +219 -0
  27. data/lib/parse/acl_scope.rb +728 -0
  28. data/lib/parse/agent/cancellation_token.rb +80 -0
  29. data/lib/parse/agent/constraint_translator.rb +480 -0
  30. data/lib/parse/agent/describe.rb +420 -0
  31. data/lib/parse/agent/errors.rb +133 -0
  32. data/lib/parse/agent/mcp_client.rb +557 -0
  33. data/lib/parse/agent/mcp_dispatcher.rb +1023 -0
  34. data/lib/parse/agent/mcp_rack_app.rb +1143 -0
  35. data/lib/parse/agent/mcp_server.rb +376 -0
  36. data/lib/parse/agent/metadata_audit.rb +259 -0
  37. data/lib/parse/agent/metadata_dsl.rb +733 -0
  38. data/lib/parse/agent/metadata_registry.rb +794 -0
  39. data/lib/parse/agent/pipeline_validator.rb +82 -0
  40. data/lib/parse/agent/prompts.rb +351 -0
  41. data/lib/parse/agent/rate_limiter.rb +158 -0
  42. data/lib/parse/agent/relation_graph.rb +162 -0
  43. data/lib/parse/agent/result_formatter.rb +453 -0
  44. data/lib/parse/agent/tools.rb +5489 -0
  45. data/lib/parse/agent.rb +3249 -0
  46. data/lib/parse/api/aggregate.rb +79 -0
  47. data/lib/parse/api/all.rb +26 -0
  48. data/lib/parse/api/analytics.rb +18 -0
  49. data/lib/parse/api/batch.rb +33 -0
  50. data/lib/parse/api/cloud_functions.rb +58 -0
  51. data/lib/parse/api/config.rb +125 -0
  52. data/lib/parse/api/files.rb +29 -0
  53. data/lib/parse/api/hooks.rb +117 -0
  54. data/lib/parse/api/objects.rb +146 -0
  55. data/lib/parse/api/path_segment.rb +75 -0
  56. data/lib/parse/api/push.rb +20 -0
  57. data/lib/parse/api/schema.rb +49 -0
  58. data/lib/parse/api/server.rb +50 -0
  59. data/lib/parse/api/sessions.rb +24 -0
  60. data/lib/parse/api/users.rb +250 -0
  61. data/lib/parse/atlas_search/index_manager.rb +353 -0
  62. data/lib/parse/atlas_search/result.rb +204 -0
  63. data/lib/parse/atlas_search/search_builder.rb +604 -0
  64. data/lib/parse/atlas_search/session.rb +253 -0
  65. data/lib/parse/atlas_search.rb +995 -0
  66. data/lib/parse/client/authentication.rb +97 -0
  67. data/lib/parse/client/batch.rb +234 -0
  68. data/lib/parse/client/body_builder.rb +240 -0
  69. data/lib/parse/client/caching.rb +203 -0
  70. data/lib/parse/client/logging.rb +293 -0
  71. data/lib/parse/client/profiling.rb +181 -0
  72. data/lib/parse/client/protocol.rb +91 -0
  73. data/lib/parse/client/request.rb +233 -0
  74. data/lib/parse/client/response.rb +208 -0
  75. data/lib/parse/client.rb +1104 -0
  76. data/lib/parse/clp_scope.rb +361 -0
  77. data/lib/parse/live_query/circuit_breaker.rb +256 -0
  78. data/lib/parse/live_query/client.rb +1001 -0
  79. data/lib/parse/live_query/configuration.rb +224 -0
  80. data/lib/parse/live_query/event.rb +115 -0
  81. data/lib/parse/live_query/event_queue.rb +272 -0
  82. data/lib/parse/live_query/health_monitor.rb +214 -0
  83. data/lib/parse/live_query/logging.rb +149 -0
  84. data/lib/parse/live_query/subscription.rb +294 -0
  85. data/lib/parse/live_query.rb +163 -0
  86. data/lib/parse/lookup_rewriter.rb +445 -0
  87. data/lib/parse/model/acl.rb +968 -0
  88. data/lib/parse/model/associations/belongs_to.rb +275 -0
  89. data/lib/parse/model/associations/collection_proxy.rb +435 -0
  90. data/lib/parse/model/associations/has_many.rb +597 -0
  91. data/lib/parse/model/associations/has_one.rb +158 -0
  92. data/lib/parse/model/associations/pointer_collection_proxy.rb +134 -0
  93. data/lib/parse/model/associations/relation_collection_proxy.rb +177 -0
  94. data/lib/parse/model/bytes.rb +62 -0
  95. data/lib/parse/model/classes/audience.rb +262 -0
  96. data/lib/parse/model/classes/installation.rb +363 -0
  97. data/lib/parse/model/classes/job_schedule.rb +153 -0
  98. data/lib/parse/model/classes/job_status.rb +264 -0
  99. data/lib/parse/model/classes/product.rb +75 -0
  100. data/lib/parse/model/classes/push_status.rb +263 -0
  101. data/lib/parse/model/classes/role.rb +751 -0
  102. data/lib/parse/model/classes/session.rb +201 -0
  103. data/lib/parse/model/classes/user.rb +943 -0
  104. data/lib/parse/model/clp.rb +544 -0
  105. data/lib/parse/model/core/actions.rb +1268 -0
  106. data/lib/parse/model/core/builder.rb +139 -0
  107. data/lib/parse/model/core/create_lock.rb +386 -0
  108. data/lib/parse/model/core/describe.rb +382 -0
  109. data/lib/parse/model/core/enhanced_change_tracking.rb +159 -0
  110. data/lib/parse/model/core/errors.rb +38 -0
  111. data/lib/parse/model/core/fetching.rb +566 -0
  112. data/lib/parse/model/core/field_guards.rb +220 -0
  113. data/lib/parse/model/core/indexing.rb +382 -0
  114. data/lib/parse/model/core/parse_reference.rb +407 -0
  115. data/lib/parse/model/core/properties.rb +809 -0
  116. data/lib/parse/model/core/querying.rb +491 -0
  117. data/lib/parse/model/core/schema.rb +202 -0
  118. data/lib/parse/model/core/search_indexing.rb +174 -0
  119. data/lib/parse/model/date.rb +88 -0
  120. data/lib/parse/model/email.rb +213 -0
  121. data/lib/parse/model/file.rb +527 -0
  122. data/lib/parse/model/geojson.rb +271 -0
  123. data/lib/parse/model/geopoint.rb +261 -0
  124. data/lib/parse/model/model.rb +260 -0
  125. data/lib/parse/model/object.rb +2068 -0
  126. data/lib/parse/model/phone.rb +520 -0
  127. data/lib/parse/model/pointer.rb +443 -0
  128. data/lib/parse/model/polygon.rb +406 -0
  129. data/lib/parse/model/push.rb +975 -0
  130. data/lib/parse/model/shortnames.rb +8 -0
  131. data/lib/parse/model/time_zone.rb +141 -0
  132. data/lib/parse/model/validations/uniqueness_validator.rb +97 -0
  133. data/lib/parse/model/validations.rb +96 -0
  134. data/lib/parse/mongodb.rb +2300 -0
  135. data/lib/parse/pipeline_security.rb +554 -0
  136. data/lib/parse/query/constraint.rb +198 -0
  137. data/lib/parse/query/constraints.rb +3279 -0
  138. data/lib/parse/query/cursor.rb +434 -0
  139. data/lib/parse/query/n_plus_one_detector.rb +445 -0
  140. data/lib/parse/query/operation.rb +104 -0
  141. data/lib/parse/query/ordering.rb +66 -0
  142. data/lib/parse/query.rb +7028 -0
  143. data/lib/parse/schema/index_migrator.rb +291 -0
  144. data/lib/parse/schema/search_index_migrator.rb +289 -0
  145. data/lib/parse/schema.rb +494 -0
  146. data/lib/parse/stack/generators/rails.rb +40 -0
  147. data/lib/parse/stack/generators/templates/model.erb +51 -0
  148. data/lib/parse/stack/generators/templates/model_installation.rb +4 -0
  149. data/lib/parse/stack/generators/templates/model_role.rb +4 -0
  150. data/lib/parse/stack/generators/templates/model_session.rb +4 -0
  151. data/lib/parse/stack/generators/templates/model_user.rb +11 -0
  152. data/lib/parse/stack/generators/templates/parse.rb +12 -0
  153. data/lib/parse/stack/generators/templates/webhooks.rb +10 -0
  154. data/lib/parse/stack/railtie.rb +18 -0
  155. data/lib/parse/stack/tasks.rb +563 -0
  156. data/lib/parse/stack/version.rb +11 -0
  157. data/lib/parse/stack.rb +455 -0
  158. data/lib/parse/two_factor_auth/user_extension.rb +449 -0
  159. data/lib/parse/two_factor_auth.rb +310 -0
  160. data/lib/parse/webhooks/payload.rb +360 -0
  161. data/lib/parse/webhooks/registration.rb +199 -0
  162. data/lib/parse/webhooks/replay_protection.rb +189 -0
  163. data/lib/parse/webhooks.rb +510 -0
  164. data/lib/parse-stack-next.rb +5 -0
  165. data/lib/parse-stack.rb +5 -0
  166. data/parse-stack-next.gemspec +82 -0
  167. data/parse-stack.png +0 -0
  168. data/scripts/debug-ips.js +35 -0
  169. data/scripts/docker/Dockerfile.parse +13 -0
  170. data/scripts/docker/atlas-init.js +284 -0
  171. data/scripts/docker/docker-compose.atlas.yml +76 -0
  172. data/scripts/docker/docker-compose.test.yml +106 -0
  173. data/scripts/docker/mongo-init.js +21 -0
  174. data/scripts/eval_mcp_with_lm_studio.rb +274 -0
  175. data/scripts/start-parse.sh +90 -0
  176. data/scripts/start_mcp_server.rb +78 -0
  177. data/scripts/test_server_connection.rb +82 -0
  178. metadata +377 -0
@@ -0,0 +1,79 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
4
+ require "active_support"
5
+ require "active_support/core_ext"
6
+ require_relative "./objects"
7
+
8
+ module Parse
9
+ module API
10
+ # REST API methods for fetching CRUD operations on Parse objects.
11
+ module Aggregate
12
+ # The class prefix for fetching objects.
13
+ # @!visibility private
14
+ PATH_PREFIX = "aggregate/"
15
+
16
+ # @!visibility private
17
+ PREFIX_MAP = Parse::API::Objects::PREFIX_MAP
18
+
19
+ # @!visibility private
20
+ def self.included(base)
21
+ base.extend(ClassMethods)
22
+ end
23
+
24
+ # Class methods to be applied to {Parse::Client}
25
+ module ClassMethods
26
+ # Get the aggregate API path for this class.
27
+ #
28
+ # +className+ is validated to prevent path-smuggling — aggregate
29
+ # endpoints are master-key-only on Parse Server, so any traversal
30
+ # here pivots a master-key request to a different endpoint.
31
+ #
32
+ # @param className [String] the name of the Parse collection.
33
+ # @return [String] the API uri path
34
+ # @raise [ArgumentError] if className violates the identifier pattern.
35
+ def aggregate_uri_path(className)
36
+ if className.is_a?(Parse::Pointer)
37
+ className = className.parse_class
38
+ end
39
+ className = Parse::API::PathSegment.identifier!(className, kind: "className")
40
+ "#{PATH_PREFIX}#{className}"
41
+ end
42
+ end
43
+
44
+ # Get the API path for this class.
45
+ # @param className [String] the name of the Parse collection.
46
+ # @return [String] the API uri path
47
+ def aggregate_uri_path(className)
48
+ self.class.aggregate_uri_path(className)
49
+ end
50
+
51
+ # Aggregate a set of matching objects for a query.
52
+ # @param className [String] the name of the Parse collection.
53
+ # @param query [Hash] The set of query constraints.
54
+ # @param opts [Hash] additional options to pass to the {Parse::Client} request.
55
+ # @param headers [Hash] additional HTTP headers to send with the request.
56
+ # @return [Parse::Response]
57
+ # @see Parse::Query
58
+ def aggregate_objects(className, query = {}, headers: {}, **opts)
59
+ response = request :get, aggregate_uri_path(className), query: query, headers: headers, opts: opts
60
+ response.parse_class = className if response.present?
61
+ response
62
+ end
63
+
64
+ # Execute a MongoDB-style aggregation pipeline on a Parse collection.
65
+ # @param className [String] the name of the Parse collection.
66
+ # @param pipeline [Array] the MongoDB aggregation pipeline stages.
67
+ # @param opts [Hash] additional options to pass to the {Parse::Client} request.
68
+ # @param headers [Hash] additional HTTP headers to send with the request.
69
+ # @return [Parse::Response]
70
+ # @see Parse::Query
71
+ def aggregate_pipeline(className, pipeline = [], headers: {}, **opts)
72
+ query = { pipeline: pipeline.to_json }
73
+ response = request :get, aggregate_uri_path(className), query: query, headers: headers, opts: opts
74
+ response.parse_class = className if response.present?
75
+ response
76
+ end
77
+ end #Aggregate
78
+ end #API
79
+ end
@@ -0,0 +1,26 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
4
+ # Note: Do not require "../client" here - this file is loaded from client.rb
5
+ # and adding that require would create a circular dependency.
6
+ require_relative "path_segment"
7
+ require_relative "analytics"
8
+ require_relative "aggregate"
9
+ require_relative "batch"
10
+ require_relative "config"
11
+ require_relative "files"
12
+ require_relative "cloud_functions"
13
+ require_relative "hooks"
14
+ require_relative "objects"
15
+ require_relative "push"
16
+ require_relative "schema"
17
+ require_relative "server"
18
+ require_relative "sessions"
19
+ require_relative "users"
20
+
21
+ module Parse
22
+ # The module containing most of the REST API requests supported by Parse Server.
23
+ # Defines all the Parse REST API endpoints.
24
+ module API
25
+ end
26
+ end
@@ -0,0 +1,18 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
4
+ module Parse
5
+ module API
6
+ # Defines the Analytics interface for the Parse REST API
7
+ module Analytics
8
+
9
+ # Send analytics data.
10
+ # @param event_name [String] the name of the event.
11
+ # @param metrics [Hash] the metrics to attach to event.
12
+ # @see http://docs.parseplatform.org/rest/guide/#analytics Parse Analytics
13
+ def send_analytics(event_name, metrics = {})
14
+ request :post, "events/#{event_name}", body: metrics
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,33 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
4
+ require "active_support"
5
+ require "active_support/core_ext"
6
+
7
+ module Parse
8
+ module API
9
+ # Defines the Batch interface for the Parse REST API
10
+ # @see Parse::BatchOperation
11
+ # @see Array.destroy
12
+ # @see Array.save
13
+ module Batch
14
+ # @note You cannot use batch_requests with {Parse::User} instances that need to
15
+ # be created.
16
+ # @overload batch_request(requests)
17
+ # Perform a set of {Parse::Request} instances as a batch operation.
18
+ # @param requests [Array<Parse::Request>] the set of requests to batch.
19
+ # @overload batch_request(operation)
20
+ # Submit a batch operation.
21
+ # @param operation [Parse::BatchOperation] the batch operation.
22
+ # @return [Array<Parse::Response>] if successful, a set of responses for each operation in the batch.
23
+ # @return [Parse::Response] if an error occurred, the error response.
24
+ def batch_request(batch_operations)
25
+ unless batch_operations.is_a?(Parse::BatchOperation)
26
+ batch_operations = Parse::BatchOperation.new batch_operations
27
+ end
28
+ response = request(:post, "batch", body: batch_operations.as_json)
29
+ response.success? && response.batch? ? response.batch_responses : response
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,58 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
4
+ module Parse
5
+ module API
6
+ # Defines the CloudCode interface for the Parse REST API
7
+ module CloudFunctions
8
+
9
+ # Call a cloud function.
10
+ # @param name [String] the name of the cloud function.
11
+ # @param body [Hash] the parameters to forward to the function.
12
+ # @param opts [Hash] additional options for the request.
13
+ # @option opts [String] :session_token The session token for authenticated requests.
14
+ # @option opts [String] :master_key Whether to use the master key for this request.
15
+ # @return [Parse::Response]
16
+ def call_function(name, body = {}, opts: {})
17
+ safe = Parse::API::PathSegment.identifier!(name, kind: "function name")
18
+ request :post, "functions/#{safe}", body: body, opts: opts
19
+ end
20
+
21
+ # Trigger a job.
22
+ # @param name [String] the name of the job to trigger.
23
+ # @param body [Hash] the parameters to forward to the job.
24
+ # @param opts [Hash] additional options for the request.
25
+ # @option opts [String] :session_token The session token for authenticated requests.
26
+ # @option opts [String] :master_key Whether to use the master key for this request.
27
+ # @return [Parse::Response]
28
+ def trigger_job(name, body = {}, opts: {})
29
+ safe = Parse::API::PathSegment.identifier!(name, kind: "job name")
30
+ request :post, "jobs/#{safe}", body: body, opts: opts
31
+ end
32
+
33
+ # Call a cloud function with a specific session token.
34
+ # This is a convenience method that ensures the session token is properly passed.
35
+ # @param name [String] the name of the cloud function.
36
+ # @param body [Hash] the parameters to forward to the function.
37
+ # @param session_token [String] the session token for authenticated requests.
38
+ # @return [Parse::Response]
39
+ def call_function_with_session(name, body = {}, session_token)
40
+ opts = {}
41
+ opts[:session_token] = session_token if session_token.present?
42
+ call_function(name, body, opts: opts)
43
+ end
44
+
45
+ # Trigger a job with a specific session token.
46
+ # This is a convenience method that ensures the session token is properly passed.
47
+ # @param name [String] the name of the job to trigger.
48
+ # @param body [Hash] the parameters to forward to the job.
49
+ # @param session_token [String] the session token for authenticated requests.
50
+ # @return [Parse::Response]
51
+ def trigger_job_with_session(name, body = {}, session_token)
52
+ opts = {}
53
+ opts[:session_token] = session_token if session_token.present?
54
+ trigger_job(name, body, opts: opts)
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,125 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
4
+ module Parse
5
+ module API
6
+ # Defines the Config interface for the Parse REST API
7
+ module Config
8
+
9
+ # @!attribute config
10
+ # @return [Hash] the cached config hash for the client.
11
+ attr_writer :config
12
+
13
+ # @!attribute master_key_only
14
+ # @return [Hash] the cached masterKeyOnly flag map for the client.
15
+ attr_writer :master_key_only
16
+
17
+ # @!visibility private
18
+ CONFIG_PATH = "config"
19
+
20
+ # @return [Hash] force fetch the application configuration hash.
21
+ def config!
22
+ @config = nil
23
+ @master_key_only = nil
24
+ self.config
25
+ end
26
+
27
+ # Return the configuration hash for the configured application for this client.
28
+ # This method caches the configuration after the first time it is fetched.
29
+ # The accompanying `masterKeyOnly` map (if returned by the server) is cached
30
+ # alongside it and exposed via {#master_key_only}.
31
+ # @return [Hash] force fetch the application configuration hash.
32
+ def config
33
+ if @config.nil?
34
+ response = request :get, CONFIG_PATH
35
+ unless response.error?
36
+ result = response.result || {}
37
+ @config = result["params"] || {}
38
+ @master_key_only = result["masterKeyOnly"] || {}
39
+ end
40
+ end
41
+ @config
42
+ end
43
+
44
+ # Return every config entry zipped with its masterKeyOnly trait.
45
+ #
46
+ # Pass `master: true` to include keys whose `masterKeyOnly` flag is `true`;
47
+ # the default `master: false` filters those entries out, matching what a
48
+ # non-master-key client would actually observe. Each entry has the shape
49
+ # `{ value: ..., master_key_only: Boolean }`. This is a client-side
50
+ # filter on the already-cached config — it does NOT re-request the
51
+ # config. When the connection is not authenticated with the master key,
52
+ # Parse Server has already stripped master-key-only entries before the
53
+ # response reaches the cache, so `master: true` has nothing extra to
54
+ # surface in that case.
55
+ #
56
+ # @param master [Boolean] when true, include master-key-only entries.
57
+ # @return [Hash{String=>Hash}] map of config key to `{value:, master_key_only:}`.
58
+ def config_entries(master: false)
59
+ config if @config.nil?
60
+ return {} if @config.nil?
61
+ flags = @master_key_only || {}
62
+ @config.each_with_object({}) do |(key, value), out|
63
+ is_master_only = flags[key] == true
64
+ next if is_master_only && !master
65
+ out[key] = { value: value, master_key_only: is_master_only }
66
+ end
67
+ end
68
+
69
+ # Return the masterKeyOnly flag map for the application configuration.
70
+ # Keys map to `true` when the corresponding config param is only readable
71
+ # by master-key clients. Lazily triggers a config fetch on first access.
72
+ # @return [Hash{String=>Boolean}] the cached masterKeyOnly map, or an
73
+ # empty hash if the server did not return one (e.g. non-master-key reads).
74
+ def master_key_only
75
+ config if @master_key_only.nil?
76
+ @master_key_only || {}
77
+ end
78
+
79
+ # Update the application configuration.
80
+ #
81
+ # Pass `master_key_only:` to additionally mark (or unmark) which keys are
82
+ # only readable by master-key clients. Parse Server merges this map into
83
+ # the existing flags; unspecified keys keep their current flag. Note that
84
+ # Parse Server rejects masterKeyOnly entries for keys that do not exist
85
+ # in `params` (either in this PUT body or already stored).
86
+ #
87
+ # @param params [Hash] the hash of key value pairs.
88
+ # @param master_key_only [Hash{String=>Boolean}, nil] optional flag map
89
+ # to merge into the server-side masterKeyOnly settings.
90
+ # @return [Boolean] true if the configuration was successfully updated.
91
+ def update_config(params, master_key_only: nil)
92
+ body = { params: params }
93
+ unless master_key_only.nil?
94
+ body[:masterKeyOnly] = master_key_only
95
+ # Parse Server (9.x) rejects PUT /parse/config when masterKeyOnly
96
+ # references a key that is not present in the request's params
97
+ # payload, EVEN IF the key already exists in stored config. The
98
+ # SDK absorbs that constraint by backfilling any flag-only keys
99
+ # from the cached @config so flag-only updates round-trip cleanly.
100
+ # Without this, `update_config({}, master_key_only: {foo: false})`
101
+ # would always fail with a server-side 400 even after foo was
102
+ # previously persisted.
103
+ if @config.is_a?(Hash)
104
+ master_key_only.each_key do |k|
105
+ ks = k.to_s
106
+ next if body[:params].key?(ks) || body[:params].key?(k)
107
+ cached = @config[ks]
108
+ body[:params][ks] = cached unless cached.nil?
109
+ end
110
+ end
111
+ end
112
+ response = request :put, CONFIG_PATH, body: body
113
+ return false if response.error?
114
+ result = response.result["result"]
115
+ if result
116
+ @config.merge!(params) if @config.present?
117
+ if master_key_only.is_a?(Hash) && @master_key_only.is_a?(Hash)
118
+ @master_key_only.merge!(master_key_only.transform_keys(&:to_s))
119
+ end
120
+ end
121
+ result
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,29 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
4
+ require "active_support"
5
+ require "active_support/core_ext"
6
+
7
+ module Parse
8
+ module API
9
+ # Defines the Parse Files interface for the Parse REST API
10
+ module Files
11
+ # @!visibility private
12
+ FILES_PATH = "files"
13
+
14
+ # Upload and create a Parse file.
15
+ # @param fileName [String] the basename of the file.
16
+ # @param data [Hash] the data related to this file.
17
+ # @param content_type [String] the mime-type of the file.
18
+ # @return [Parse::Response]
19
+ def create_file(fileName, data = {}, content_type = nil)
20
+ safe = Parse::API::PathSegment.file!(fileName, kind: "file name")
21
+ headers = {}
22
+ headers.merge!({ Parse::Protocol::CONTENT_TYPE => content_type.to_s }) if content_type.present?
23
+ response = request :post, "#{FILES_PATH}/#{safe}", body: data, headers: headers
24
+ response.parse_class = Parse::Model::TYPE_FILE
25
+ response
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,117 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
4
+ module Parse
5
+ module API
6
+ # Defines the Parse webhooks interface for the Parse REST API
7
+ module Hooks
8
+ # @!visibility private
9
+ HOOKS_PREFIX = "hooks/"
10
+ # The allowed set of Parse triggers.
11
+ TRIGGER_NAMES = [:afterCreate, :afterDelete, :afterFind, :afterSave, :beforeDelete, :beforeFind, :beforeSave].freeze
12
+ # @!visibility private
13
+ TRIGGER_NAMES_LOCAL = [:after_create, :after_delete, :after_find, :after_save, :before_delete, :before_find, :before_save].freeze
14
+ # @!visibility private
15
+ def _verify_trigger(triggerName)
16
+ triggerName = triggerName.to_s.camelize(:lower).to_sym
17
+ raise ArgumentError, "Invalid trigger name #{triggerName}" unless TRIGGER_NAMES.include?(triggerName)
18
+ triggerName
19
+ end
20
+
21
+ # Fetch all defined cloud code functions.
22
+ # @return [Parse::Response]
23
+ def functions
24
+ opts = { cache: false }
25
+ request :get, "#{HOOKS_PREFIX}functions", opts: opts
26
+ end
27
+
28
+ # Fetch information about a specific registered cloud function.
29
+ # @param functionName [String] the name of the cloud code function.
30
+ # @return [Parse::Response]
31
+ def fetch_function(functionName)
32
+ safe = Parse::API::PathSegment.identifier!(functionName, kind: "function name")
33
+ request :get, "#{HOOKS_PREFIX}functions/#{safe}"
34
+ end
35
+
36
+ # Register a cloud code webhook function pointing to a endpoint url.
37
+ # @param functionName [String] the name of the cloud code function.
38
+ # @param url [String] the url endpoint for this cloud code function.
39
+ # @return [Parse::Response]
40
+ def create_function(functionName, url)
41
+ request :post, "#{HOOKS_PREFIX}functions", body: { functionName: functionName, url: url }
42
+ end
43
+
44
+ # Updated the endpoint url for a registered cloud code webhook function.
45
+ # @param functionName [String] the name of the cloud code function.
46
+ # @param url [String] the new url endpoint for this cloud code function.
47
+ # @return [Parse::Response]
48
+ def update_function(functionName, url)
49
+ # If you add _method => "PUT" to the JSON body,
50
+ # and send it as a POST request and parse will accept it as a PUT.
51
+ safe = Parse::API::PathSegment.identifier!(functionName, kind: "function name")
52
+ request :put, "#{HOOKS_PREFIX}functions/#{safe}", body: { url: url }
53
+ end
54
+
55
+ # Remove a registered cloud code webhook function.
56
+ # @param functionName [String] the name of the cloud code function.
57
+ # @return [Parse::Response]
58
+ def delete_function(functionName)
59
+ safe = Parse::API::PathSegment.identifier!(functionName, kind: "function name")
60
+ request :put, "#{HOOKS_PREFIX}functions/#{safe}", body: { __op: "Delete" }
61
+ end
62
+
63
+ # Get the set of registered triggers.
64
+ # @return [Parse::Response]
65
+ def triggers
66
+ opts = { cache: false }
67
+ request :get, "#{HOOKS_PREFIX}triggers", opts: opts
68
+ end
69
+
70
+ # Fetch information about a registered webhook trigger.
71
+ # @param triggerName [String] the name of the trigger. (ex. beforeSave, afterSave)
72
+ # @param className [String] the name of the Parse collection for the trigger.
73
+ # @return [Parse::Response]
74
+ # @see TRIGGER_NAMES
75
+ def fetch_trigger(triggerName, className)
76
+ triggerName = _verify_trigger(triggerName)
77
+ safe_class = Parse::API::PathSegment.identifier!(className, kind: "class name")
78
+ request :get, "#{HOOKS_PREFIX}triggers/#{safe_class}/#{triggerName}"
79
+ end
80
+
81
+ # Register a new cloud code webhook trigger with an endpoint url.
82
+ # @param triggerName [String] the name of the trigger. (ex. beforeSave, afterSave)
83
+ # @param className [String] the name of the Parse collection for the trigger.
84
+ # @param url [String] the url endpoint for this webhook trigger.
85
+ # @return [Parse::Response]
86
+ # @see Parse::API::Hooks::TRIGGER_NAMES
87
+ def create_trigger(triggerName, className, url)
88
+ triggerName = _verify_trigger(triggerName)
89
+ body = { className: className, triggerName: triggerName, url: url }
90
+ request :post, "#{HOOKS_PREFIX}triggers", body: body
91
+ end
92
+
93
+ # Updated the registered endpoint for this cloud code webhook trigger.
94
+ # @param triggerName [String] the name of the trigger. (ex. beforeSave, afterSave)
95
+ # @param className [String] the name of the Parse collection for the trigger.
96
+ # @param url [String] the new url endpoint for this webhook trigger.
97
+ # @return [Parse::Response]
98
+ # @see Parse::API::Hooks::TRIGGER_NAMES
99
+ def update_trigger(triggerName, className, url)
100
+ triggerName = _verify_trigger(triggerName)
101
+ safe_class = Parse::API::PathSegment.identifier!(className, kind: "class name")
102
+ request :put, "#{HOOKS_PREFIX}triggers/#{safe_class}/#{triggerName}", body: { url: url }
103
+ end
104
+
105
+ # Remove a registered cloud code webhook trigger.
106
+ # @param triggerName [String] the name of the trigger. (ex. beforeSave, afterSave)
107
+ # @param className [String] the name of the Parse collection for the trigger.
108
+ # @return [Parse::Response]
109
+ # @see Parse::API::Hooks::TRIGGER_NAMES
110
+ def delete_trigger(triggerName, className)
111
+ triggerName = _verify_trigger(triggerName)
112
+ safe_class = Parse::API::PathSegment.identifier!(className, kind: "class name")
113
+ request :put, "#{HOOKS_PREFIX}triggers/#{safe_class}/#{triggerName}", body: { __op: "Delete" }
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,146 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
4
+ require "active_support"
5
+ require "active_support/core_ext"
6
+ require_relative "path_segment"
7
+
8
+ module Parse
9
+ module API
10
+ # REST API methods for fetching CRUD operations on Parse objects.
11
+ module Objects
12
+ # The class prefix for fetching objects.
13
+ # @!visibility private
14
+ CLASS_PATH_PREFIX = "classes/"
15
+
16
+ # @!visibility private
17
+ PREFIX_MAP = { installation: "installations", _installation: "installations",
18
+ user: "users", _user: "users",
19
+ role: "roles", _role: "roles",
20
+ session: "sessions", _session: "sessions" }.freeze
21
+
22
+ # @!visibility private
23
+ # Parse Server objectId format: 1-40 characters of alphanumerics.
24
+ # The default custom objectId generator in Parse Server uses a
25
+ # 10-char alphanumeric string; we permit up to 40 to cover apps that
26
+ # have configured a longer length but still refuse anything outside
27
+ # this character class.
28
+ OBJECT_ID_PATTERN = /\A[A-Za-z0-9]{1,40}\z/.freeze
29
+
30
+ # @!visibility private
31
+ def self.included(base)
32
+ base.extend(ClassMethods)
33
+ end
34
+
35
+ # Class methods to be applied to {Parse::Client}
36
+ module ClassMethods
37
+ # Get the API path for this class.
38
+ #
39
+ # Both +className+ and +id+ are validated to prevent path-smuggling
40
+ # attacks where an attacker-controlled string traverses to a
41
+ # different REST endpoint (e.g. +"../sessions/me"+) with whatever
42
+ # auth the outer request carries — typically the master key.
43
+ #
44
+ # @param className [String] the name of the Parse collection.
45
+ # @param id [String, nil] optional objectId to add at the end of the path.
46
+ # @return [String] the API uri path
47
+ # @raise [ArgumentError] if className or id violates the strict
48
+ # identifier / objectId patterns.
49
+ def uri_path(className, id = nil)
50
+ if className.is_a?(Parse::Pointer)
51
+ id = className.id
52
+ className = className.parse_class
53
+ end
54
+ className = Parse::API::PathSegment.identifier!(className, kind: "className")
55
+ if id
56
+ id_str = id.to_s
57
+ unless OBJECT_ID_PATTERN.match?(id_str)
58
+ raise ArgumentError,
59
+ "objectId #{id_str.inspect} contains characters not " \
60
+ "allowed in a Parse objectId. Must match " \
61
+ "/\\A[A-Za-z0-9]{1,40}\\z/."
62
+ end
63
+ id = id_str
64
+ end
65
+ uri = "#{CLASS_PATH_PREFIX}#{className}"
66
+ class_prefix = className.downcase.to_sym
67
+ if PREFIX_MAP.has_key?(class_prefix)
68
+ uri = PREFIX_MAP[class_prefix]
69
+ end
70
+ id.present? ? "#{uri}/#{id}" : "#{uri}/"
71
+ end
72
+ end
73
+
74
+ # Get the API path for this class.
75
+ # @param className [String] the name of the Parse collection.
76
+ # @param id [String] optional objectId to add at the end of the path.
77
+ # @return [String] the API uri path
78
+ def uri_path(className, id = nil)
79
+ self.class.uri_path(className, id)
80
+ end
81
+
82
+ # Create an object in a collection.
83
+ # @param className [String] the name of the Parse collection.
84
+ # @param body [Hash] the body of the request.
85
+ # @param opts [Hash] additional options to pass to the {Parse::Client} request.
86
+ # @param headers [Hash] additional HTTP headers to send with the request.
87
+ # @return [Parse::Response]
88
+ def create_object(className, body = {}, headers: {}, **opts)
89
+ response = request :post, uri_path(className), body: body, headers: headers, opts: opts
90
+ response.parse_class = className if response.present?
91
+ response
92
+ end
93
+
94
+ # Delete an object in a collection.
95
+ # @param className [String] the name of the Parse collection.
96
+ # @param id [String] The objectId of the record in the collection.
97
+ # @param opts [Hash] additional options to pass to the {Parse::Client} request.
98
+ # @param headers [Hash] additional HTTP headers to send with the request.
99
+ # @return [Parse::Response]
100
+ def delete_object(className, id, headers: {}, **opts)
101
+ response = request :delete, uri_path(className, id), headers: headers, opts: opts
102
+ response.parse_class = className if response.present?
103
+ response
104
+ end
105
+
106
+ # Fetch a specific object from a collection.
107
+ # @param className [String] the name of the Parse collection.
108
+ # @param id [String] The objectId of the record in the collection.
109
+ # @param query [Hash] optional query parameters like keys and include.
110
+ # @param opts [Hash] additional options to pass to the {Parse::Client} request.
111
+ # @param headers [Hash] additional HTTP headers to send with the request.
112
+ # @return [Parse::Response]
113
+ def fetch_object(className, id, query: nil, headers: {}, **opts)
114
+ response = request :get, uri_path(className, id), query: query, headers: headers, opts: opts
115
+ response.parse_class = className if response.present?
116
+ response
117
+ end
118
+
119
+ # Fetch a set of matching objects for a query.
120
+ # @param className [String] the name of the Parse collection.
121
+ # @param query [Hash] The set of query constraints.
122
+ # @param opts [Hash] additional options to pass to the {Parse::Client} request.
123
+ # @param headers [Hash] additional HTTP headers to send with the request.
124
+ # @return [Parse::Response]
125
+ # @see Parse::Query
126
+ def find_objects(className, query = {}, headers: {}, **opts)
127
+ response = request :get, uri_path(className), query: query, headers: headers, opts: opts
128
+ response.parse_class = className if response.present?
129
+ response
130
+ end
131
+
132
+ # Update an object in a collection.
133
+ # @param className [String] the name of the Parse collection.
134
+ # @param id [String] The objectId of the record in the collection.
135
+ # @param body [Hash] The key value pairs to update.
136
+ # @param opts [Hash] additional options to pass to the {Parse::Client} request.
137
+ # @param headers [Hash] additional HTTP headers to send with the request.
138
+ # @return [Parse::Response]
139
+ def update_object(className, id, body = {}, headers: {}, **opts)
140
+ response = request :put, uri_path(className, id), body: body, headers: headers, opts: opts
141
+ response.parse_class = className if response.present?
142
+ response
143
+ end
144
+ end #Objects
145
+ end #API
146
+ end