Files
medusa-store/docs-util/oas-output/base/store.oas.base.yaml
Shahed Nasser 374a3f4dab docs-util: support generating OAS in docblock generator (#6338)
## What

This PR adds support for generating OAS in the docblock generator tool.

## How

As OAS are generated in a different manner/location than regular TSDocs, it requires a new type of generator within the tool. As such, the existing docblock generator now only handles files that aren't under the `packages/medusa/src/api` and `packages/medusa/src/api-v2` directories. The new generator handles files under these directories. However, it only considers a node to be an API route if it's a function having two parameters of types `MedusaRequest` and `MedusaResponse` respectively. So, only new API Routes are considered.

The new generator runs the same way as the existing docblock generator with the same method. The generators will detect whether they can run on the file or not and the docblocks/oas are generated based on that. I've also added a `--type` option to the CLI commands of the docblock generator tool to further filter and choose which generator to use.

When the OAS generator finds an API route, it will generate its OAS under the `docs-util/oas-output/operations` directory in a TypeScript file. I chose to generate in TS files rather than YAML files to maintain the functionality of `medusa-oas` without major changes.

Schemas detected in the OAS operation, such as the request and response schemas, are generated as OAS schemas under the `docs-util/oas-output/schemas` directory and referenced in operations and other resources.

The OAS generator also handles updating OAS. When you run the same command on a file/directory and an API route already has OAS associated with it, its information and associated schemas are updated instead of generating new schemas/operations. However, summaries and descriptions aren't updated unless they're not available or their values are the default value SUMMARY.

## API Route Handling

### Request and Response Types

The tool extracts the type of request/response schemas from the type arguments passed to the `MedusaRequest` and `MedusaResponse` respectively. For example:

```ts
export const POST = async (
  req: MedusaRequest<{
    id: string
  }>,
  res: MedusaResponse<ResponseType>
) => {
  // ...
}
```

If these types aren't provided, the request/response is considered empty.

### Path Parameters

Path parameters are extracted from the file's path name. For example, for `packages/medusa/src/api-v2/admin/campaigns/[id]/route.ts` the `id` path parameter is extracted.

### Query Parameters

The tool extracts the query parameters of an API route based on the type of `request.validatedQuery`. Once we narrow down how we're typing query parameters, we can revisit this implementation.

## Changes to Medusa Oas CLI

I added a `--v2` option to the Medusa OAS CLI to support loading OAS from `docs-util/oas-output` directory rather than the `medusa` package. This will output the OAS in `www/apps/api-reference/specs`, wiping out old OAS. This is only helpful for testing purposes to check how the new OAS looks like in the API reference. It also allows us to slowly start adapting the new OAS.

## Other Notes and Changes

- I've added a GitHub action that creates a PR for generated OAS when Version Packages is merged (similar to regular TSDocs). However, this will only generate the OAS in the `docs-util/oas-output` directory and will not affect the existing OAS in the API reference. Once we're ready to include it those OAS, we can talk about next steps.
- I've moved the base YAML from the `medusa` package to the `docs-util/oas-output/base` directory and changed the `medusa-oas` tool to load them from there.
- I added a `clean:oas` command to the docblock generator CLI tool that removes unused OAS operations, schemas, and tags from `docs-util/oas-output`. The tool also supports updating OAS operations and their associated schemas. However, I didn't add a specific mechanism to update schemas on their own as that's a bit tricky and would require the help of typedoc. I believe with the process of running the tool on the `api-v2` directory whenever there's a new release should be enough to update associated schemas, but if we find that not enough, we can revisit updating schemas individually.
- Because of the `clean:oas` command which makes changes to tags (removing the existing ones, more details on this one later), I've added new base YAML under `docs-util/oas-output/base-v2`. This is used by the tool when generating/cleaning OAS, and the Medusa OAS CLI when the `--v2` option is used.

## Testing

### Prerequisites

To test with request/response types, I recommend minimally modifying `packages/medusa/src/types/routing.ts` to allow type arguments of `MedusaRequest` and `MedusaResponse`:

```ts
import type { NextFunction, Request, Response } from "express"

import type { Customer, User } from "../models"
import type { MedusaContainer } from "./global"

export interface MedusaRequest<T = unknown> extends Request {
  user?: (User | Customer) & { customer_id?: string; userId?: string }
  scope: MedusaContainer
}

export type MedusaResponse<T = unknown> = Response

export type MedusaNextFunction = NextFunction

export type MedusaRequestHandler = (
  req: MedusaRequest,
  res: MedusaResponse,
  next: MedusaNextFunction
) => Promise<void> | void
```

You can then add type arguments to the routes in `packages/medusa/src/api-v2/admin/campaigns/[id]/route.ts`. For example:

```ts
import {
  deleteCampaignsWorkflow,
  updateCampaignsWorkflow,
} from "@medusajs/core-flows"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { CampaignDTO, IPromotionModuleService } from "@medusajs/types"
import { MedusaRequest, MedusaResponse } from "../../../../types/routing"

interface ResponseType {
  campaign: CampaignDTO
}

export const GET = async (
  req: MedusaRequest,
  res: MedusaResponse<ResponseType>
) => {
  const promotionModuleService: IPromotionModuleService = req.scope.resolve(
    ModuleRegistrationName.PROMOTION
  )
  const campaign = await promotionModuleService.retrieveCampaign(
    req.params.id,
    {
      select: req.retrieveConfig.select,
      relations: req.retrieveConfig.relations,
    }
  )
  res.status(200).json({ campaign })
}

export const POST = async (
  req: MedusaRequest<{
    id: string
  }>,
  res: MedusaResponse<ResponseType>
) => {
  const updateCampaigns = updateCampaignsWorkflow(req.scope)
  const campaignsData = [
    {
      id: req.params.id,
      ...(req.validatedBody || {}),
    },
  ]
  const { result, errors } = await updateCampaigns.run({
    input: { campaignsData },
    throwOnError: false,
  })
  if (Array.isArray(errors) && errors[0]) {
    throw errors[0].error
  }
  res.status(200).json({ campaign: result[0] })
}

export const DELETE = async (
  req: MedusaRequest,
  res: MedusaResponse<{
    id: string
    object: string
    deleted: boolean
  }>
) => {
  const id = req.params.id
  const manager = req.scope.resolve("manager")
  const deleteCampaigns = deleteCampaignsWorkflow(req.scope)
  const { errors } = await deleteCampaigns.run({
    input: { ids: [id] },
    context: { manager },
    throwOnError: false,
  })
  if (Array.isArray(errors) && errors[0]) {
    throw errors[0].error
  }
  res.status(200).json({
    id,
    object: "campaign",
    deleted: true,
  })
}
```

### Generate OAS

- Install dependencies in the `docs-util` directory
- Run the following command in the `docs-util/packages/docblock-generator` directory:

```bash
yarn dev run "../../../packages/medusa/src/api-v2/admin/campaigns/[id]/route.ts"
```

This will generate the OAS operation and schemas and necessary and update the base YAML to include the new tags.

### Generate OAS with Examples

By default, the tool will only generate cURL examples for OAS operations. To generate templated JS Client and (placeholder) Medusa React examples, add the `--generate-examples` option to the command:

```bash
yarn dev run "../../../packages/medusa/src/api-v2/admin/campaigns/[id]/route.ts" --generate-examples
```

> Note: the command will update the existing OAS you generated in the previous test.

### Testing Updates

To test updating OAS, you can try updating request/response types, then running the command, and the associated OAS/schemas will be updated.

### Clean OAS

The `clean:oas` command will remove any unused operation, tags, or schemas. To test it out you can try:

- Remove an API Route => this removes its associated operation and schemas (if not referenced anywhere else).
- Remove all references to a schema => this removes the schema.
- Remove all operations in `docs-util/oas-output/operations` associated with a tag => this removes the tag from the base YAML.

```bash
yarn dev clean:oas
```

> Note: when running this command, existing tags in the base YAML (such as Products) will be removed since there are no operations using it. As it's running on the base YAML under `base-v2`, this doesn't affect base YAML used for the API reference.

### Medusa Oas CLI

- Install and build dependencies in the root of the monorepo
- Run the following command to generate reference OAS for v2 API Routes (must have generated OAS previously using the docblock generator tool):

```bash
yarn openapi:generate --v2
```

- This wipes out existing OAS in `www/apps/api-reference/specs` and replaces them with the new ones. At this point, you can view the new API routes in the API reference by running the `yarn dev` command in `www/apps/api-reference` (although not necessary for testing here).
- Run the command again without the `--v2` option:

```bash
yarn openapi:generate
```

The specs in `www/apps/api-reference/specs` are reverted back to the old routes.
2024-02-13 08:40:04 +00:00

287 lines
12 KiB
YAML

openapi: 3.0.0
info:
version: 1.0.0
title: Medusa Storefront API
license:
name: MIT
url: https://github.com/medusajs/medusa/blob/master/LICENSE
tags:
- name: Auth
description: |
Authentication API Routes allow you to manage a customer's session, such as login or log out.
You can send authenticated requests for a customer either using the Cookie header or using the JWT Token.
externalDocs:
description: How to implement customer profiles in your storefront
url: https://docs.medusajs.com/modules/customers/storefront/implement-customer-profiles
- name: Carts
description: |
A cart is a virtual shopping bag that customers can use to add items they want to purchase.
A cart is then used to checkout and place an order.
externalDocs:
description: How to implement cart functionality in your storefront
url: https://docs.medusajs.com/modules/carts-and-checkout/storefront/implement-cart
- name: Customers
description: |
A customer can register and manage their information such as addresses, orders, payment methods, and more.
externalDocs:
description: How to implement customer profiles in your storefront
url: https://docs.medusajs.com/modules/customers/storefront/implement-customer-profiles
- name: Gift Cards
description: |
Customers can use gift cards during checkout to deduct the gift card's balance from the checkout total.
The Gift Card API Routes allow retrieving a gift card's details by its code. A gift card can be applied to a cart using the Carts API Routes.
externalDocs:
description: How to use gift cards in a storefront
url: https://docs.medusajs.com/modules/gift-cards/storefront/use-gift-cards
- name: Orders
description: |
Orders are purchases made by customers, typically through a storefront.
Orders are placed and created using the Carts API Routes. The Orders API Routes allow retrieving and claiming orders.
externalDocs:
description: How to retrieve order details in a storefront
url: https://docs.medusajs.com/modules/orders/storefront/retrieve-order-details
- name: Order Edits
description: |
Order edits are changes made to items in an order such as adding, updating their quantity, or deleting them. Order edits are created by the admin.
A customer can review order edit requests created by an admin and confirm or decline them.
externalDocs:
description: How to handle order edits in a storefront
url: https://docs.medusajs.com/modules/orders/storefront/handle-order-edits
- name: Payment Collections
description: |
A payment collection is useful for managing additional payments, such as for Order Edits, or installment payments.
- name: Products
description: |
Products are saleable items in a store. This also includes [saleable gift cards](https://docs.medusajs.com/modules/gift-cards/storefront/use-gift-cards) in a store.
Using these API Routes, you can filter products by categories, collections, sales channels, and more.
externalDocs:
description: How to show products in a storefront
url: https://docs.medusajs.com/modules/products/storefront/show-products
- name: Product Variants
description: |
Product variants are the actual salable item in your store. Each variant is a combination of the different option values available on the product.
- name: Product Categories
description: |
Products can be categoriezed into categories. A product can be associated more than one category.
Using these API Routes, you can list or retrieve a category's details and products.
externalDocs:
description: How to use product categories in a storefront
url: https://docs.medusajs.com/modules/products/storefront/use-categories
- name: Product Collections
description: |
A product collection is used to organize products for different purposes such as marketing or discount purposes. For example, you can create a Summer Collection.
Using these API Routes, you can list or retrieve a collection's details and products.
- name: Product Tags
description: |
Product tags are string values that can be used to filter products by.
Products can have more than one tag, and products can share tags.
- name: Product Types
description: |
Product types are string values that can be used to filter products by.
Products can have more than one tag, and products can share types.
- name: Regions
description: |
Regions are different countries or geographical regions that the commerce store serves customers in.
Customers can choose what region they're in, which can be used to change the prices shown based on the region and its currency.
externalDocs:
description: How to use regions in a storefront
url: https://docs.medusajs.com/modules/regions-and-currencies/storefront/use-regions
- name: Returns
description: |
A return can be created by a customer to return items in an order.
externalDocs:
description: How to create a return in a storefront
url: https://docs.medusajs.com/modules/orders/storefront/create-return
- name: Return Reasons
description: |
Return reasons are key-value pairs that are used to specify why an order return is being created.
- name: Shipping Options
description: |
A shipping option is used to define the available shipping methods during checkout or when creating a return.
externalDocs:
description: Shipping Option architecture
url: https://docs.medusajs.com/modules/carts-and-checkout/shipping#shipping-option
- name: Swaps
description: |
A swap is created by a customer or an admin to exchange an item with a new one.
Creating a swap implicitely includes creating a return for the item being exchanged.
externalDocs:
description: How to create a swap in a storefront
url: https://docs.medusajs.com/modules/orders/storefront/create-swap
servers:
- url: http://localhost:9000
- url: https://api.medusa-commerce.com
paths: { }
components:
responses:
default_error:
description: Default Error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
example:
code: "unknown_error"
message: "An unknown error occurred."
type: "unknown_error"
invalid_state_error:
description: Invalid State Error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
example:
code: "unknown_error"
message: "The request conflicted with another request. You may retry the request with the provided Idempotency-Key."
type: "QueryRunnerAlreadyReleasedError"
invalid_request_error:
description: Invalid Request Error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
example:
code: "invalid_request_error"
message: "Discount with code TEST already exists."
type: "duplicate_error"
not_found_error:
description: Not Found Error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
example:
message: "Entity with id 1 was not found"
type: "not_found"
400_error:
description: Client Error or Multiple Errors
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/Error"
- $ref: "#/components/schemas/MultipleErrors"
examples:
not_allowed:
$ref: "#/components/examples/not_allowed_error"
invalid_data:
$ref: "#/components/examples/invalid_data_error"
MultipleErrors:
$ref: "#/components/examples/multiple_errors"
500_error:
description: Server Error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
examples:
database:
$ref: "#/components/examples/database_error"
unexpected_state:
$ref: "#/components/examples/unexpected_state_error"
invalid_argument:
$ref: "#/components/examples/invalid_argument_error"
default_error:
$ref: "#/components/examples/default_error"
unauthorized:
description: 'User is not authorized. Must log in first'
content:
text/plain:
schema:
type: string
default: Unauthorized
example: Unauthorized
incorrect_credentials:
description: 'User does not exist or incorrect credentials'
content:
text/plain:
schema:
type: string
default: Unauthorized
example: Unauthorized
examples:
not_allowed_error:
summary: Not Allowed Error
value:
message: "Discount must be set to dynamic"
type: "not_allowed"
invalid_data_error:
summary: Invalid Data Error
value:
message: "first_name must be a string"
type: "invalid_data"
multiple_errors:
summary: Multiple Errors
value:
message: "Provided request body contains errors. Please check the data and retry the request"
errors:
- message: "first_name must be a string"
type: "invalid_data"
- message: "Discount must be set to dynamic"
type: "not_allowed"
database_error:
summary: Database Error
value:
code: "api_error"
message: "An error occured while hashing password"
type: "database_error"
unexpected_state_error:
summary: Unexpected State Error
value:
message: "cart.total must be defined"
type: "unexpected_state"
invalid_argument_error:
summary: Invalid Argument Error
value:
message: "cart.total must be defined"
type: "unexpected_state"
default_error:
summary: Default Error
value:
code: "unknown_error"
message: "An unknown error occurred."
type: "unknown_error"
securitySchemes:
jwt_token:
type: http
x-displayName: JWT Token
scheme: bearer
cookie_auth:
type: apiKey
x-displayName: Cookie Session ID
in: cookie
name: connect.sid
description: |
Use a cookie session to send authenticated requests.
### How to Obtain the Cookie Session
If you're sending requests through a browser, using JS Client, or using tools like Postman, the cookie session should be automatically set when the customer is logged in.
If you're sending requests using cURL, you must set the Session ID in the cookie manually.
To do that, send a request to [authenticate the customer](#tag/Auth/operation/PostAuth) and pass the cURL option `-v`:
```bash
curl -v --location --request POST 'https://medusa-url.com/store/auth' \
--header 'Content-Type: application/json' \
--data-raw '{
"email": "user@example.com",
"password": "supersecret"
}'
```
The headers will be logged in the terminal as well as the response. You should find in the headers a Cookie header similar to this:
```bash
Set-Cookie: connect.sid=s%3A2Bu8BkaP9JUfHu9rG59G16Ma0QZf6Gj1.WT549XqX37PN8n0OecqnMCq798eLjZC5IT7yiDCBHPM;
```
Copy the value after `connect.sid` (without the `;` at the end) and pass it as a cookie in subsequent requests as the following:
```bash
curl --location --request GET 'https://medusa-url.com/store/customers/me/orders' \
--header 'Cookie: connect.sid={sid}'
```
Where `{sid}` is the value of `connect.sid` that you copied.