reqcord 0.1.0 → 0.1.1
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/.gitignore +8 -0
- data/CHANGELOG.md +77 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +171 -0
- data/LICENSE.txt +21 -0
- data/README.md +239 -95
- data/Rakefile +13 -0
- data/docs/configuration.md +319 -0
- data/examples/complex-spec-app/.rspec +1 -0
- data/examples/complex-spec-app/README.md +21 -0
- data/examples/complex-spec-app/app.rb +363 -0
- data/examples/complex-spec-app/docs/api/README.md +104 -0
- data/examples/complex-spec-app/docs/api/api/v1/admin/products/create.md +117 -0
- data/examples/complex-spec-app/docs/api/api/v1/admin/products/destroy.md +51 -0
- data/examples/complex-spec-app/docs/api/api/v1/admin/products/index.md +11 -0
- data/examples/complex-spec-app/docs/api/api/v1/admin/products/list.md +106 -0
- data/examples/complex-spec-app/docs/api/api/v1/auth/index.md +9 -0
- data/examples/complex-spec-app/docs/api/api/v1/auth/login.md +76 -0
- data/examples/complex-spec-app/docs/api/api/v1/cart-items/create.md +91 -0
- data/examples/complex-spec-app/docs/api/api/v1/cart-items/destroy.md +51 -0
- data/examples/complex-spec-app/docs/api/api/v1/cart-items/index.md +10 -0
- data/examples/complex-spec-app/docs/api/api/v1/carts/checkout.md +102 -0
- data/examples/complex-spec-app/docs/api/api/v1/carts/index.md +10 -0
- data/examples/complex-spec-app/docs/api/api/v1/carts/show.md +73 -0
- data/examples/complex-spec-app/docs/api/api/v1/home/index.md +9 -0
- data/examples/complex-spec-app/docs/api/api/v1/home/list.md +47 -0
- data/examples/complex-spec-app/docs/api/api/v1/notes/create.md +94 -0
- data/examples/complex-spec-app/docs/api/api/v1/notes/index.md +10 -0
- data/examples/complex-spec-app/docs/api/api/v1/notes/list.md +61 -0
- data/examples/complex-spec-app/docs/api/api/v1/orders/cancel.md +69 -0
- data/examples/complex-spec-app/docs/api/api/v1/orders/create.md +150 -0
- data/examples/complex-spec-app/docs/api/api/v1/orders/destroy.md +37 -0
- data/examples/complex-spec-app/docs/api/api/v1/orders/index.md +13 -0
- data/examples/complex-spec-app/docs/api/api/v1/orders/list.md +78 -0
- data/examples/complex-spec-app/docs/api/api/v1/orders/show.md +83 -0
- data/examples/complex-spec-app/docs/api/api/v1/products/index.md +11 -0
- data/examples/complex-spec-app/docs/api/api/v1/products/list.md +83 -0
- data/examples/complex-spec-app/docs/api/api/v1/products/search.md +88 -0
- data/examples/complex-spec-app/docs/api/api/v1/products/show.md +71 -0
- data/examples/complex-spec-app/docs/api/api/v1/profiles/index.md +10 -0
- data/examples/complex-spec-app/docs/api/api/v1/profiles/show.md +61 -0
- data/examples/complex-spec-app/docs/api/api/v1/profiles/update.md +91 -0
- data/examples/complex-spec-app/docs/api/api/v2/products/index.md +9 -0
- data/examples/complex-spec-app/docs/api/api/v2/products/list.md +77 -0
- data/examples/complex-spec-app/docs/api/curl/api/v1/admin/products/create.sh +18 -0
- data/examples/complex-spec-app/docs/api/curl/api/v1/admin/products/destroy.sh +9 -0
- data/examples/complex-spec-app/docs/api/curl/api/v1/admin/products/list.sh +9 -0
- data/examples/complex-spec-app/docs/api/curl/api/v1/auth/login.sh +9 -0
- data/examples/complex-spec-app/docs/api/curl/api/v1/cart-items/create.sh +16 -0
- data/examples/complex-spec-app/docs/api/curl/api/v1/cart-items/destroy.sh +9 -0
- data/examples/complex-spec-app/docs/api/curl/api/v1/carts/checkout.sh +13 -0
- data/examples/complex-spec-app/docs/api/curl/api/v1/carts/show.sh +9 -0
- data/examples/complex-spec-app/docs/api/curl/api/v1/home/list.sh +8 -0
- data/examples/complex-spec-app/docs/api/curl/api/v1/notes/create.sh +15 -0
- data/examples/complex-spec-app/docs/api/curl/api/v1/notes/list.sh +9 -0
- data/examples/complex-spec-app/docs/api/curl/api/v1/orders/cancel.sh +9 -0
- data/examples/complex-spec-app/docs/api/curl/api/v1/orders/create.sh +29 -0
- data/examples/complex-spec-app/docs/api/curl/api/v1/orders/destroy.sh +9 -0
- data/examples/complex-spec-app/docs/api/curl/api/v1/orders/list.sh +9 -0
- data/examples/complex-spec-app/docs/api/curl/api/v1/orders/show.sh +9 -0
- data/examples/complex-spec-app/docs/api/curl/api/v1/products/list.sh +8 -0
- data/examples/complex-spec-app/docs/api/curl/api/v1/products/search.sh +8 -0
- data/examples/complex-spec-app/docs/api/curl/api/v1/products/show.sh +8 -0
- data/examples/complex-spec-app/docs/api/curl/api/v1/profiles/show.sh +9 -0
- data/examples/complex-spec-app/docs/api/curl/api/v1/profiles/update.sh +16 -0
- data/examples/complex-spec-app/docs/api/curl/api/v2/products/list.sh +8 -0
- data/examples/complex-spec-app/docs/api/dataset.json +5116 -0
- data/examples/complex-spec-app/docs/api/postman/collection.json +2519 -0
- data/examples/complex-spec-app/generate.rb +14 -0
- data/examples/complex-spec-app/reqcord.yml +28 -0
- data/examples/complex-spec-app/spec/requests/admin_spec.rb +67 -0
- data/examples/complex-spec-app/spec/requests/cart_spec.rb +61 -0
- data/examples/complex-spec-app/spec/requests/home_and_auth_spec.rb +49 -0
- data/examples/complex-spec-app/spec/requests/orders_spec.rb +124 -0
- data/examples/complex-spec-app/spec/requests/products_spec.rb +65 -0
- data/examples/complex-spec-app/spec/spec_helper.rb +17 -0
- data/examples/complex-test-app/README.md +40 -0
- data/examples/complex-test-app/app.rb +384 -0
- data/examples/complex-test-app/docs/api/README.md +104 -0
- data/examples/complex-test-app/docs/api/api/v1/admin/products/create.md +117 -0
- data/examples/complex-test-app/docs/api/api/v1/admin/products/destroy.md +51 -0
- data/examples/complex-test-app/docs/api/api/v1/admin/products/index.md +11 -0
- data/examples/complex-test-app/docs/api/api/v1/admin/products/list.md +106 -0
- data/examples/complex-test-app/docs/api/api/v1/auth/index.md +9 -0
- data/examples/complex-test-app/docs/api/api/v1/auth/login.md +76 -0
- data/examples/complex-test-app/docs/api/api/v1/cart-items/create.md +91 -0
- data/examples/complex-test-app/docs/api/api/v1/cart-items/destroy.md +51 -0
- data/examples/complex-test-app/docs/api/api/v1/cart-items/index.md +10 -0
- data/examples/complex-test-app/docs/api/api/v1/carts/checkout.md +102 -0
- data/examples/complex-test-app/docs/api/api/v1/carts/index.md +10 -0
- data/examples/complex-test-app/docs/api/api/v1/carts/show.md +73 -0
- data/examples/complex-test-app/docs/api/api/v1/home/index.md +9 -0
- data/examples/complex-test-app/docs/api/api/v1/home/list.md +47 -0
- data/examples/complex-test-app/docs/api/api/v1/notes/create.md +94 -0
- data/examples/complex-test-app/docs/api/api/v1/notes/index.md +10 -0
- data/examples/complex-test-app/docs/api/api/v1/notes/list.md +61 -0
- data/examples/complex-test-app/docs/api/api/v1/orders/cancel.md +69 -0
- data/examples/complex-test-app/docs/api/api/v1/orders/create.md +150 -0
- data/examples/complex-test-app/docs/api/api/v1/orders/destroy.md +37 -0
- data/examples/complex-test-app/docs/api/api/v1/orders/index.md +13 -0
- data/examples/complex-test-app/docs/api/api/v1/orders/list.md +71 -0
- data/examples/complex-test-app/docs/api/api/v1/orders/show.md +83 -0
- data/examples/complex-test-app/docs/api/api/v1/products/index.md +11 -0
- data/examples/complex-test-app/docs/api/api/v1/products/list.md +83 -0
- data/examples/complex-test-app/docs/api/api/v1/products/search.md +88 -0
- data/examples/complex-test-app/docs/api/api/v1/products/show.md +71 -0
- data/examples/complex-test-app/docs/api/api/v1/profiles/index.md +10 -0
- data/examples/complex-test-app/docs/api/api/v1/profiles/show.md +61 -0
- data/examples/complex-test-app/docs/api/api/v1/profiles/update.md +91 -0
- data/examples/complex-test-app/docs/api/api/v2/products/index.md +9 -0
- data/examples/complex-test-app/docs/api/api/v2/products/list.md +77 -0
- data/examples/complex-test-app/docs/api/curl/api/v1/admin/products/create.sh +18 -0
- data/examples/complex-test-app/docs/api/curl/api/v1/admin/products/destroy.sh +9 -0
- data/examples/complex-test-app/docs/api/curl/api/v1/admin/products/list.sh +9 -0
- data/examples/complex-test-app/docs/api/curl/api/v1/auth/login.sh +9 -0
- data/examples/complex-test-app/docs/api/curl/api/v1/cart-items/create.sh +16 -0
- data/examples/complex-test-app/docs/api/curl/api/v1/cart-items/destroy.sh +9 -0
- data/examples/complex-test-app/docs/api/curl/api/v1/carts/checkout.sh +13 -0
- data/examples/complex-test-app/docs/api/curl/api/v1/carts/show.sh +9 -0
- data/examples/complex-test-app/docs/api/curl/api/v1/home/list.sh +8 -0
- data/examples/complex-test-app/docs/api/curl/api/v1/notes/create.sh +15 -0
- data/examples/complex-test-app/docs/api/curl/api/v1/notes/list.sh +9 -0
- data/examples/complex-test-app/docs/api/curl/api/v1/orders/cancel.sh +9 -0
- data/examples/complex-test-app/docs/api/curl/api/v1/orders/create.sh +29 -0
- data/examples/complex-test-app/docs/api/curl/api/v1/orders/destroy.sh +9 -0
- data/examples/complex-test-app/docs/api/curl/api/v1/orders/list.sh +9 -0
- data/examples/complex-test-app/docs/api/curl/api/v1/orders/show.sh +9 -0
- data/examples/complex-test-app/docs/api/curl/api/v1/products/list.sh +8 -0
- data/examples/complex-test-app/docs/api/curl/api/v1/products/search.sh +8 -0
- data/examples/complex-test-app/docs/api/curl/api/v1/products/show.sh +8 -0
- data/examples/complex-test-app/docs/api/curl/api/v1/profiles/show.sh +9 -0
- data/examples/complex-test-app/docs/api/curl/api/v1/profiles/update.sh +16 -0
- data/examples/complex-test-app/docs/api/curl/api/v2/products/list.sh +8 -0
- data/examples/complex-test-app/docs/api/dataset.json +5109 -0
- data/examples/complex-test-app/docs/api/postman/collection.json +2568 -0
- data/examples/complex-test-app/generate.rb +14 -0
- data/examples/complex-test-app/reqcord.yml +28 -0
- data/examples/complex-test-app/test/integration/admin_test.rb +70 -0
- data/examples/complex-test-app/test/integration/cart_test.rb +64 -0
- data/examples/complex-test-app/test/integration/home_and_auth_test.rb +53 -0
- data/examples/complex-test-app/test/integration/orders_test.rb +127 -0
- data/examples/complex-test-app/test/integration/products_test.rb +66 -0
- data/examples/reqcord.yml +58 -0
- data/examples/spec-app/.rspec +1 -0
- data/examples/spec-app/README.md +36 -0
- data/examples/spec-app/app.rb +209 -0
- data/examples/spec-app/docs/api/README.md +45 -0
- data/examples/spec-app/docs/api/api/v1/customers/create.md +95 -0
- data/examples/spec-app/docs/api/api/v1/customers/index.md +11 -0
- data/examples/spec-app/docs/api/api/v1/customers/list.md +67 -0
- data/examples/spec-app/docs/api/api/v1/customers/show.md +65 -0
- data/examples/spec-app/docs/api/api/v1/tasks/complete.md +51 -0
- data/examples/spec-app/docs/api/api/v1/tasks/create.md +90 -0
- data/examples/spec-app/docs/api/api/v1/tasks/destroy.md +35 -0
- data/examples/spec-app/docs/api/api/v1/tasks/index.md +14 -0
- data/examples/spec-app/docs/api/api/v1/tasks/list.md +65 -0
- data/examples/spec-app/docs/api/api/v1/tasks/show.md +65 -0
- data/examples/spec-app/docs/api/api/v1/tasks/update.md +74 -0
- data/examples/spec-app/docs/api/api/v1/users/create.md +95 -0
- data/examples/spec-app/docs/api/api/v1/users/index.md +11 -0
- data/examples/spec-app/docs/api/api/v1/users/list.md +67 -0
- data/examples/spec-app/docs/api/api/v1/users/show.md +65 -0
- data/examples/spec-app/docs/api/curl/api/v1/customers/create.sh +17 -0
- data/examples/spec-app/docs/api/curl/api/v1/customers/list.sh +9 -0
- data/examples/spec-app/docs/api/curl/api/v1/customers/show.sh +9 -0
- data/examples/spec-app/docs/api/curl/api/v1/tasks/complete.sh +8 -0
- data/examples/spec-app/docs/api/curl/api/v1/tasks/create.sh +15 -0
- data/examples/spec-app/docs/api/curl/api/v1/tasks/destroy.sh +8 -0
- data/examples/spec-app/docs/api/curl/api/v1/tasks/list.sh +8 -0
- data/examples/spec-app/docs/api/curl/api/v1/tasks/show.sh +8 -0
- data/examples/spec-app/docs/api/curl/api/v1/tasks/update.sh +14 -0
- data/examples/spec-app/docs/api/curl/api/v1/users/create.sh +17 -0
- data/examples/spec-app/docs/api/curl/api/v1/users/list.sh +9 -0
- data/examples/spec-app/docs/api/curl/api/v1/users/show.sh +9 -0
- data/examples/spec-app/docs/api/dataset.json +2402 -0
- data/examples/spec-app/docs/api/postman/collection.json +1290 -0
- data/examples/spec-app/generate.rb +14 -0
- data/examples/spec-app/reqcord.yml +27 -0
- data/examples/spec-app/spec/requests/customers_spec.rb +71 -0
- data/examples/spec-app/spec/requests/tasks_spec.rb +78 -0
- data/examples/spec-app/spec/requests/users_spec.rb +68 -0
- data/examples/spec-app/spec/spec_helper.rb +17 -0
- data/examples/test-app/README.md +57 -0
- data/examples/test-app/app.rb +212 -0
- data/examples/test-app/docs/api/README.md +45 -0
- data/examples/test-app/docs/api/api/v1/customers/create.md +95 -0
- data/examples/test-app/docs/api/api/v1/customers/index.md +11 -0
- data/examples/test-app/docs/api/api/v1/customers/list.md +67 -0
- data/examples/test-app/docs/api/api/v1/customers/show.md +65 -0
- data/examples/test-app/docs/api/api/v1/tasks/complete.md +51 -0
- data/examples/test-app/docs/api/api/v1/tasks/create.md +90 -0
- data/examples/test-app/docs/api/api/v1/tasks/destroy.md +35 -0
- data/examples/test-app/docs/api/api/v1/tasks/index.md +14 -0
- data/examples/test-app/docs/api/api/v1/tasks/list.md +65 -0
- data/examples/test-app/docs/api/api/v1/tasks/show.md +65 -0
- data/examples/test-app/docs/api/api/v1/tasks/update.md +74 -0
- data/examples/test-app/docs/api/api/v1/users/create.md +95 -0
- data/examples/test-app/docs/api/api/v1/users/index.md +11 -0
- data/examples/test-app/docs/api/api/v1/users/list.md +67 -0
- data/examples/test-app/docs/api/api/v1/users/show.md +65 -0
- data/examples/test-app/docs/api/curl/api/v1/customers/create.sh +17 -0
- data/examples/test-app/docs/api/curl/api/v1/customers/list.sh +9 -0
- data/examples/test-app/docs/api/curl/api/v1/customers/show.sh +9 -0
- data/examples/test-app/docs/api/curl/api/v1/tasks/complete.sh +8 -0
- data/examples/test-app/docs/api/curl/api/v1/tasks/create.sh +15 -0
- data/examples/test-app/docs/api/curl/api/v1/tasks/destroy.sh +8 -0
- data/examples/test-app/docs/api/curl/api/v1/tasks/list.sh +8 -0
- data/examples/test-app/docs/api/curl/api/v1/tasks/show.sh +8 -0
- data/examples/test-app/docs/api/curl/api/v1/tasks/update.sh +14 -0
- data/examples/test-app/docs/api/curl/api/v1/users/create.sh +17 -0
- data/examples/test-app/docs/api/curl/api/v1/users/list.sh +9 -0
- data/examples/test-app/docs/api/curl/api/v1/users/show.sh +9 -0
- data/examples/test-app/docs/api/dataset.json +2402 -0
- data/examples/test-app/docs/api/postman/collection.json +1290 -0
- data/examples/test-app/generate.rb +14 -0
- data/examples/test-app/reqcord.yml +28 -0
- data/examples/test-app/test/integration/customers_test.rb +72 -0
- data/examples/test-app/test/integration/tasks_test.rb +80 -0
- data/examples/test-app/test/integration/users_test.rb +72 -0
- data/lib/reqcord/capture/collector.rb +31 -0
- data/lib/reqcord/capture/integration_patch.rb +209 -0
- data/lib/reqcord/capture/minitest_context.rb +34 -0
- data/lib/reqcord/capture/rspec_context.rb +42 -0
- data/lib/reqcord/capture/test_context.rb +25 -0
- data/lib/reqcord/capture.rb +19 -0
- data/lib/reqcord/configuration.rb +198 -0
- data/lib/reqcord/dataset.rb +176 -0
- data/lib/reqcord/endpoint.rb +263 -0
- data/lib/reqcord/errors.rb +9 -0
- data/lib/reqcord/exporters/curl.rb +68 -0
- data/lib/reqcord/exporters/markdown.rb +295 -0
- data/lib/reqcord/exporters/postman.rb +206 -0
- data/lib/reqcord/exporters.rb +32 -0
- data/lib/reqcord/generator.rb +364 -0
- data/lib/reqcord/railtie.rb +51 -0
- data/lib/reqcord/renderers/curl.rb +56 -0
- data/lib/reqcord/renderers/payload.rb +69 -0
- data/lib/reqcord/request_example.rb +104 -0
- data/lib/reqcord/response_example.rb +72 -0
- data/lib/reqcord/route_collector.rb +242 -0
- data/lib/reqcord/sanitizers/sanitizer.rb +140 -0
- data/lib/reqcord/schema.rb +187 -0
- data/lib/reqcord/support.rb +58 -0
- data/lib/reqcord/version.rb +5 -0
- data/lib/reqcord.rb +78 -0
- data/lib/tasks/reqcord.rake +99 -0
- data/reqcord.gemspec +46 -0
- metadata +317 -3
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# A store API in one file: closer to a real application than test-app, so the
|
|
4
|
+
# generated documentation shows the harder cases.
|
|
5
|
+
#
|
|
6
|
+
# GET /api/v1 root inside a namespace ("Home")
|
|
7
|
+
# POST /api/v1/auth/login form-encoded, token in the response body
|
|
8
|
+
# GET|PATCH /api/v1/profile singular resource, PATCH and PUT folded
|
|
9
|
+
# GET /api/v1/products filter[category], sort, page, per_page
|
|
10
|
+
# GET /api/v1/products/search collection action, 400 without q
|
|
11
|
+
# GET /api/v1/orders/:id 403 for someone else's order
|
|
12
|
+
# POST /api/v1/orders line_items[] and a nested address
|
|
13
|
+
# POST /api/v1/orders/:id/cancel 409 once shipped
|
|
14
|
+
# GET /api/v1/orders/:order_id/notes nested resource
|
|
15
|
+
# GET /api/v1/cart singular resource with nested items
|
|
16
|
+
# POST /api/v1/cart/items add by sku, DELETE /cart/items/:sku
|
|
17
|
+
# POST /api/v1/cart/checkout payment_method: "card" | "bank_transfer"
|
|
18
|
+
# */api/v1/admin/products X-Api-Key, 409 on a duplicate sku
|
|
19
|
+
# GET /api/v2/products a second API version, cursor paging
|
|
20
|
+
ENV["RAILS_ENV"] ||= "test"
|
|
21
|
+
|
|
22
|
+
# Only needed because this example lives inside the Reqcord repository.
|
|
23
|
+
# In your own application, `gem "reqcord"` in the Gemfile is enough.
|
|
24
|
+
$LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
|
|
25
|
+
|
|
26
|
+
require "rails"
|
|
27
|
+
require "action_controller/railtie"
|
|
28
|
+
require "reqcord"
|
|
29
|
+
|
|
30
|
+
class ComplexApp < Rails::Application
|
|
31
|
+
config.root = __dir__
|
|
32
|
+
config.eager_load = false
|
|
33
|
+
config.logger = Logger.new(IO::NULL)
|
|
34
|
+
config.secret_key_base = "a" * 64
|
|
35
|
+
config.hosts.clear
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
Rails.application.initialize!
|
|
39
|
+
|
|
40
|
+
module Store
|
|
41
|
+
PRODUCTS = [
|
|
42
|
+
{ id: 1, sku: "TEA-001", name: "Earl Grey", category: "tea", price_cents: 1200, cost_cents: 500, tags: %w[black bergamot] },
|
|
43
|
+
{ id: 2, sku: "TEA-002", name: "Sencha", category: "tea", price_cents: 1500, cost_cents: 700, tags: %w[green] },
|
|
44
|
+
{ id: 3, sku: "MUG-001", name: "Stoneware Mug", category: "mugs", price_cents: 2400, cost_cents: 900, tags: %w[ceramic] },
|
|
45
|
+
{ id: 4, sku: "MUG-002", name: "Travel Mug", category: "mugs", price_cents: 3200, cost_cents: 1400, tags: %w[steel insulated] }
|
|
46
|
+
].freeze
|
|
47
|
+
|
|
48
|
+
PUBLIC_PRODUCT = %i[id sku name category price_cents tags].freeze
|
|
49
|
+
|
|
50
|
+
ORDERS = [
|
|
51
|
+
{ id: 1, user_id: 1, status: "pending", currency: "USD", total_cents: 2700 },
|
|
52
|
+
{ id: 2, user_id: 1, status: "shipped", currency: "USD", total_cents: 3200 },
|
|
53
|
+
{ id: 3, user_id: 2, status: "pending", currency: "EUR", total_cents: 1500 }
|
|
54
|
+
].freeze
|
|
55
|
+
|
|
56
|
+
NOTES = [
|
|
57
|
+
{ id: 1, order_id: 1, body: "Leave at the door", author: "ada" },
|
|
58
|
+
{ id: 2, order_id: 1, body: "Gift wrap please", author: "ada" }
|
|
59
|
+
].freeze
|
|
60
|
+
|
|
61
|
+
PROFILE = { id: 1, name: "Ada Lovelace", email: "ada@example.com", locale: "en" }.freeze
|
|
62
|
+
LOCALES = %w[en tr].freeze
|
|
63
|
+
|
|
64
|
+
CART_ITEMS = [
|
|
65
|
+
{ sku: "TEA-001", name: "Earl Grey", quantity: 2, unit_price_cents: 1200 },
|
|
66
|
+
{ sku: "MUG-001", name: "Stoneware Mug", quantity: 1, unit_price_cents: 2400 }
|
|
67
|
+
].freeze
|
|
68
|
+
PAYMENT_METHODS = %w[card bank_transfer].freeze
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
module Api
|
|
72
|
+
module V1
|
|
73
|
+
class BaseController < ActionController::API
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
def authenticate!
|
|
77
|
+
return if request.headers["Authorization"].present?
|
|
78
|
+
|
|
79
|
+
render json: { error: "Unauthorized" }, status: :unauthorized
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def not_found
|
|
83
|
+
render json: { error: "Not Found" }, status: :not_found
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
class HomeController < BaseController
|
|
88
|
+
def index
|
|
89
|
+
render json: { name: "Store API", version: "v1", links: { products: "/api/v1/products", orders: "/api/v1/orders" } }
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
class AuthController < BaseController
|
|
94
|
+
# A classic form login: the test posts a form, not JSON.
|
|
95
|
+
def login
|
|
96
|
+
if params[:email] == "ada@example.com" && params[:password] == "correct-horse-battery"
|
|
97
|
+
render json: { token: "tok_live_9f8e7d6c", token_type: "Bearer", expires_in: 3600 }
|
|
98
|
+
else
|
|
99
|
+
render json: { error: "Invalid email or password" }, status: :unauthorized
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
class ProfilesController < BaseController
|
|
105
|
+
before_action :authenticate!
|
|
106
|
+
|
|
107
|
+
def show
|
|
108
|
+
render json: Store::PROFILE
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def update
|
|
112
|
+
attributes = params.require(:profile).permit(:name, :locale).to_h.symbolize_keys
|
|
113
|
+
|
|
114
|
+
if attributes.key?(:locale) && !Store::LOCALES.include?(attributes[:locale])
|
|
115
|
+
return render(json: { errors: { locale: ["is not included in the list"] } }, status: :unprocessable_entity)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
render json: Store::PROFILE.merge(attributes)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
class ProductsController < BaseController
|
|
123
|
+
def index
|
|
124
|
+
products = Store::PRODUCTS
|
|
125
|
+
category = params.dig(:filter, :category)
|
|
126
|
+
products = products.select { |product| product[:category] == category } if category.present?
|
|
127
|
+
products = products.sort_by { |product| product[:price_cents] } if params[:sort] == "price_asc"
|
|
128
|
+
products = products.sort_by { |product| -product[:price_cents] } if params[:sort] == "price_desc"
|
|
129
|
+
|
|
130
|
+
page = params.fetch(:page, 1).to_i
|
|
131
|
+
per_page = params.fetch(:per_page, 2).to_i
|
|
132
|
+
slice = products.each_slice(per_page).to_a[page - 1] || []
|
|
133
|
+
|
|
134
|
+
render json: {
|
|
135
|
+
data: slice.map { |product| product.slice(*Store::PUBLIC_PRODUCT) },
|
|
136
|
+
meta: { page: page, per_page: per_page, total: products.size }
|
|
137
|
+
}
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def show
|
|
141
|
+
product = Store::PRODUCTS.find { |candidate| candidate[:id] == params[:id].to_i }
|
|
142
|
+
|
|
143
|
+
return not_found unless product
|
|
144
|
+
|
|
145
|
+
render json: product.slice(*Store::PUBLIC_PRODUCT)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def search
|
|
149
|
+
query = params[:q].to_s.strip
|
|
150
|
+
|
|
151
|
+
return render(json: { error: "q is required" }, status: :bad_request) if query.empty?
|
|
152
|
+
|
|
153
|
+
matches = Store::PRODUCTS.select { |product| product[:name].downcase.include?(query.downcase) }
|
|
154
|
+
|
|
155
|
+
render json: { query: query, results: matches.map { |product| product.slice(*Store::PUBLIC_PRODUCT) } }
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
class OrdersController < BaseController
|
|
160
|
+
before_action :authenticate!
|
|
161
|
+
before_action :find_order, only: %i[show cancel destroy]
|
|
162
|
+
|
|
163
|
+
def index
|
|
164
|
+
orders = Store::ORDERS.select { |order| order[:user_id] == 1 }
|
|
165
|
+
orders = orders.select { |order| order[:status] == params[:status] } if params[:status].present?
|
|
166
|
+
|
|
167
|
+
render json: orders
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def show
|
|
171
|
+
return render(json: { error: "Forbidden" }, status: :forbidden) unless @order[:user_id] == 1
|
|
172
|
+
|
|
173
|
+
render json: @order
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def create
|
|
177
|
+
attributes = params.require(:order)
|
|
178
|
+
.permit(line_items: %i[sku quantity], shipping_address: %i[line1 city country])
|
|
179
|
+
.to_h.deep_symbolize_keys
|
|
180
|
+
items = Array(attributes[:line_items])
|
|
181
|
+
|
|
182
|
+
return render(json: { errors: { line_items: ["can't be blank"] } }, status: :unprocessable_entity) if items.empty?
|
|
183
|
+
|
|
184
|
+
lines = items.map do |item|
|
|
185
|
+
product = Store::PRODUCTS.find { |candidate| candidate[:sku] == item[:sku] }
|
|
186
|
+
|
|
187
|
+
return render(json: { errors: { sku: ["#{item[:sku]} is not a known product"] } }, status: :unprocessable_entity) unless product
|
|
188
|
+
|
|
189
|
+
quantity = item[:quantity].to_i
|
|
190
|
+
{
|
|
191
|
+
sku: product[:sku], name: product[:name], quantity: quantity,
|
|
192
|
+
unit_price_cents: product[:price_cents], subtotal_cents: product[:price_cents] * quantity
|
|
193
|
+
}
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
render json: {
|
|
197
|
+
id: 4, status: "pending", currency: "USD",
|
|
198
|
+
total_cents: lines.sum { |line| line[:subtotal_cents] },
|
|
199
|
+
line_items: lines,
|
|
200
|
+
shipping_address: attributes[:shipping_address]
|
|
201
|
+
}, status: :created
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def cancel
|
|
205
|
+
if @order[:status] == "shipped"
|
|
206
|
+
return render(json: { error: "Shipped orders cannot be cancelled" }, status: :conflict)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
render json: @order.merge(status: "cancelled")
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def destroy
|
|
213
|
+
head :no_content
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
private
|
|
217
|
+
|
|
218
|
+
def find_order
|
|
219
|
+
@order = Store::ORDERS.find { |order| order[:id] == params[:id].to_i }
|
|
220
|
+
|
|
221
|
+
not_found unless @order
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Nested under an order: /api/v1/orders/:order_id/notes
|
|
226
|
+
class NotesController < BaseController
|
|
227
|
+
before_action :authenticate!
|
|
228
|
+
before_action :find_order
|
|
229
|
+
|
|
230
|
+
def index
|
|
231
|
+
render json: Store::NOTES.select { |note| note[:order_id] == @order[:id] }
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def create
|
|
235
|
+
body = params.dig(:note, :body).to_s
|
|
236
|
+
|
|
237
|
+
return render(json: { errors: { body: ["can't be blank"] } }, status: :unprocessable_entity) if body.empty?
|
|
238
|
+
|
|
239
|
+
render json: { id: 3, order_id: @order[:id], body: body, author: "ada" }, status: :created
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
private
|
|
243
|
+
|
|
244
|
+
def find_order
|
|
245
|
+
@order = Store::ORDERS.find { |order| order[:id] == params[:order_id].to_i }
|
|
246
|
+
|
|
247
|
+
not_found unless @order
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# `resource :cart`: one cart per customer, no id in the path.
|
|
252
|
+
class CartsController < BaseController
|
|
253
|
+
before_action :authenticate!
|
|
254
|
+
|
|
255
|
+
def show
|
|
256
|
+
render json: cart
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
# Turns the cart into an order.
|
|
260
|
+
def checkout
|
|
261
|
+
method = params[:payment_method].to_s
|
|
262
|
+
|
|
263
|
+
unless Store::PAYMENT_METHODS.include?(method)
|
|
264
|
+
return render(json: { errors: { payment_method: ["is not included in the list"] } }, status: :unprocessable_entity)
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
render json: { id: 5, status: "pending", payment_method: method, total_cents: cart[:total_cents], line_items: cart[:items] },
|
|
268
|
+
status: :created
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
private
|
|
272
|
+
|
|
273
|
+
def cart
|
|
274
|
+
{ items: Store::CART_ITEMS, total_cents: Store::CART_ITEMS.sum { |item| item[:quantity] * item[:unit_price_cents] } }
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# Nested under the cart and addressed by sku: /api/v1/cart/items/:sku
|
|
279
|
+
class CartItemsController < BaseController
|
|
280
|
+
before_action :authenticate!
|
|
281
|
+
|
|
282
|
+
def create
|
|
283
|
+
attributes = params.require(:item).permit(:sku, :quantity).to_h.symbolize_keys
|
|
284
|
+
product = Store::PRODUCTS.find { |candidate| candidate[:sku] == attributes[:sku] }
|
|
285
|
+
|
|
286
|
+
return render(json: { errors: { sku: ["#{attributes[:sku]} is not a known product"] } }, status: :unprocessable_entity) unless product
|
|
287
|
+
|
|
288
|
+
render json: { sku: product[:sku], name: product[:name], quantity: attributes[:quantity].to_i, unit_price_cents: product[:price_cents] },
|
|
289
|
+
status: :created
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def destroy
|
|
293
|
+
return not_found unless Store::CART_ITEMS.any? { |item| item[:sku] == params[:sku] }
|
|
294
|
+
|
|
295
|
+
head :no_content
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
module Admin
|
|
300
|
+
# A different credential (X-Api-Key) and a fuller view of the same data.
|
|
301
|
+
class ProductsController < BaseController
|
|
302
|
+
before_action :require_api_key
|
|
303
|
+
|
|
304
|
+
def index
|
|
305
|
+
render json: Store::PRODUCTS
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
def create
|
|
309
|
+
attributes = params.require(:product).permit(:sku, :name, :category, :price_cents).to_h.symbolize_keys
|
|
310
|
+
|
|
311
|
+
if attributes[:sku].to_s.empty? || attributes[:name].to_s.empty?
|
|
312
|
+
return render(json: { errors: { sku: ["can't be blank"] } }, status: :unprocessable_entity)
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
if Store::PRODUCTS.any? { |product| product[:sku] == attributes[:sku] }
|
|
316
|
+
return render(json: { error: "sku #{attributes[:sku]} already exists" }, status: :conflict)
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
render json: { id: 5, cost_cents: 0, tags: [], **attributes }, status: :created
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
def destroy
|
|
323
|
+
return not_found unless Store::PRODUCTS.any? { |product| product[:id] == params[:id].to_i }
|
|
324
|
+
|
|
325
|
+
head :no_content
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
private
|
|
329
|
+
|
|
330
|
+
def require_api_key
|
|
331
|
+
return if request.headers["X-Api-Key"].present?
|
|
332
|
+
|
|
333
|
+
render json: { error: "API key required" }, status: :unauthorized
|
|
334
|
+
end
|
|
335
|
+
end
|
|
336
|
+
end
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
module V2
|
|
340
|
+
class ProductsController < ActionController::API
|
|
341
|
+
# v2 pages by cursor instead of page numbers.
|
|
342
|
+
def index
|
|
343
|
+
start = params[:cursor].present? ? 2 : 0
|
|
344
|
+
items = Store::PRODUCTS[start, 2].map { |product| product.slice(*Store::PUBLIC_PRODUCT).merge(price: format("%.2f", product[:price_cents] / 100.0)) }
|
|
345
|
+
|
|
346
|
+
render json: { items: items, next_cursor: start.zero? ? "eyJpZCI6Mn0" : nil }
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
Rails.application.routes.draw do
|
|
353
|
+
namespace :api do
|
|
354
|
+
namespace :v1 do
|
|
355
|
+
root to: "home#index"
|
|
356
|
+
|
|
357
|
+
post "auth/login", to: "auth#login"
|
|
358
|
+
|
|
359
|
+
resource :profile, only: %i[show update]
|
|
360
|
+
|
|
361
|
+
resources :products, only: %i[index show] do
|
|
362
|
+
get :search, on: :collection
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
resources :orders, only: %i[index show create destroy] do
|
|
366
|
+
post :cancel, on: :member
|
|
367
|
+
resources :notes, only: %i[index create]
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
resource :cart, only: :show do
|
|
371
|
+
post :checkout
|
|
372
|
+
resources :items, only: %i[create destroy], controller: "cart_items", param: :sku
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
namespace :admin do
|
|
376
|
+
resources :products, only: %i[index create destroy]
|
|
377
|
+
end
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
namespace :v2 do
|
|
381
|
+
resources :products, only: :index
|
|
382
|
+
end
|
|
383
|
+
end
|
|
384
|
+
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# API Documentation
|
|
2
|
+
|
|
3
|
+
Generated by Reqcord from the application's integration tests.
|
|
4
|
+
|
|
5
|
+
Base URL: `http://localhost:3000`
|
|
6
|
+
|
|
7
|
+
## Placeholders
|
|
8
|
+
|
|
9
|
+
| Header | Replace with |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| Authorization | `Bearer {{token}}` |
|
|
12
|
+
| X-Api-Key | `{{api_key}}` |
|
|
13
|
+
|
|
14
|
+
## Products
|
|
15
|
+
|
|
16
|
+
Namespace: `api/v1/admin`
|
|
17
|
+
|
|
18
|
+
| Method | Path | Description |
|
|
19
|
+
| --- | --- | --- |
|
|
20
|
+
| `GET` | [`/api/v1/admin/products`](api/v1/admin/products/list.md) | List Products |
|
|
21
|
+
| `POST` | [`/api/v1/admin/products`](api/v1/admin/products/create.md) | Create Product |
|
|
22
|
+
| `DELETE` | [`/api/v1/admin/products/:id`](api/v1/admin/products/destroy.md) | Delete Product |
|
|
23
|
+
|
|
24
|
+
## Auth
|
|
25
|
+
|
|
26
|
+
Namespace: `api/v1`
|
|
27
|
+
|
|
28
|
+
| Method | Path | Description |
|
|
29
|
+
| --- | --- | --- |
|
|
30
|
+
| `POST` | [`/api/v1/auth/login`](api/v1/auth/login.md) | Login |
|
|
31
|
+
|
|
32
|
+
## Cart Items
|
|
33
|
+
|
|
34
|
+
Namespace: `api/v1`
|
|
35
|
+
|
|
36
|
+
| Method | Path | Description |
|
|
37
|
+
| --- | --- | --- |
|
|
38
|
+
| `POST` | [`/api/v1/cart/items`](api/v1/cart-items/create.md) | Create Cart Item |
|
|
39
|
+
| `DELETE` | [`/api/v1/cart/items/:sku`](api/v1/cart-items/destroy.md) | Delete Cart Item |
|
|
40
|
+
|
|
41
|
+
## Carts
|
|
42
|
+
|
|
43
|
+
Namespace: `api/v1`
|
|
44
|
+
|
|
45
|
+
| Method | Path | Description |
|
|
46
|
+
| --- | --- | --- |
|
|
47
|
+
| `GET` | [`/api/v1/cart`](api/v1/carts/show.md) | Get Cart |
|
|
48
|
+
| `POST` | [`/api/v1/cart/checkout`](api/v1/carts/checkout.md) | Checkout Cart |
|
|
49
|
+
|
|
50
|
+
## Home
|
|
51
|
+
|
|
52
|
+
Namespace: `api/v1`
|
|
53
|
+
|
|
54
|
+
| Method | Path | Description |
|
|
55
|
+
| --- | --- | --- |
|
|
56
|
+
| `GET` | [`/api/v1`](api/v1/home/list.md) | Home |
|
|
57
|
+
|
|
58
|
+
## Notes
|
|
59
|
+
|
|
60
|
+
Namespace: `api/v1`
|
|
61
|
+
|
|
62
|
+
| Method | Path | Description |
|
|
63
|
+
| --- | --- | --- |
|
|
64
|
+
| `GET` | [`/api/v1/orders/:order_id/notes`](api/v1/notes/list.md) | List Notes |
|
|
65
|
+
| `POST` | [`/api/v1/orders/:order_id/notes`](api/v1/notes/create.md) | Create Note |
|
|
66
|
+
|
|
67
|
+
## Orders
|
|
68
|
+
|
|
69
|
+
Namespace: `api/v1`
|
|
70
|
+
|
|
71
|
+
| Method | Path | Description |
|
|
72
|
+
| --- | --- | --- |
|
|
73
|
+
| `GET` | [`/api/v1/orders`](api/v1/orders/list.md) | List Orders |
|
|
74
|
+
| `POST` | [`/api/v1/orders`](api/v1/orders/create.md) | Create Order |
|
|
75
|
+
| `DELETE` | [`/api/v1/orders/:id`](api/v1/orders/destroy.md) | Delete Order |
|
|
76
|
+
| `GET` | [`/api/v1/orders/:id`](api/v1/orders/show.md) | Get Order |
|
|
77
|
+
| `POST` | [`/api/v1/orders/:id/cancel`](api/v1/orders/cancel.md) | Cancel Order |
|
|
78
|
+
|
|
79
|
+
## Products
|
|
80
|
+
|
|
81
|
+
Namespace: `api/v1`
|
|
82
|
+
|
|
83
|
+
| Method | Path | Description |
|
|
84
|
+
| --- | --- | --- |
|
|
85
|
+
| `GET` | [`/api/v1/products`](api/v1/products/list.md) | List Products |
|
|
86
|
+
| `GET` | [`/api/v1/products/:id`](api/v1/products/show.md) | Get Product |
|
|
87
|
+
| `GET` | [`/api/v1/products/search`](api/v1/products/search.md) | Search Products |
|
|
88
|
+
|
|
89
|
+
## Profiles
|
|
90
|
+
|
|
91
|
+
Namespace: `api/v1`
|
|
92
|
+
|
|
93
|
+
| Method | Path | Description |
|
|
94
|
+
| --- | --- | --- |
|
|
95
|
+
| `GET` | [`/api/v1/profile`](api/v1/profiles/show.md) | Get Profile |
|
|
96
|
+
| `PATCH` | [`/api/v1/profile`](api/v1/profiles/update.md) | Update Profile |
|
|
97
|
+
|
|
98
|
+
## Products
|
|
99
|
+
|
|
100
|
+
Namespace: `api/v2`
|
|
101
|
+
|
|
102
|
+
| Method | Path | Description |
|
|
103
|
+
| --- | --- | --- |
|
|
104
|
+
| `GET` | [`/api/v2/products`](api/v2/products/list.md) | List Products |
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Create Product
|
|
2
|
+
|
|
3
|
+
`POST /api/v1/admin/products`
|
|
4
|
+
|
|
5
|
+
Namespace: `api/v1/admin`
|
|
6
|
+
|
|
7
|
+
## Headers
|
|
8
|
+
|
|
9
|
+
| Header | Value |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| X-Api-Key | `{{api_key}}` |
|
|
12
|
+
| Content-Type | `application/json` |
|
|
13
|
+
| Accept | `application/json` |
|
|
14
|
+
|
|
15
|
+
## Body Parameters
|
|
16
|
+
|
|
17
|
+
| Field | Type | Required | Values |
|
|
18
|
+
| --- | --- | --- | --- |
|
|
19
|
+
| `product.sku` | string | yes | `"TEA-003"` |
|
|
20
|
+
| `product.name` | string | yes | `"Jasmine"` |
|
|
21
|
+
| `product.category` | string | yes | `"tea"` |
|
|
22
|
+
| `product.price_cents` | integer | yes | `1400` |
|
|
23
|
+
|
|
24
|
+
## Example Request
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"product": {
|
|
29
|
+
"sku": "TEA-003",
|
|
30
|
+
"name": "Jasmine",
|
|
31
|
+
"category": "tea",
|
|
32
|
+
"price_cents": 1400
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## cURL
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
curl --request POST \
|
|
41
|
+
--url "http://localhost:3000/api/v1/admin/products" \
|
|
42
|
+
--header "X-Api-Key: {{api_key}}" \
|
|
43
|
+
--header "Content-Type: application/json" \
|
|
44
|
+
--header "Accept: application/json" \
|
|
45
|
+
--data '{
|
|
46
|
+
"product": {
|
|
47
|
+
"sku": "TEA-003",
|
|
48
|
+
"name": "Jasmine",
|
|
49
|
+
"category": "tea",
|
|
50
|
+
"price_cents": 1400
|
|
51
|
+
}
|
|
52
|
+
}'
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Responses
|
|
56
|
+
|
|
57
|
+
### 201 Created
|
|
58
|
+
|
|
59
|
+
#### Fields
|
|
60
|
+
|
|
61
|
+
| Field | Type | Required | Values |
|
|
62
|
+
| --- | --- | --- | --- |
|
|
63
|
+
| `id` | integer | yes | `5` |
|
|
64
|
+
| `cost_cents` | integer | yes | `0` |
|
|
65
|
+
| `tags` | array | yes | - |
|
|
66
|
+
| `sku` | string | yes | `"TEA-003"` |
|
|
67
|
+
| `name` | string | yes | `"Jasmine"` |
|
|
68
|
+
| `category` | string | yes | `"tea"` |
|
|
69
|
+
| `price_cents` | integer | yes | `1400` |
|
|
70
|
+
|
|
71
|
+
```json
|
|
72
|
+
{
|
|
73
|
+
"id": 5,
|
|
74
|
+
"cost_cents": 0,
|
|
75
|
+
"tags": [],
|
|
76
|
+
"sku": "TEA-003",
|
|
77
|
+
"name": "Jasmine",
|
|
78
|
+
"category": "tea",
|
|
79
|
+
"price_cents": 1400
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### 409 Conflict
|
|
84
|
+
|
|
85
|
+
#### Fields
|
|
86
|
+
|
|
87
|
+
| Field | Type | Required | Values |
|
|
88
|
+
| --- | --- | --- | --- |
|
|
89
|
+
| `error` | string | yes | `"sku TEA-001 already exists"` |
|
|
90
|
+
|
|
91
|
+
```json
|
|
92
|
+
{
|
|
93
|
+
"error": "sku TEA-001 already exists"
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### 422 Unprocessable Content
|
|
98
|
+
|
|
99
|
+
#### Fields
|
|
100
|
+
|
|
101
|
+
| Field | Type | Required | Values |
|
|
102
|
+
| --- | --- | --- | --- |
|
|
103
|
+
| `errors.sku[]` | string | yes | `"can't be blank"` |
|
|
104
|
+
|
|
105
|
+
```json
|
|
106
|
+
{
|
|
107
|
+
"errors": {
|
|
108
|
+
"sku": [
|
|
109
|
+
"can't be blank"
|
|
110
|
+
]
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
Resource: [Products](index.md)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Delete Product
|
|
2
|
+
|
|
3
|
+
`DELETE /api/v1/admin/products/:id`
|
|
4
|
+
|
|
5
|
+
Namespace: `api/v1/admin`
|
|
6
|
+
|
|
7
|
+
## Headers
|
|
8
|
+
|
|
9
|
+
| Header | Value |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| X-Api-Key | `{{api_key}}` |
|
|
12
|
+
| Accept | `application/json` |
|
|
13
|
+
|
|
14
|
+
## Path Parameters
|
|
15
|
+
|
|
16
|
+
| Field | Type | Required | Values |
|
|
17
|
+
| --- | --- | --- | --- |
|
|
18
|
+
| `id` | string | yes | `"4"` |
|
|
19
|
+
|
|
20
|
+
## cURL
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
curl --request DELETE \
|
|
24
|
+
--url "http://localhost:3000/api/v1/admin/products/4" \
|
|
25
|
+
--header "X-Api-Key: {{api_key}}" \
|
|
26
|
+
--header "Accept: application/json"
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Responses
|
|
30
|
+
|
|
31
|
+
### 204 No Content
|
|
32
|
+
|
|
33
|
+
_No response body._
|
|
34
|
+
|
|
35
|
+
### 404 Not Found
|
|
36
|
+
|
|
37
|
+
#### Fields
|
|
38
|
+
|
|
39
|
+
| Field | Type | Required | Values |
|
|
40
|
+
| --- | --- | --- | --- |
|
|
41
|
+
| `error` | string | yes | `"Not Found"` |
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
{
|
|
45
|
+
"error": "Not Found"
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
Resource: [Products](index.md)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Products
|
|
2
|
+
|
|
3
|
+
Namespace: `api/v1/admin`
|
|
4
|
+
|
|
5
|
+
API version: `v1`
|
|
6
|
+
|
|
7
|
+
| Method | Path | Description |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| `GET` | [`/api/v1/admin/products`](list.md) | List Products |
|
|
10
|
+
| `POST` | [`/api/v1/admin/products`](create.md) | Create Product |
|
|
11
|
+
| `DELETE` | [`/api/v1/admin/products/:id`](destroy.md) | Delete Product |
|