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.
- checksums.yaml +7 -0
- data/.bundle/config +2 -0
- data/.env.sample +112 -0
- data/.env.test +10 -0
- data/.github/workflows/ruby.yml +36 -0
- data/.gitignore +49 -0
- data/.ruby-version +1 -0
- data/.solargraph.yml +22 -0
- data/CHANGELOG.md +5816 -0
- data/Gemfile +30 -0
- data/Gemfile.lock +175 -0
- data/LICENSE.txt +23 -0
- data/Makefile +63 -0
- data/README.md +5655 -0
- data/Rakefile +573 -0
- data/bin/console +38 -0
- data/bin/parse-console +136 -0
- data/bin/server +17 -0
- data/bin/setup +7 -0
- data/config/parse-config.json +12 -0
- data/docs/TEST_SERVER.md +271 -0
- data/docs/_config.yml +1 -0
- data/docs/mcp_guide.md +3484 -0
- data/docs/mongodb_direct_guide.md +1348 -0
- data/docs/mongodb_index_optimization_guide.md +631 -0
- data/examples/transaction_example.rb +219 -0
- data/lib/parse/acl_scope.rb +728 -0
- data/lib/parse/agent/cancellation_token.rb +80 -0
- data/lib/parse/agent/constraint_translator.rb +480 -0
- data/lib/parse/agent/describe.rb +420 -0
- data/lib/parse/agent/errors.rb +133 -0
- data/lib/parse/agent/mcp_client.rb +557 -0
- data/lib/parse/agent/mcp_dispatcher.rb +1023 -0
- data/lib/parse/agent/mcp_rack_app.rb +1143 -0
- data/lib/parse/agent/mcp_server.rb +376 -0
- data/lib/parse/agent/metadata_audit.rb +259 -0
- data/lib/parse/agent/metadata_dsl.rb +733 -0
- data/lib/parse/agent/metadata_registry.rb +794 -0
- data/lib/parse/agent/pipeline_validator.rb +82 -0
- data/lib/parse/agent/prompts.rb +351 -0
- data/lib/parse/agent/rate_limiter.rb +158 -0
- data/lib/parse/agent/relation_graph.rb +162 -0
- data/lib/parse/agent/result_formatter.rb +453 -0
- data/lib/parse/agent/tools.rb +5489 -0
- data/lib/parse/agent.rb +3249 -0
- data/lib/parse/api/aggregate.rb +79 -0
- data/lib/parse/api/all.rb +26 -0
- data/lib/parse/api/analytics.rb +18 -0
- data/lib/parse/api/batch.rb +33 -0
- data/lib/parse/api/cloud_functions.rb +58 -0
- data/lib/parse/api/config.rb +125 -0
- data/lib/parse/api/files.rb +29 -0
- data/lib/parse/api/hooks.rb +117 -0
- data/lib/parse/api/objects.rb +146 -0
- data/lib/parse/api/path_segment.rb +75 -0
- data/lib/parse/api/push.rb +20 -0
- data/lib/parse/api/schema.rb +49 -0
- data/lib/parse/api/server.rb +50 -0
- data/lib/parse/api/sessions.rb +24 -0
- data/lib/parse/api/users.rb +250 -0
- data/lib/parse/atlas_search/index_manager.rb +353 -0
- data/lib/parse/atlas_search/result.rb +204 -0
- data/lib/parse/atlas_search/search_builder.rb +604 -0
- data/lib/parse/atlas_search/session.rb +253 -0
- data/lib/parse/atlas_search.rb +995 -0
- data/lib/parse/client/authentication.rb +97 -0
- data/lib/parse/client/batch.rb +234 -0
- data/lib/parse/client/body_builder.rb +240 -0
- data/lib/parse/client/caching.rb +203 -0
- data/lib/parse/client/logging.rb +293 -0
- data/lib/parse/client/profiling.rb +181 -0
- data/lib/parse/client/protocol.rb +91 -0
- data/lib/parse/client/request.rb +233 -0
- data/lib/parse/client/response.rb +208 -0
- data/lib/parse/client.rb +1104 -0
- data/lib/parse/clp_scope.rb +361 -0
- data/lib/parse/live_query/circuit_breaker.rb +256 -0
- data/lib/parse/live_query/client.rb +1001 -0
- data/lib/parse/live_query/configuration.rb +224 -0
- data/lib/parse/live_query/event.rb +115 -0
- data/lib/parse/live_query/event_queue.rb +272 -0
- data/lib/parse/live_query/health_monitor.rb +214 -0
- data/lib/parse/live_query/logging.rb +149 -0
- data/lib/parse/live_query/subscription.rb +294 -0
- data/lib/parse/live_query.rb +163 -0
- data/lib/parse/lookup_rewriter.rb +445 -0
- data/lib/parse/model/acl.rb +968 -0
- data/lib/parse/model/associations/belongs_to.rb +275 -0
- data/lib/parse/model/associations/collection_proxy.rb +435 -0
- data/lib/parse/model/associations/has_many.rb +597 -0
- data/lib/parse/model/associations/has_one.rb +158 -0
- data/lib/parse/model/associations/pointer_collection_proxy.rb +134 -0
- data/lib/parse/model/associations/relation_collection_proxy.rb +177 -0
- data/lib/parse/model/bytes.rb +62 -0
- data/lib/parse/model/classes/audience.rb +262 -0
- data/lib/parse/model/classes/installation.rb +363 -0
- data/lib/parse/model/classes/job_schedule.rb +153 -0
- data/lib/parse/model/classes/job_status.rb +264 -0
- data/lib/parse/model/classes/product.rb +75 -0
- data/lib/parse/model/classes/push_status.rb +263 -0
- data/lib/parse/model/classes/role.rb +751 -0
- data/lib/parse/model/classes/session.rb +201 -0
- data/lib/parse/model/classes/user.rb +943 -0
- data/lib/parse/model/clp.rb +544 -0
- data/lib/parse/model/core/actions.rb +1268 -0
- data/lib/parse/model/core/builder.rb +139 -0
- data/lib/parse/model/core/create_lock.rb +386 -0
- data/lib/parse/model/core/describe.rb +382 -0
- data/lib/parse/model/core/enhanced_change_tracking.rb +159 -0
- data/lib/parse/model/core/errors.rb +38 -0
- data/lib/parse/model/core/fetching.rb +566 -0
- data/lib/parse/model/core/field_guards.rb +220 -0
- data/lib/parse/model/core/indexing.rb +382 -0
- data/lib/parse/model/core/parse_reference.rb +407 -0
- data/lib/parse/model/core/properties.rb +809 -0
- data/lib/parse/model/core/querying.rb +491 -0
- data/lib/parse/model/core/schema.rb +202 -0
- data/lib/parse/model/core/search_indexing.rb +174 -0
- data/lib/parse/model/date.rb +88 -0
- data/lib/parse/model/email.rb +213 -0
- data/lib/parse/model/file.rb +527 -0
- data/lib/parse/model/geojson.rb +271 -0
- data/lib/parse/model/geopoint.rb +261 -0
- data/lib/parse/model/model.rb +260 -0
- data/lib/parse/model/object.rb +2068 -0
- data/lib/parse/model/phone.rb +520 -0
- data/lib/parse/model/pointer.rb +443 -0
- data/lib/parse/model/polygon.rb +406 -0
- data/lib/parse/model/push.rb +975 -0
- data/lib/parse/model/shortnames.rb +8 -0
- data/lib/parse/model/time_zone.rb +141 -0
- data/lib/parse/model/validations/uniqueness_validator.rb +97 -0
- data/lib/parse/model/validations.rb +96 -0
- data/lib/parse/mongodb.rb +2300 -0
- data/lib/parse/pipeline_security.rb +554 -0
- data/lib/parse/query/constraint.rb +198 -0
- data/lib/parse/query/constraints.rb +3279 -0
- data/lib/parse/query/cursor.rb +434 -0
- data/lib/parse/query/n_plus_one_detector.rb +445 -0
- data/lib/parse/query/operation.rb +104 -0
- data/lib/parse/query/ordering.rb +66 -0
- data/lib/parse/query.rb +7028 -0
- data/lib/parse/schema/index_migrator.rb +291 -0
- data/lib/parse/schema/search_index_migrator.rb +289 -0
- data/lib/parse/schema.rb +494 -0
- data/lib/parse/stack/generators/rails.rb +40 -0
- data/lib/parse/stack/generators/templates/model.erb +51 -0
- data/lib/parse/stack/generators/templates/model_installation.rb +4 -0
- data/lib/parse/stack/generators/templates/model_role.rb +4 -0
- data/lib/parse/stack/generators/templates/model_session.rb +4 -0
- data/lib/parse/stack/generators/templates/model_user.rb +11 -0
- data/lib/parse/stack/generators/templates/parse.rb +12 -0
- data/lib/parse/stack/generators/templates/webhooks.rb +10 -0
- data/lib/parse/stack/railtie.rb +18 -0
- data/lib/parse/stack/tasks.rb +563 -0
- data/lib/parse/stack/version.rb +11 -0
- data/lib/parse/stack.rb +455 -0
- data/lib/parse/two_factor_auth/user_extension.rb +449 -0
- data/lib/parse/two_factor_auth.rb +310 -0
- data/lib/parse/webhooks/payload.rb +360 -0
- data/lib/parse/webhooks/registration.rb +199 -0
- data/lib/parse/webhooks/replay_protection.rb +189 -0
- data/lib/parse/webhooks.rb +510 -0
- data/lib/parse-stack-next.rb +5 -0
- data/lib/parse-stack.rb +5 -0
- data/parse-stack-next.gemspec +82 -0
- data/parse-stack.png +0 -0
- data/scripts/debug-ips.js +35 -0
- data/scripts/docker/Dockerfile.parse +13 -0
- data/scripts/docker/atlas-init.js +284 -0
- data/scripts/docker/docker-compose.atlas.yml +76 -0
- data/scripts/docker/docker-compose.test.yml +106 -0
- data/scripts/docker/mongo-init.js +21 -0
- data/scripts/eval_mcp_with_lm_studio.rb +274 -0
- data/scripts/start-parse.sh +90 -0
- data/scripts/start_mcp_server.rb +78 -0
- data/scripts/test_server_connection.rb +82 -0
- 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
|