First commit

This commit is contained in:
Stéphane Tailland
2026-08-20 13:24:10 +02:00
parent 1275748655
commit 89add4914a
10 changed files with 1111 additions and 2 deletions
+2
View File
@@ -0,0 +1,2 @@
.DS_Store
*.swp
+27
View File
@@ -0,0 +1,27 @@
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: catalog-dev-api
namespace: flux-system
spec:
interval: 10m
sourceRef:
kind: GitRepository
name: catalog-api
path: ./dev/kong
prune: true
dependsOn:
- name: catalog-dev-api-gateway
postBuild:
substitute:
NAME: catalog-dev
NAMESPACE: catalog
CONTROL_PLANE_REF_NAME: catalog-dev-bkphl
BACKEND_HOST: classic-models-api.classic-models.svc.cluster.local
# PLACEHOLDER audience name — no Keycloak client for "catalog-api"
# exists yet, this will reject every real token until that client is
# created.
API_AUDIENCE: '["catalog-api"]'
substituteFrom:
- kind: ConfigMap
name: dataplane-template-defaults
+30
View File
@@ -0,0 +1,30 @@
# Per-API audience check — narrows on top of the domain-wide whitelist
# check already enforced globally (k8s-platform/dataplane-globalpolicy-template,
# scope: GlobalInControlPlane). Defense in depth: both instances run
# independently on every request (verified Kong behavior, see STATUS.md).
#
# PLACEHOLDER-in-spirit: no Keycloak client named "catalog-api" exists yet
# (only classic-models-api's own client/mapper does) — this will reject
# every real token until that client + its oidc-audience-mapper are created.
apiVersion: configuration.konghq.com/v1
kind: KongPlugin
metadata:
name: ${NAME}-catalog-api-openid-connect
namespace: ${NAMESPACE}
plugin: openid-connect
config:
issuer: ${OIDC_ISSUER}
# Whole YAML flow-sequence as one string var, same reason as
# JWT_AUDIENCE_WHITELIST in the global template — postBuild.substitute
# does plain text substitution, not list generation.
audience_required: ${API_AUDIENCE}
auth_methods:
- bearer
display_errors: false
# This Control Plane enforces a global tls_certificate_verify option
# (Konnect-side, not set by us) that rejects any plugin instance whose TLS
# verification is off — openid-connect defaults to ssl_verify: false,
# which silently broke the DataPlane's config sync entirely (verified via
# DataPlane pod logs: "ssl_verify cannot be disabled"). Explicit true is
# the correct fix anyway: verify the issuer's cert when fetching JWKS.
ssl_verify: true
+14
View File
@@ -0,0 +1,14 @@
# Explicit on purpose, unlike catalog-api's earlier (removed) layout —
# kong-kustomization.yaml (the Flux CR) now lives in this same directory,
# and Flux's path-without-a-kustomization.yaml auto-generation scans every
# yaml file present, indiscriminately. Without this explicit list, it would
# include kong-kustomization.yaml itself as a managed resource — the
# Kustomization would end up applying (and, with prune: true, potentially
# pruning) its own object. Listing resources explicitly avoids that.
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- service.yaml
- routes.yaml
- kongplugin.yaml
- pluginbindings.yaml
+21
View File
@@ -0,0 +1,21 @@
# Bound to the natively-authored KongService (../service.yaml), not to
# individual routes — all of catalog-api's routes share the same audience,
# same as tasks-api's pattern. Unlike the earlier HTTPRoute-derived
# KongService, this name is ours and stable across reconciles.
apiVersion: configuration.konghq.com/v1alpha1
kind: KongPluginBinding
metadata:
name: ${NAME}-catalog-api-openid-connect-binding
namespace: ${NAMESPACE}
spec:
controlPlaneRef:
type: konnectNamespacedRef
konnectNamespacedRef:
name: ${CONTROL_PLANE_REF_NAME}
pluginRef:
name: ${NAME}-catalog-api-openid-connect
targets:
serviceRef:
group: configuration.konghq.com
kind: KongService
name: ${NAME}-catalog-api
+285
View File
@@ -0,0 +1,285 @@
# One KongRoute per operation in ../openapi.yaml (14 total — same
# granularity as ~/dev/kong/kube/apis/tasks-api/routes.yaml: split by
# operation even when two share a path, so a future per-operation policy
# doesn't require restructuring). Regex paths use Kong's own `~` syntax
# directly — no auto-translation layer involved this time (that was
# specific to the HTTPRoute path we abandoned).
apiVersion: configuration.konghq.com/v1alpha1
kind: KongRoute
metadata:
name: ${NAME}-catalog-api-listproductlines
namespace: ${NAMESPACE}
spec:
serviceRef:
type: namespacedRef
namespacedRef:
name: ${NAME}-catalog-api
name: catalog-api_listproductlines
methods:
- GET
- OPTIONS
paths:
- /classic-models/api/v1/productlines/
strip_path: false
tags:
- catalog
---
apiVersion: configuration.konghq.com/v1alpha1
kind: KongRoute
metadata:
name: ${NAME}-catalog-api-createproductline
namespace: ${NAMESPACE}
spec:
serviceRef:
type: namespacedRef
namespacedRef:
name: ${NAME}-catalog-api
name: catalog-api_createproductline
methods:
- POST
- OPTIONS
paths:
- /classic-models/api/v1/productlines/
strip_path: false
tags:
- catalog
---
apiVersion: configuration.konghq.com/v1alpha1
kind: KongRoute
metadata:
name: ${NAME}-catalog-api-getproductline
namespace: ${NAMESPACE}
spec:
serviceRef:
type: namespacedRef
namespacedRef:
name: ${NAME}-catalog-api
name: catalog-api_getproductline
methods:
- GET
- OPTIONS
paths:
- ~/classic-models/api/v1/productlines/(?<productline>[^#?/]+)/$
strip_path: false
tags:
- catalog
---
apiVersion: configuration.konghq.com/v1alpha1
kind: KongRoute
metadata:
name: ${NAME}-catalog-api-updateproductline
namespace: ${NAMESPACE}
spec:
serviceRef:
type: namespacedRef
namespacedRef:
name: ${NAME}-catalog-api
name: catalog-api_updateproductline
methods:
- PUT
- OPTIONS
paths:
- ~/classic-models/api/v1/productlines/(?<productline>[^#?/]+)/$
strip_path: false
tags:
- catalog
---
apiVersion: configuration.konghq.com/v1alpha1
kind: KongRoute
metadata:
name: ${NAME}-catalog-api-patchproductline
namespace: ${NAMESPACE}
spec:
serviceRef:
type: namespacedRef
namespacedRef:
name: ${NAME}-catalog-api
name: catalog-api_patchproductline
methods:
- PATCH
- OPTIONS
paths:
- ~/classic-models/api/v1/productlines/(?<productline>[^#?/]+)/$
strip_path: false
tags:
- catalog
---
apiVersion: configuration.konghq.com/v1alpha1
kind: KongRoute
metadata:
name: ${NAME}-catalog-api-deleteproductline
namespace: ${NAMESPACE}
spec:
serviceRef:
type: namespacedRef
namespacedRef:
name: ${NAME}-catalog-api
name: catalog-api_deleteproductline
methods:
- DELETE
- OPTIONS
paths:
- ~/classic-models/api/v1/productlines/(?<productline>[^#?/]+)/$
strip_path: false
tags:
- catalog
---
apiVersion: configuration.konghq.com/v1alpha1
kind: KongRoute
metadata:
name: ${NAME}-catalog-api-listproductlineproducts
namespace: ${NAMESPACE}
spec:
serviceRef:
type: namespacedRef
namespacedRef:
name: ${NAME}-catalog-api
name: catalog-api_listproductlineproducts
methods:
- GET
- OPTIONS
paths:
- ~/classic-models/api/v1/productlines/(?<productline>[^#?/]+)/products/$
strip_path: false
tags:
- catalog
---
apiVersion: configuration.konghq.com/v1alpha1
kind: KongRoute
metadata:
name: ${NAME}-catalog-api-listproducts
namespace: ${NAMESPACE}
spec:
serviceRef:
type: namespacedRef
namespacedRef:
name: ${NAME}-catalog-api
name: catalog-api_listproducts
methods:
- GET
- OPTIONS
paths:
- /classic-models/api/v1/products/
strip_path: false
tags:
- catalog
---
apiVersion: configuration.konghq.com/v1alpha1
kind: KongRoute
metadata:
name: ${NAME}-catalog-api-createproduct
namespace: ${NAMESPACE}
spec:
serviceRef:
type: namespacedRef
namespacedRef:
name: ${NAME}-catalog-api
name: catalog-api_createproduct
methods:
- POST
- OPTIONS
paths:
- /classic-models/api/v1/products/
strip_path: false
tags:
- catalog
---
apiVersion: configuration.konghq.com/v1alpha1
kind: KongRoute
metadata:
name: ${NAME}-catalog-api-getproduct
namespace: ${NAMESPACE}
spec:
serviceRef:
type: namespacedRef
namespacedRef:
name: ${NAME}-catalog-api
name: catalog-api_getproduct
methods:
- GET
- OPTIONS
paths:
- ~/classic-models/api/v1/products/(?<productcode>[^#?/]+)/$
strip_path: false
tags:
- catalog
---
apiVersion: configuration.konghq.com/v1alpha1
kind: KongRoute
metadata:
name: ${NAME}-catalog-api-updateproduct
namespace: ${NAMESPACE}
spec:
serviceRef:
type: namespacedRef
namespacedRef:
name: ${NAME}-catalog-api
name: catalog-api_updateproduct
methods:
- PUT
- OPTIONS
paths:
- ~/classic-models/api/v1/products/(?<productcode>[^#?/]+)/$
strip_path: false
tags:
- catalog
---
apiVersion: configuration.konghq.com/v1alpha1
kind: KongRoute
metadata:
name: ${NAME}-catalog-api-patchproduct
namespace: ${NAMESPACE}
spec:
serviceRef:
type: namespacedRef
namespacedRef:
name: ${NAME}-catalog-api
name: catalog-api_patchproduct
methods:
- PATCH
- OPTIONS
paths:
- ~/classic-models/api/v1/products/(?<productcode>[^#?/]+)/$
strip_path: false
tags:
- catalog
---
apiVersion: configuration.konghq.com/v1alpha1
kind: KongRoute
metadata:
name: ${NAME}-catalog-api-deleteproduct
namespace: ${NAMESPACE}
spec:
serviceRef:
type: namespacedRef
namespacedRef:
name: ${NAME}-catalog-api
name: catalog-api_deleteproduct
methods:
- DELETE
- OPTIONS
paths:
- ~/classic-models/api/v1/products/(?<productcode>[^#?/]+)/$
strip_path: false
tags:
- catalog
---
apiVersion: configuration.konghq.com/v1alpha1
kind: KongRoute
metadata:
name: ${NAME}-catalog-api-getproductorderdetails
namespace: ${NAMESPACE}
spec:
serviceRef:
type: namespacedRef
namespacedRef:
name: ${NAME}-catalog-api
name: catalog-api_getproductorderdetails
methods:
- GET
- OPTIONS
paths:
- ~/classic-models/api/v1/products/(?<productcode>[^#?/]+)/order-details/$
strip_path: false
tags:
- catalog
+25
View File
@@ -0,0 +1,25 @@
# host is a plain DNS string, not a Kubernetes object reference — unlike
# HTTPRoute's backendRefs, this needs no ReferenceGrant to cross into the
# classic-models namespace.
#
# metadata.name carries the ${NAME} (env) prefix because this domain's
# environments share one k8s namespace (see dataplane-catalog's own
# NAME/NAMESPACE split) — spec.name (Kong/Konnect-side) doesn't need it,
# each env already has its own isolated Control Plane.
apiVersion: configuration.konghq.com/v1alpha1
kind: KongService
metadata:
name: ${NAME}-catalog-api
namespace: ${NAMESPACE}
spec:
controlPlaneRef:
type: konnectNamespacedRef
konnectNamespacedRef:
name: ${CONTROL_PLANE_REF_NAME}
name: catalog-api
host: ${BACKEND_HOST}
port: 8000
protocol: http
path: /
tags:
- catalog
-1
View File
@@ -1 +0,0 @@
# TODO: HTTPRoute for catalog's resources
-1
View File
@@ -1 +0,0 @@
# TODO: KongPlugin(s) specific to catalog
+707
View File
@@ -0,0 +1,707 @@
openapi: 3.0.3
info:
title: Classic Models API — Catalog domain
version: 4.7.0
description: Catalog domain (productlines, products) subset of the shared classic-models-api backend. Kong exposes only these paths for the catalog domain's Control Plane.
paths:
/classic-models/api/v1/productlines/:
post:
operationId: create_product_line
description: Add a new product line category to the system.
summary: Create a new product line
tags:
- Product Lines
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/ProductLineRequest'
application/x-www-form-urlencoded:
schema:
$ref: '#/components/schemas/ProductLineRequest'
multipart/form-data:
schema:
$ref: '#/components/schemas/ProductLineRequest'
required: true
security:
- ApiKeyAuth: []
- JWTAuth: []
responses:
"201":
content:
application/json:
schema:
$ref: '#/components/schemas/ProductLine'
description: ""
get:
operationId: list_product_lines
description: Retrieve a paginated list of all product line categories in the system.
summary: List all product lines
parameters:
- name: page
required: false
in: query
description: A page number within the paginated result set.
schema:
type: integer
- name: page_size
required: false
in: query
description: Number of results to return per page.
schema:
type: integer
tags:
- Product Lines
security:
- ApiKeyAuth: []
- JWTAuth: []
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/PaginatedProductLineList'
description: ""
/classic-models/api/v1/productlines/{productline}/:
patch:
operationId: patch_product_line
description: Update specific fields of an existing product line.
summary: Partially update a product line
parameters:
- in: path
name: productline
schema:
type: string
description: A unique value identifying this product line.
required: true
tags:
- Product Lines
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/PatchedProductLineRequest'
application/x-www-form-urlencoded:
schema:
$ref: '#/components/schemas/PatchedProductLineRequest'
multipart/form-data:
schema:
$ref: '#/components/schemas/PatchedProductLineRequest'
security:
- ApiKeyAuth: []
- JWTAuth: []
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/ProductLine'
description: ""
get:
operationId: get_product_line
description: Retrieve detailed information about a specific product line by its identifier.
summary: Get a specific product line
parameters:
- in: path
name: productline
schema:
type: string
description: A unique value identifying this product line.
required: true
tags:
- Product Lines
security:
- ApiKeyAuth: []
- JWTAuth: []
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/ProductLine'
description: ""
put:
operationId: update_product_line
description: Update all fields of an existing product line.
summary: Update a product line
parameters:
- in: path
name: productline
schema:
type: string
description: A unique value identifying this product line.
required: true
tags:
- Product Lines
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/ProductLineRequest'
application/x-www-form-urlencoded:
schema:
$ref: '#/components/schemas/ProductLineRequest'
multipart/form-data:
schema:
$ref: '#/components/schemas/ProductLineRequest'
required: true
security:
- ApiKeyAuth: []
- JWTAuth: []
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/ProductLine'
description: ""
delete:
operationId: delete_product_line
description: Remove a product line from the system.
summary: Delete a product line
parameters:
- in: path
name: productline
schema:
type: string
description: A unique value identifying this product line.
required: true
tags:
- Product Lines
security:
- ApiKeyAuth: []
- JWTAuth: []
responses:
"204":
description: No response body
/classic-models/api/v1/productlines/{productline}/products/:
get:
operationId: get_product_line_products
description: Retrieve all products in a specific product line category. This endpoint allows browsing products by category.
summary: Get products by product line
parameters:
- name: page
required: false
in: query
description: A page number within the paginated result set.
schema:
type: integer
- name: page_size
required: false
in: query
description: Number of results to return per page.
schema:
type: integer
- in: path
name: productline
schema:
type: string
description: The product line identifier
required: true
tags:
- Product Lines
security:
- ApiKeyAuth: []
- JWTAuth: []
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/PaginatedProductList'
description: ""
/classic-models/api/v1/products/:
post:
operationId: create_product
description: Add a new product to the catalog with all required information.
summary: Create a new product
tags:
- Products
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/ProductRequest'
application/x-www-form-urlencoded:
schema:
$ref: '#/components/schemas/ProductRequest'
multipart/form-data:
schema:
$ref: '#/components/schemas/ProductRequest'
required: true
security:
- ApiKeyAuth: []
- JWTAuth: []
responses:
"201":
content:
application/json:
schema:
$ref: '#/components/schemas/Product'
description: ""
get:
operationId: list_products
description: Retrieve a paginated list of all products in the catalog with their details.
summary: List all products
parameters:
- name: page
required: false
in: query
description: A page number within the paginated result set.
schema:
type: integer
- name: page_size
required: false
in: query
description: Number of results to return per page.
schema:
type: integer
tags:
- Products
security:
- ApiKeyAuth: []
- JWTAuth: []
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/PaginatedProductList'
description: ""
/classic-models/api/v1/products/{productcode}/:
patch:
operationId: patch_product
description: Update specific fields of an existing product.
summary: Partially update a product
parameters:
- in: path
name: productcode
schema:
type: string
description: A unique value identifying this product.
required: true
tags:
- Products
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/PatchedProductRequest'
application/x-www-form-urlencoded:
schema:
$ref: '#/components/schemas/PatchedProductRequest'
multipart/form-data:
schema:
$ref: '#/components/schemas/PatchedProductRequest'
security:
- ApiKeyAuth: []
- JWTAuth: []
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/Product'
description: ""
get:
operationId: get_product
description: Retrieve detailed information about a specific product by its code.
summary: Get a specific product
parameters:
- in: path
name: productcode
schema:
type: string
description: A unique value identifying this product.
required: true
tags:
- Products
security:
- ApiKeyAuth: []
- JWTAuth: []
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/Product'
description: ""
put:
operationId: update_product
description: Update all fields of an existing product in the catalog.
summary: Update a product
parameters:
- in: path
name: productcode
schema:
type: string
description: A unique value identifying this product.
required: true
tags:
- Products
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/ProductRequest'
application/x-www-form-urlencoded:
schema:
$ref: '#/components/schemas/ProductRequest'
multipart/form-data:
schema:
$ref: '#/components/schemas/ProductRequest'
required: true
security:
- ApiKeyAuth: []
- JWTAuth: []
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/Product'
description: ""
delete:
operationId: delete_product
description: Remove a product from the catalog.
summary: Delete a product
parameters:
- in: path
name: productcode
schema:
type: string
description: A unique value identifying this product.
required: true
tags:
- Products
security:
- ApiKeyAuth: []
- JWTAuth: []
responses:
"204":
description: No response body
/classic-models/api/v1/products/{productcode}/order-details/:
get:
operationId: get_product_order_details
description: Retrieve all order details (sales) for a specific product. This endpoint provides the complete sales history for the product.
summary: Get product order history
parameters:
- name: page
required: false
in: query
description: A page number within the paginated result set.
schema:
type: integer
- name: page_size
required: false
in: query
description: Number of results to return per page.
schema:
type: integer
- in: path
name: productcode
schema:
type: string
description: The product code
required: true
tags:
- Products
security:
- ApiKeyAuth: []
- JWTAuth: []
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/PaginatedOrderdetailList'
description: ""
components:
schemas:
Orderdetail:
type: object
description: Order line items with product details
properties:
id:
type: integer
readOnly: true
quantityordered:
type: integer
maximum: 2147483647
minimum: -2147483648
priceeach:
type: string
format: decimal
pattern: ^-?\d{0,8}(?:\.\d{0,2})?$
orderlinenumber:
type: integer
maximum: 32767
minimum: -32768
ordernumber:
type: integer
maximum: 2147483647
minimum: -2147483648
productcode:
type: string
required:
- id
- orderlinenumber
- ordernumber
- priceeach
- productcode
- quantityordered
PaginatedOrderdetailList:
type: object
properties:
count:
type: integer
example: 123
next:
type: string
nullable: true
format: uri
example: http://api.example.org/accounts/?page=4
previous:
type: string
nullable: true
format: uri
example: http://api.example.org/accounts/?page=2
results:
type: array
items:
$ref: '#/components/schemas/Orderdetail'
PaginatedProductLineList:
type: object
properties:
count:
type: integer
example: 123
next:
type: string
nullable: true
format: uri
example: http://api.example.org/accounts/?page=4
previous:
type: string
nullable: true
format: uri
example: http://api.example.org/accounts/?page=2
results:
type: array
items:
$ref: '#/components/schemas/ProductLine'
PaginatedProductList:
type: object
properties:
count:
type: integer
example: 123
next:
type: string
nullable: true
format: uri
example: http://api.example.org/accounts/?page=4
previous:
type: string
nullable: true
format: uri
example: http://api.example.org/accounts/?page=2
results:
type: array
items:
$ref: '#/components/schemas/Product'
PatchedProductLineRequest:
type: object
description: Product line category information
properties:
productline:
type: string
minLength: 1
maxLength: 50
textdescription:
type: string
nullable: true
maxLength: 4000
htmldescription:
type: string
nullable: true
PatchedProductRequest:
type: object
description: Product catalog information
properties:
productcode:
type: string
minLength: 1
maxLength: 15
productname:
type: string
minLength: 1
maxLength: 70
productscale:
type: string
minLength: 1
maxLength: 10
productvendor:
type: string
minLength: 1
maxLength: 50
productdescription:
type: string
minLength: 1
quantityinstock:
type: integer
maximum: 32767
minimum: -32768
buyprice:
type: string
format: decimal
pattern: ^-?\d{0,8}(?:\.\d{0,2})?$
msrp:
type: string
format: decimal
pattern: ^-?\d{0,8}(?:\.\d{0,2})?$
productline:
type: string
minLength: 1
Product:
type: object
description: Product catalog information
properties:
productcode:
type: string
maxLength: 15
productname:
type: string
maxLength: 70
productscale:
type: string
maxLength: 10
productvendor:
type: string
maxLength: 50
productdescription:
type: string
quantityinstock:
type: integer
maximum: 32767
minimum: -32768
buyprice:
type: string
format: decimal
pattern: ^-?\d{0,8}(?:\.\d{0,2})?$
msrp:
type: string
format: decimal
pattern: ^-?\d{0,8}(?:\.\d{0,2})?$
productline:
type: string
required:
- buyprice
- msrp
- productcode
- productdescription
- productline
- productname
- productscale
- productvendor
- quantityinstock
ProductLine:
type: object
description: Product line category information
properties:
productline:
type: string
maxLength: 50
textdescription:
type: string
nullable: true
maxLength: 4000
htmldescription:
type: string
nullable: true
image:
type: string
format: byte
readOnly: true
nullable: true
required:
- image
- productline
ProductLineRequest:
type: object
description: Product line category information
properties:
productline:
type: string
minLength: 1
maxLength: 50
textdescription:
type: string
nullable: true
maxLength: 4000
htmldescription:
type: string
nullable: true
required:
- productline
ProductRequest:
type: object
description: Product catalog information
properties:
productcode:
type: string
minLength: 1
maxLength: 15
productname:
type: string
minLength: 1
maxLength: 70
productscale:
type: string
minLength: 1
maxLength: 10
productvendor:
type: string
minLength: 1
maxLength: 50
productdescription:
type: string
minLength: 1
quantityinstock:
type: integer
maximum: 32767
minimum: -32768
buyprice:
type: string
format: decimal
pattern: ^-?\d{0,8}(?:\.\d{0,2})?$
msrp:
type: string
format: decimal
pattern: ^-?\d{0,8}(?:\.\d{0,2})?$
productline:
type: string
minLength: 1
required:
- buyprice
- msrp
- productcode
- productdescription
- productline
- productname
- productscale
- productvendor
- quantityinstock
securitySchemes:
ApiKeyAuth:
type: apiKey
in: header
name: X-API-Key
description: API key authentication using X-API-Key header
JWTAuth:
type: http
scheme: bearer
bearerFormat: JWT
servers:
- url: http://localhost:8000
description: API Server