reqcord 0.1.1 → 0.1.2

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 (223) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +13 -0
  3. data/Gemfile +5 -0
  4. data/README.md +13 -0
  5. data/gemfiles/rails_7.1.gemfile +13 -0
  6. data/gemfiles/rails_7.2.gemfile +13 -0
  7. data/gemfiles/rails_8.0.gemfile +13 -0
  8. data/gemfiles/rails_8.1.gemfile +13 -0
  9. data/lib/reqcord/version.rb +1 -1
  10. data/reqcord.gemspec +18 -4
  11. metadata +65 -217
  12. data/.gitignore +0 -8
  13. data/Gemfile.lock +0 -171
  14. data/examples/complex-spec-app/.rspec +0 -1
  15. data/examples/complex-spec-app/README.md +0 -21
  16. data/examples/complex-spec-app/app.rb +0 -363
  17. data/examples/complex-spec-app/docs/api/README.md +0 -104
  18. data/examples/complex-spec-app/docs/api/api/v1/admin/products/create.md +0 -117
  19. data/examples/complex-spec-app/docs/api/api/v1/admin/products/destroy.md +0 -51
  20. data/examples/complex-spec-app/docs/api/api/v1/admin/products/index.md +0 -11
  21. data/examples/complex-spec-app/docs/api/api/v1/admin/products/list.md +0 -106
  22. data/examples/complex-spec-app/docs/api/api/v1/auth/index.md +0 -9
  23. data/examples/complex-spec-app/docs/api/api/v1/auth/login.md +0 -76
  24. data/examples/complex-spec-app/docs/api/api/v1/cart-items/create.md +0 -91
  25. data/examples/complex-spec-app/docs/api/api/v1/cart-items/destroy.md +0 -51
  26. data/examples/complex-spec-app/docs/api/api/v1/cart-items/index.md +0 -10
  27. data/examples/complex-spec-app/docs/api/api/v1/carts/checkout.md +0 -102
  28. data/examples/complex-spec-app/docs/api/api/v1/carts/index.md +0 -10
  29. data/examples/complex-spec-app/docs/api/api/v1/carts/show.md +0 -73
  30. data/examples/complex-spec-app/docs/api/api/v1/home/index.md +0 -9
  31. data/examples/complex-spec-app/docs/api/api/v1/home/list.md +0 -47
  32. data/examples/complex-spec-app/docs/api/api/v1/notes/create.md +0 -94
  33. data/examples/complex-spec-app/docs/api/api/v1/notes/index.md +0 -10
  34. data/examples/complex-spec-app/docs/api/api/v1/notes/list.md +0 -61
  35. data/examples/complex-spec-app/docs/api/api/v1/orders/cancel.md +0 -69
  36. data/examples/complex-spec-app/docs/api/api/v1/orders/create.md +0 -150
  37. data/examples/complex-spec-app/docs/api/api/v1/orders/destroy.md +0 -37
  38. data/examples/complex-spec-app/docs/api/api/v1/orders/index.md +0 -13
  39. data/examples/complex-spec-app/docs/api/api/v1/orders/list.md +0 -78
  40. data/examples/complex-spec-app/docs/api/api/v1/orders/show.md +0 -83
  41. data/examples/complex-spec-app/docs/api/api/v1/products/index.md +0 -11
  42. data/examples/complex-spec-app/docs/api/api/v1/products/list.md +0 -83
  43. data/examples/complex-spec-app/docs/api/api/v1/products/search.md +0 -88
  44. data/examples/complex-spec-app/docs/api/api/v1/products/show.md +0 -71
  45. data/examples/complex-spec-app/docs/api/api/v1/profiles/index.md +0 -10
  46. data/examples/complex-spec-app/docs/api/api/v1/profiles/show.md +0 -61
  47. data/examples/complex-spec-app/docs/api/api/v1/profiles/update.md +0 -91
  48. data/examples/complex-spec-app/docs/api/api/v2/products/index.md +0 -9
  49. data/examples/complex-spec-app/docs/api/api/v2/products/list.md +0 -77
  50. data/examples/complex-spec-app/docs/api/curl/api/v1/admin/products/create.sh +0 -18
  51. data/examples/complex-spec-app/docs/api/curl/api/v1/admin/products/destroy.sh +0 -9
  52. data/examples/complex-spec-app/docs/api/curl/api/v1/admin/products/list.sh +0 -9
  53. data/examples/complex-spec-app/docs/api/curl/api/v1/auth/login.sh +0 -9
  54. data/examples/complex-spec-app/docs/api/curl/api/v1/cart-items/create.sh +0 -16
  55. data/examples/complex-spec-app/docs/api/curl/api/v1/cart-items/destroy.sh +0 -9
  56. data/examples/complex-spec-app/docs/api/curl/api/v1/carts/checkout.sh +0 -13
  57. data/examples/complex-spec-app/docs/api/curl/api/v1/carts/show.sh +0 -9
  58. data/examples/complex-spec-app/docs/api/curl/api/v1/home/list.sh +0 -8
  59. data/examples/complex-spec-app/docs/api/curl/api/v1/notes/create.sh +0 -15
  60. data/examples/complex-spec-app/docs/api/curl/api/v1/notes/list.sh +0 -9
  61. data/examples/complex-spec-app/docs/api/curl/api/v1/orders/cancel.sh +0 -9
  62. data/examples/complex-spec-app/docs/api/curl/api/v1/orders/create.sh +0 -29
  63. data/examples/complex-spec-app/docs/api/curl/api/v1/orders/destroy.sh +0 -9
  64. data/examples/complex-spec-app/docs/api/curl/api/v1/orders/list.sh +0 -9
  65. data/examples/complex-spec-app/docs/api/curl/api/v1/orders/show.sh +0 -9
  66. data/examples/complex-spec-app/docs/api/curl/api/v1/products/list.sh +0 -8
  67. data/examples/complex-spec-app/docs/api/curl/api/v1/products/search.sh +0 -8
  68. data/examples/complex-spec-app/docs/api/curl/api/v1/products/show.sh +0 -8
  69. data/examples/complex-spec-app/docs/api/curl/api/v1/profiles/show.sh +0 -9
  70. data/examples/complex-spec-app/docs/api/curl/api/v1/profiles/update.sh +0 -16
  71. data/examples/complex-spec-app/docs/api/curl/api/v2/products/list.sh +0 -8
  72. data/examples/complex-spec-app/docs/api/dataset.json +0 -5116
  73. data/examples/complex-spec-app/docs/api/postman/collection.json +0 -2519
  74. data/examples/complex-spec-app/generate.rb +0 -14
  75. data/examples/complex-spec-app/reqcord.yml +0 -28
  76. data/examples/complex-spec-app/spec/requests/admin_spec.rb +0 -67
  77. data/examples/complex-spec-app/spec/requests/cart_spec.rb +0 -61
  78. data/examples/complex-spec-app/spec/requests/home_and_auth_spec.rb +0 -49
  79. data/examples/complex-spec-app/spec/requests/orders_spec.rb +0 -124
  80. data/examples/complex-spec-app/spec/requests/products_spec.rb +0 -65
  81. data/examples/complex-spec-app/spec/spec_helper.rb +0 -17
  82. data/examples/complex-test-app/README.md +0 -40
  83. data/examples/complex-test-app/app.rb +0 -384
  84. data/examples/complex-test-app/docs/api/README.md +0 -104
  85. data/examples/complex-test-app/docs/api/api/v1/admin/products/create.md +0 -117
  86. data/examples/complex-test-app/docs/api/api/v1/admin/products/destroy.md +0 -51
  87. data/examples/complex-test-app/docs/api/api/v1/admin/products/index.md +0 -11
  88. data/examples/complex-test-app/docs/api/api/v1/admin/products/list.md +0 -106
  89. data/examples/complex-test-app/docs/api/api/v1/auth/index.md +0 -9
  90. data/examples/complex-test-app/docs/api/api/v1/auth/login.md +0 -76
  91. data/examples/complex-test-app/docs/api/api/v1/cart-items/create.md +0 -91
  92. data/examples/complex-test-app/docs/api/api/v1/cart-items/destroy.md +0 -51
  93. data/examples/complex-test-app/docs/api/api/v1/cart-items/index.md +0 -10
  94. data/examples/complex-test-app/docs/api/api/v1/carts/checkout.md +0 -102
  95. data/examples/complex-test-app/docs/api/api/v1/carts/index.md +0 -10
  96. data/examples/complex-test-app/docs/api/api/v1/carts/show.md +0 -73
  97. data/examples/complex-test-app/docs/api/api/v1/home/index.md +0 -9
  98. data/examples/complex-test-app/docs/api/api/v1/home/list.md +0 -47
  99. data/examples/complex-test-app/docs/api/api/v1/notes/create.md +0 -94
  100. data/examples/complex-test-app/docs/api/api/v1/notes/index.md +0 -10
  101. data/examples/complex-test-app/docs/api/api/v1/notes/list.md +0 -61
  102. data/examples/complex-test-app/docs/api/api/v1/orders/cancel.md +0 -69
  103. data/examples/complex-test-app/docs/api/api/v1/orders/create.md +0 -150
  104. data/examples/complex-test-app/docs/api/api/v1/orders/destroy.md +0 -37
  105. data/examples/complex-test-app/docs/api/api/v1/orders/index.md +0 -13
  106. data/examples/complex-test-app/docs/api/api/v1/orders/list.md +0 -71
  107. data/examples/complex-test-app/docs/api/api/v1/orders/show.md +0 -83
  108. data/examples/complex-test-app/docs/api/api/v1/products/index.md +0 -11
  109. data/examples/complex-test-app/docs/api/api/v1/products/list.md +0 -83
  110. data/examples/complex-test-app/docs/api/api/v1/products/search.md +0 -88
  111. data/examples/complex-test-app/docs/api/api/v1/products/show.md +0 -71
  112. data/examples/complex-test-app/docs/api/api/v1/profiles/index.md +0 -10
  113. data/examples/complex-test-app/docs/api/api/v1/profiles/show.md +0 -61
  114. data/examples/complex-test-app/docs/api/api/v1/profiles/update.md +0 -91
  115. data/examples/complex-test-app/docs/api/api/v2/products/index.md +0 -9
  116. data/examples/complex-test-app/docs/api/api/v2/products/list.md +0 -77
  117. data/examples/complex-test-app/docs/api/curl/api/v1/admin/products/create.sh +0 -18
  118. data/examples/complex-test-app/docs/api/curl/api/v1/admin/products/destroy.sh +0 -9
  119. data/examples/complex-test-app/docs/api/curl/api/v1/admin/products/list.sh +0 -9
  120. data/examples/complex-test-app/docs/api/curl/api/v1/auth/login.sh +0 -9
  121. data/examples/complex-test-app/docs/api/curl/api/v1/cart-items/create.sh +0 -16
  122. data/examples/complex-test-app/docs/api/curl/api/v1/cart-items/destroy.sh +0 -9
  123. data/examples/complex-test-app/docs/api/curl/api/v1/carts/checkout.sh +0 -13
  124. data/examples/complex-test-app/docs/api/curl/api/v1/carts/show.sh +0 -9
  125. data/examples/complex-test-app/docs/api/curl/api/v1/home/list.sh +0 -8
  126. data/examples/complex-test-app/docs/api/curl/api/v1/notes/create.sh +0 -15
  127. data/examples/complex-test-app/docs/api/curl/api/v1/notes/list.sh +0 -9
  128. data/examples/complex-test-app/docs/api/curl/api/v1/orders/cancel.sh +0 -9
  129. data/examples/complex-test-app/docs/api/curl/api/v1/orders/create.sh +0 -29
  130. data/examples/complex-test-app/docs/api/curl/api/v1/orders/destroy.sh +0 -9
  131. data/examples/complex-test-app/docs/api/curl/api/v1/orders/list.sh +0 -9
  132. data/examples/complex-test-app/docs/api/curl/api/v1/orders/show.sh +0 -9
  133. data/examples/complex-test-app/docs/api/curl/api/v1/products/list.sh +0 -8
  134. data/examples/complex-test-app/docs/api/curl/api/v1/products/search.sh +0 -8
  135. data/examples/complex-test-app/docs/api/curl/api/v1/products/show.sh +0 -8
  136. data/examples/complex-test-app/docs/api/curl/api/v1/profiles/show.sh +0 -9
  137. data/examples/complex-test-app/docs/api/curl/api/v1/profiles/update.sh +0 -16
  138. data/examples/complex-test-app/docs/api/curl/api/v2/products/list.sh +0 -8
  139. data/examples/complex-test-app/docs/api/dataset.json +0 -5109
  140. data/examples/complex-test-app/docs/api/postman/collection.json +0 -2568
  141. data/examples/complex-test-app/generate.rb +0 -14
  142. data/examples/complex-test-app/reqcord.yml +0 -28
  143. data/examples/complex-test-app/test/integration/admin_test.rb +0 -70
  144. data/examples/complex-test-app/test/integration/cart_test.rb +0 -64
  145. data/examples/complex-test-app/test/integration/home_and_auth_test.rb +0 -53
  146. data/examples/complex-test-app/test/integration/orders_test.rb +0 -127
  147. data/examples/complex-test-app/test/integration/products_test.rb +0 -66
  148. data/examples/spec-app/.rspec +0 -1
  149. data/examples/spec-app/README.md +0 -36
  150. data/examples/spec-app/app.rb +0 -209
  151. data/examples/spec-app/docs/api/README.md +0 -45
  152. data/examples/spec-app/docs/api/api/v1/customers/create.md +0 -95
  153. data/examples/spec-app/docs/api/api/v1/customers/index.md +0 -11
  154. data/examples/spec-app/docs/api/api/v1/customers/list.md +0 -67
  155. data/examples/spec-app/docs/api/api/v1/customers/show.md +0 -65
  156. data/examples/spec-app/docs/api/api/v1/tasks/complete.md +0 -51
  157. data/examples/spec-app/docs/api/api/v1/tasks/create.md +0 -90
  158. data/examples/spec-app/docs/api/api/v1/tasks/destroy.md +0 -35
  159. data/examples/spec-app/docs/api/api/v1/tasks/index.md +0 -14
  160. data/examples/spec-app/docs/api/api/v1/tasks/list.md +0 -65
  161. data/examples/spec-app/docs/api/api/v1/tasks/show.md +0 -65
  162. data/examples/spec-app/docs/api/api/v1/tasks/update.md +0 -74
  163. data/examples/spec-app/docs/api/api/v1/users/create.md +0 -95
  164. data/examples/spec-app/docs/api/api/v1/users/index.md +0 -11
  165. data/examples/spec-app/docs/api/api/v1/users/list.md +0 -67
  166. data/examples/spec-app/docs/api/api/v1/users/show.md +0 -65
  167. data/examples/spec-app/docs/api/curl/api/v1/customers/create.sh +0 -17
  168. data/examples/spec-app/docs/api/curl/api/v1/customers/list.sh +0 -9
  169. data/examples/spec-app/docs/api/curl/api/v1/customers/show.sh +0 -9
  170. data/examples/spec-app/docs/api/curl/api/v1/tasks/complete.sh +0 -8
  171. data/examples/spec-app/docs/api/curl/api/v1/tasks/create.sh +0 -15
  172. data/examples/spec-app/docs/api/curl/api/v1/tasks/destroy.sh +0 -8
  173. data/examples/spec-app/docs/api/curl/api/v1/tasks/list.sh +0 -8
  174. data/examples/spec-app/docs/api/curl/api/v1/tasks/show.sh +0 -8
  175. data/examples/spec-app/docs/api/curl/api/v1/tasks/update.sh +0 -14
  176. data/examples/spec-app/docs/api/curl/api/v1/users/create.sh +0 -17
  177. data/examples/spec-app/docs/api/curl/api/v1/users/list.sh +0 -9
  178. data/examples/spec-app/docs/api/curl/api/v1/users/show.sh +0 -9
  179. data/examples/spec-app/docs/api/dataset.json +0 -2402
  180. data/examples/spec-app/docs/api/postman/collection.json +0 -1290
  181. data/examples/spec-app/generate.rb +0 -14
  182. data/examples/spec-app/reqcord.yml +0 -27
  183. data/examples/spec-app/spec/requests/customers_spec.rb +0 -71
  184. data/examples/spec-app/spec/requests/tasks_spec.rb +0 -78
  185. data/examples/spec-app/spec/requests/users_spec.rb +0 -68
  186. data/examples/spec-app/spec/spec_helper.rb +0 -17
  187. data/examples/test-app/README.md +0 -57
  188. data/examples/test-app/app.rb +0 -212
  189. data/examples/test-app/docs/api/README.md +0 -45
  190. data/examples/test-app/docs/api/api/v1/customers/create.md +0 -95
  191. data/examples/test-app/docs/api/api/v1/customers/index.md +0 -11
  192. data/examples/test-app/docs/api/api/v1/customers/list.md +0 -67
  193. data/examples/test-app/docs/api/api/v1/customers/show.md +0 -65
  194. data/examples/test-app/docs/api/api/v1/tasks/complete.md +0 -51
  195. data/examples/test-app/docs/api/api/v1/tasks/create.md +0 -90
  196. data/examples/test-app/docs/api/api/v1/tasks/destroy.md +0 -35
  197. data/examples/test-app/docs/api/api/v1/tasks/index.md +0 -14
  198. data/examples/test-app/docs/api/api/v1/tasks/list.md +0 -65
  199. data/examples/test-app/docs/api/api/v1/tasks/show.md +0 -65
  200. data/examples/test-app/docs/api/api/v1/tasks/update.md +0 -74
  201. data/examples/test-app/docs/api/api/v1/users/create.md +0 -95
  202. data/examples/test-app/docs/api/api/v1/users/index.md +0 -11
  203. data/examples/test-app/docs/api/api/v1/users/list.md +0 -67
  204. data/examples/test-app/docs/api/api/v1/users/show.md +0 -65
  205. data/examples/test-app/docs/api/curl/api/v1/customers/create.sh +0 -17
  206. data/examples/test-app/docs/api/curl/api/v1/customers/list.sh +0 -9
  207. data/examples/test-app/docs/api/curl/api/v1/customers/show.sh +0 -9
  208. data/examples/test-app/docs/api/curl/api/v1/tasks/complete.sh +0 -8
  209. data/examples/test-app/docs/api/curl/api/v1/tasks/create.sh +0 -15
  210. data/examples/test-app/docs/api/curl/api/v1/tasks/destroy.sh +0 -8
  211. data/examples/test-app/docs/api/curl/api/v1/tasks/list.sh +0 -8
  212. data/examples/test-app/docs/api/curl/api/v1/tasks/show.sh +0 -8
  213. data/examples/test-app/docs/api/curl/api/v1/tasks/update.sh +0 -14
  214. data/examples/test-app/docs/api/curl/api/v1/users/create.sh +0 -17
  215. data/examples/test-app/docs/api/curl/api/v1/users/list.sh +0 -9
  216. data/examples/test-app/docs/api/curl/api/v1/users/show.sh +0 -9
  217. data/examples/test-app/docs/api/dataset.json +0 -2402
  218. data/examples/test-app/docs/api/postman/collection.json +0 -1290
  219. data/examples/test-app/generate.rb +0 -14
  220. data/examples/test-app/reqcord.yml +0 -28
  221. data/examples/test-app/test/integration/customers_test.rb +0 -72
  222. data/examples/test-app/test/integration/tasks_test.rb +0 -80
  223. data/examples/test-app/test/integration/users_test.rb +0 -72
@@ -1,384 +0,0 @@
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
@@ -1,104 +0,0 @@
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 |
@@ -1,117 +0,0 @@
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)
@@ -1,51 +0,0 @@
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)
@@ -1,11 +0,0 @@
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 |