@nasebanal/cli 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -2
- package/dist/apis.js +1 -2
- package/dist/reports/locust.js +26 -7
- package/dist/reports/upload.js +57 -4
- package/package.json +1 -1
- package/specs/account.yaml +1 -1
- package/specs/app-template.yaml +0 -488
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ Contract-Driven Development (CDD) pipeline, alongside the web frontends.
|
|
|
10
10
|
|
|
11
11
|
```console
|
|
12
12
|
$ nb account me get
|
|
13
|
-
$ nb
|
|
13
|
+
$ nb record records list
|
|
14
14
|
$ nb target shares accept <id>
|
|
15
15
|
$ nb account me tokens create --data '{"name":"ci"}'
|
|
16
16
|
```
|
|
@@ -45,7 +45,17 @@ export NB_TOKEN=nbpat_… # a PAT or service-account token (no browser ne
|
|
|
45
45
|
|
|
46
46
|
# JUnit XML: vitest (--reporter=junit), pytest (--junitxml), Specmatic, Playwright (--reporter=junit)
|
|
47
47
|
nb assurance report upload --file report/junit.xml --kind unit --tool pytest
|
|
48
|
-
nb assurance report upload --file results/junit.xml --kind e2e --tool playwright --
|
|
48
|
+
nb assurance report upload --file results/junit.xml --kind e2e --tool playwright --component web
|
|
49
|
+
|
|
50
|
+
# Two scans of one tool on the same components (ZAP's baseline and full scan): a label tells them apart
|
|
51
|
+
nb assurance report upload --file report/baseline-junit.xml --kind security --tool zap --component frontend --label baseline
|
|
52
|
+
nb assurance report upload --file report/full-scan-junit.xml --kind security --tool zap --component frontend --label full-scan
|
|
53
|
+
|
|
54
|
+
# The tool's own version is informational and optional
|
|
55
|
+
nb assurance report upload --file report/junit.xml --kind unit --tool pytest --tool-version 9.1.0
|
|
56
|
+
|
|
57
|
+
# A result may be about several components: repeat the flag (new names are created in the project; none = `default`)
|
|
58
|
+
nb assurance report upload --file results/junit.xml --kind e2e --tool playwright --component frontend --component backend
|
|
49
59
|
|
|
50
60
|
# Specmatic: the JUnit XML, plus its HTML report for the API coverage (coverage_pct = operations exercised / operations in the spec)
|
|
51
61
|
nb assurance report upload --file specmatic/junit/TEST-junit-jupiter.xml --kind contract --tool specmatic \
|
package/dist/apis.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
export const API_CATALOG = [
|
|
2
2
|
{ name: "account", spec: "account.yaml", summary: "User profiles, subscriptions, billing, and API tokens" },
|
|
3
|
-
{ name: "
|
|
3
|
+
{ name: "record", spec: "recorder.yaml", summary: "Records and tags (NASEBANAL Recorder)" },
|
|
4
4
|
{ name: "target", spec: "target.yaml", summary: "Targets, cells, and shares (NASEBANAL Target)" },
|
|
5
5
|
{ name: "assurance", spec: "assurance.yaml", summary: "Monitors, projects and CI/CD test results (NASEBANAL Assurance)" },
|
|
6
|
-
{ name: "app-template", spec: "app-template.yaml", summary: "Reference API surface (App Template)" },
|
|
7
6
|
];
|
package/dist/reports/locust.js
CHANGED
|
@@ -7,6 +7,20 @@
|
|
|
7
7
|
* task exception, or a failure ratio above the allowed one, fails the run.
|
|
8
8
|
*/
|
|
9
9
|
import { toRecords } from "./csv.js";
|
|
10
|
+
/**
|
|
11
|
+
* The CSV's column names changed between Locust versions: 1.x wrote "# requests" / "# failures" / "Median response
|
|
12
|
+
* time" (and named the aggregate row "Total"), 2.x writes "Request Count" / "Failure Count" / "Median Response Time"
|
|
13
|
+
* ("Aggregated"). Each value is read under every name it has had, the first present one winning.
|
|
14
|
+
*/
|
|
15
|
+
const COLUMNS = {
|
|
16
|
+
requests: ["Request Count", "# requests"],
|
|
17
|
+
failures: ["Failure Count", "# failures"],
|
|
18
|
+
median: ["Median Response Time", "Median response time"],
|
|
19
|
+
average: ["Average Response Time", "Average response time"],
|
|
20
|
+
max: ["Max Response Time", "Max response time"],
|
|
21
|
+
};
|
|
22
|
+
const has = (row, names) => names.some((n) => n in row);
|
|
23
|
+
const read = (row, names) => num(row[names.find((n) => n in row) ?? ""]);
|
|
10
24
|
function num(value) {
|
|
11
25
|
if (value === undefined || value === "" || value === "N/A")
|
|
12
26
|
return null;
|
|
@@ -16,21 +30,26 @@ function num(value) {
|
|
|
16
30
|
const round = (n, digits = 2) => Math.round(n * 10 ** digits) / 10 ** digits;
|
|
17
31
|
export function summariseLocust(files, maxFailRatio) {
|
|
18
32
|
const stats = toRecords(files.stats);
|
|
19
|
-
|
|
33
|
+
// The row of all requests together: "Aggregated" (2.x) or "Total" (1.x).
|
|
34
|
+
const total = stats.find((r) => r.Name === "Aggregated" || r.Name === "Total");
|
|
20
35
|
if (!total)
|
|
21
|
-
throw new Error('No "Aggregated" row in the stats CSV - is this a Locust *_stats.csv file?');
|
|
22
|
-
|
|
23
|
-
|
|
36
|
+
throw new Error('No "Aggregated" (or "Total") row in the stats CSV - is this a Locust *_stats.csv file?');
|
|
37
|
+
// Without the counts the numbers below would silently be 0, a green result for a report nobody understood: refuse it.
|
|
38
|
+
if (!has(total, COLUMNS.requests) || !has(total, COLUMNS.failures)) {
|
|
39
|
+
throw new Error(`The stats CSV has no request / failure count column (looked for ${[...COLUMNS.requests, ...COLUMNS.failures].map((n) => `"${n}"`).join(", ")}; found: ${Object.keys(total).join(", ")}) - is it from a Locust version this nb does not know?`);
|
|
40
|
+
}
|
|
41
|
+
const requests = read(total, COLUMNS.requests) ?? 0;
|
|
42
|
+
const failed = read(total, COLUMNS.failures) ?? 0;
|
|
24
43
|
const metrics = { requests, failures: failed };
|
|
25
44
|
const put = (key, value) => {
|
|
26
45
|
if (value !== null)
|
|
27
46
|
metrics[key] = round(value);
|
|
28
47
|
};
|
|
29
|
-
put("avg_ms",
|
|
30
|
-
put("p50_ms", num(total["50%"]) ??
|
|
48
|
+
put("avg_ms", read(total, COLUMNS.average));
|
|
49
|
+
put("p50_ms", num(total["50%"]) ?? read(total, COLUMNS.median));
|
|
31
50
|
put("p95_ms", num(total["95%"]));
|
|
32
51
|
put("p99_ms", num(total["99%"]));
|
|
33
|
-
put("max_ms",
|
|
52
|
+
put("max_ms", read(total, COLUMNS.max));
|
|
34
53
|
put("rps", num(total["Requests/s"]));
|
|
35
54
|
const ratio = requests > 0 ? failed / requests : 0;
|
|
36
55
|
metrics.error_rate_pct = round(ratio * 100);
|
package/dist/reports/upload.js
CHANGED
|
@@ -127,7 +127,13 @@ function locustSibling(statsFile, suffix) {
|
|
|
127
127
|
}
|
|
128
128
|
export function parseReport(opts) {
|
|
129
129
|
const text = readFileSync(opts.file, "utf8");
|
|
130
|
-
const base = {
|
|
130
|
+
const base = {
|
|
131
|
+
kind: opts.kind,
|
|
132
|
+
tool: opts.tool,
|
|
133
|
+
...(opts.components && opts.components.length > 0 ? { components: opts.components } : {}),
|
|
134
|
+
...(opts.label ? { label: opts.label } : {}),
|
|
135
|
+
...(opts.toolVersion ? { tool_version: opts.toolVersion } : {}),
|
|
136
|
+
};
|
|
131
137
|
if (opts.format === "junit") {
|
|
132
138
|
const j = parseJunit(text);
|
|
133
139
|
const status = opts.status ?? (j.failed === 0 ? "passed" : "failed");
|
|
@@ -214,6 +220,49 @@ function statsFileIn(dir) {
|
|
|
214
220
|
fail(`No *_stats.csv file in ${dir}`);
|
|
215
221
|
return join(dir, files.sort()[files.length - 1]);
|
|
216
222
|
}
|
|
223
|
+
/** At most this many components per result (the API's limit) and this many characters in a name. */
|
|
224
|
+
const MAX_COMPONENTS = 20;
|
|
225
|
+
const MAX_COMPONENT_LENGTH = 100;
|
|
226
|
+
/** The `--label`: trimmed, not empty, within the API's limit. */
|
|
227
|
+
function checkLabel(raw) {
|
|
228
|
+
if (raw === undefined)
|
|
229
|
+
return undefined;
|
|
230
|
+
const label = raw.trim();
|
|
231
|
+
if (label === "")
|
|
232
|
+
fail("--label must not be empty");
|
|
233
|
+
if (label.length > MAX_COMPONENT_LENGTH)
|
|
234
|
+
fail(`--label must be ${MAX_COMPONENT_LENGTH} characters or fewer`);
|
|
235
|
+
return label;
|
|
236
|
+
}
|
|
237
|
+
/** The `--tool-version`: trimmed, not empty, within the API's limit. */
|
|
238
|
+
function checkToolVersion(raw) {
|
|
239
|
+
if (raw === undefined)
|
|
240
|
+
return undefined;
|
|
241
|
+
const version = raw.trim();
|
|
242
|
+
if (version === "")
|
|
243
|
+
fail("--tool-version must not be empty");
|
|
244
|
+
if (version.length > 50)
|
|
245
|
+
fail("--tool-version must be 50 characters or fewer");
|
|
246
|
+
return version;
|
|
247
|
+
}
|
|
248
|
+
/** The `--component` names: trimmed, no empty one, no two alike ignoring case, within the API's limits. */
|
|
249
|
+
function checkComponents(raw) {
|
|
250
|
+
if (raw === undefined)
|
|
251
|
+
return undefined;
|
|
252
|
+
const out = [];
|
|
253
|
+
for (const value of raw) {
|
|
254
|
+
const name = value.trim();
|
|
255
|
+
if (name === "")
|
|
256
|
+
fail("--component must not be empty");
|
|
257
|
+
if (name.length > MAX_COMPONENT_LENGTH)
|
|
258
|
+
fail(`--component must be ${MAX_COMPONENT_LENGTH} characters or fewer: ${name.slice(0, 20)}…`);
|
|
259
|
+
if (!out.some((o) => o.toLowerCase() === name.toLowerCase()))
|
|
260
|
+
out.push(name);
|
|
261
|
+
}
|
|
262
|
+
if (out.length > MAX_COMPONENTS)
|
|
263
|
+
fail(`At most ${MAX_COMPONENTS} --component values`);
|
|
264
|
+
return out;
|
|
265
|
+
}
|
|
217
266
|
function resolveFormat(opts, file) {
|
|
218
267
|
if (opts.format !== undefined) {
|
|
219
268
|
if (opts.format !== "junit" && opts.format !== "locust")
|
|
@@ -235,9 +284,11 @@ export function buildReportUploadCommand() {
|
|
|
235
284
|
.option("--format <format>", "junit | locust (default: from the file name)")
|
|
236
285
|
.option("--kind <kind>", `what the suite proves: ${SUITE_KINDS.join(" | ")} (locust: load)`)
|
|
237
286
|
.option("--tool <label>", "the producing tool, a free label, e.g. vitest, pytest, playwright, specmatic, zap, trivy (locust: locust)")
|
|
238
|
-
.option("--
|
|
287
|
+
.option("--component <name>", "a component the result is for (e.g. frontend, backend), repeatable when it covers several (e.g. an end-to-end test: --component frontend --component backend); new names are created in the project; leave it out for the component `default`", (v, all = []) => [...all, v])
|
|
288
|
+
.option("--label <name>", "tells apart results that are otherwise the same, such as two scans of one tool on the same components (e.g. --label baseline, --label full-scan); part of the result's identity, so the same label again replaces the result; optional")
|
|
289
|
+
.option("--tool-version <version>", "the version of the tool that produced the report (e.g. 9.1.0): informational, shown next to the tool, handy when a tool's new version changes its layout; optional")
|
|
239
290
|
.option("--metric <key=value>", "an extra number for the suite, repeatable (e.g. --metric high=0 --metric medium=3; for security: critical, high, medium, low, info)", (v, all = []) => [...all, v])
|
|
240
|
-
.option("--api-version <label>", "the API version the result is for (e.g. v1, v2), part of the
|
|
291
|
+
.option("--api-version <label>", "the API version the result is for (e.g. v1, v2), part of the result's identity next to its kind and components; leave it out for results that hold for every version, such as unit tests")
|
|
241
292
|
.option("--project <name>", "project name (CI: the repository); found, or created if missing")
|
|
242
293
|
.option("--repo <repo>", "repository recorded on a newly created project (CI: detected)")
|
|
243
294
|
.option("--run-key <key>", "identity of the pipeline run (CI: detected); all jobs of a run share it")
|
|
@@ -272,7 +323,9 @@ export function buildReportUploadCommand() {
|
|
|
272
323
|
format,
|
|
273
324
|
kind,
|
|
274
325
|
tool: checkedTool,
|
|
275
|
-
|
|
326
|
+
components: checkComponents(opts.component),
|
|
327
|
+
label: checkLabel(opts.label),
|
|
328
|
+
toolVersion: checkToolVersion(opts.toolVersion),
|
|
276
329
|
status: opts.status,
|
|
277
330
|
maxFailRatio,
|
|
278
331
|
file,
|
package/package.json
CHANGED
package/specs/account.yaml
CHANGED
|
@@ -2,7 +2,7 @@ openapi: 3.0.3
|
|
|
2
2
|
info:
|
|
3
3
|
title: NASEBANAL Account API
|
|
4
4
|
description: Centralized user and billing management API for NASEBANAL applications. Manages user profiles, per-service subscriptions, Stripe billing, and Personal Access Tokens (PAT).
|
|
5
|
-
version: 1.
|
|
5
|
+
version: 1.6.0
|
|
6
6
|
contact:
|
|
7
7
|
name: NASEBANAL
|
|
8
8
|
url: https://www.nasebanal.com
|
package/specs/app-template.yaml
DELETED
|
@@ -1,488 +0,0 @@
|
|
|
1
|
-
openapi: 3.0.3
|
|
2
|
-
info:
|
|
3
|
-
title: NASEBANAL - App Template API
|
|
4
|
-
description: |-
|
|
5
|
-
A template API for NASEBANAL applications. Provides sample CRUD endpoints, user authentication via Auth0 JWT and Personal Access Tokens (PAT), and integration with the Account API.
|
|
6
|
-
|
|
7
|
-
All responses use a uniform envelope: successful responses are `{ "success": true, "data": ... }` and failures are `{ "success": false, "error": { "code", "message" } }`. This mirrors `successResponse()` / `errorResponse()` in the implementation. The `success` discriminator is pinned with `enum` so both Specmatic stub generation and contract tests treat it as a fixed value per response class.
|
|
8
|
-
version: 1.1.1
|
|
9
|
-
contact:
|
|
10
|
-
name: NASEBANAL
|
|
11
|
-
url: https://www.nasebanal.com
|
|
12
|
-
servers:
|
|
13
|
-
- url: http://localhost:8789
|
|
14
|
-
description: Local dev (wrangler dev)
|
|
15
|
-
- url: https://api.app-template.nasebanal.com
|
|
16
|
-
description: Production
|
|
17
|
-
security:
|
|
18
|
-
- BearerAuth: []
|
|
19
|
-
paths:
|
|
20
|
-
/api/v1/health:
|
|
21
|
-
get:
|
|
22
|
-
operationId: getHealth
|
|
23
|
-
tags:
|
|
24
|
-
- Health
|
|
25
|
-
summary: Health check
|
|
26
|
-
description: Returns the health status of the API.
|
|
27
|
-
security: []
|
|
28
|
-
responses:
|
|
29
|
-
"200":
|
|
30
|
-
description: API is healthy
|
|
31
|
-
content:
|
|
32
|
-
application/json:
|
|
33
|
-
schema:
|
|
34
|
-
type: object
|
|
35
|
-
required:
|
|
36
|
-
- success
|
|
37
|
-
- data
|
|
38
|
-
properties:
|
|
39
|
-
success:
|
|
40
|
-
type: boolean
|
|
41
|
-
enum:
|
|
42
|
-
- true
|
|
43
|
-
data:
|
|
44
|
-
type: object
|
|
45
|
-
required:
|
|
46
|
-
- status
|
|
47
|
-
- timestamp
|
|
48
|
-
properties:
|
|
49
|
-
status:
|
|
50
|
-
type: string
|
|
51
|
-
timestamp:
|
|
52
|
-
type: string
|
|
53
|
-
format: date-time
|
|
54
|
-
examples:
|
|
55
|
-
valid:
|
|
56
|
-
value:
|
|
57
|
-
success: true
|
|
58
|
-
data:
|
|
59
|
-
status: ok
|
|
60
|
-
timestamp: 2026-05-14T12:00:00Z
|
|
61
|
-
/api/v1/me:
|
|
62
|
-
get:
|
|
63
|
-
operationId: getCurrentUser
|
|
64
|
-
tags:
|
|
65
|
-
- User
|
|
66
|
-
summary: Get current user profile
|
|
67
|
-
description: Returns the authenticated user's profile information from the Account API.
|
|
68
|
-
responses:
|
|
69
|
-
"200":
|
|
70
|
-
description: User profile
|
|
71
|
-
content:
|
|
72
|
-
application/json:
|
|
73
|
-
schema:
|
|
74
|
-
$ref: "#/components/schemas/UserResponse"
|
|
75
|
-
examples:
|
|
76
|
-
valid:
|
|
77
|
-
value:
|
|
78
|
-
success: true
|
|
79
|
-
data:
|
|
80
|
-
id: 42
|
|
81
|
-
auth0_sub: auth0|0123456789abcdef
|
|
82
|
-
email: user@example.com
|
|
83
|
-
name: Sample User
|
|
84
|
-
language: ja
|
|
85
|
-
plan: pro
|
|
86
|
-
subscription_status: active
|
|
87
|
-
subscription_expires_at: 2026-12-31T23:59:59Z
|
|
88
|
-
"401":
|
|
89
|
-
$ref: "#/components/responses/Unauthorized"
|
|
90
|
-
/api/v1/items:
|
|
91
|
-
get:
|
|
92
|
-
operationId: listItems
|
|
93
|
-
tags:
|
|
94
|
-
- Items
|
|
95
|
-
summary: List items
|
|
96
|
-
description: Returns all items belonging to the authenticated user.
|
|
97
|
-
responses:
|
|
98
|
-
"200":
|
|
99
|
-
description: List of items
|
|
100
|
-
content:
|
|
101
|
-
application/json:
|
|
102
|
-
schema:
|
|
103
|
-
type: object
|
|
104
|
-
required:
|
|
105
|
-
- success
|
|
106
|
-
- data
|
|
107
|
-
properties:
|
|
108
|
-
success:
|
|
109
|
-
type: boolean
|
|
110
|
-
enum:
|
|
111
|
-
- true
|
|
112
|
-
data:
|
|
113
|
-
type: object
|
|
114
|
-
required:
|
|
115
|
-
- data
|
|
116
|
-
- total
|
|
117
|
-
properties:
|
|
118
|
-
data:
|
|
119
|
-
type: array
|
|
120
|
-
items:
|
|
121
|
-
$ref: "#/components/schemas/Item"
|
|
122
|
-
total:
|
|
123
|
-
type: integer
|
|
124
|
-
examples:
|
|
125
|
-
valid:
|
|
126
|
-
value:
|
|
127
|
-
success: true
|
|
128
|
-
data:
|
|
129
|
-
data:
|
|
130
|
-
- id: itm_a1b2c3d4
|
|
131
|
-
user_id: 42
|
|
132
|
-
name: My Item
|
|
133
|
-
description: A sample item description
|
|
134
|
-
created_at: 2026-05-14T12:00:00Z
|
|
135
|
-
updated_at: 2026-05-14T12:00:00Z
|
|
136
|
-
total: 1
|
|
137
|
-
"401":
|
|
138
|
-
$ref: "#/components/responses/Unauthorized"
|
|
139
|
-
post:
|
|
140
|
-
operationId: createItem
|
|
141
|
-
tags:
|
|
142
|
-
- Items
|
|
143
|
-
summary: Create item
|
|
144
|
-
description: Creates a new item for the authenticated user.
|
|
145
|
-
requestBody:
|
|
146
|
-
required: true
|
|
147
|
-
content:
|
|
148
|
-
application/json:
|
|
149
|
-
schema:
|
|
150
|
-
$ref: "#/components/schemas/CreateItemInput"
|
|
151
|
-
examples:
|
|
152
|
-
valid:
|
|
153
|
-
value:
|
|
154
|
-
name: My Item
|
|
155
|
-
description: A sample item description
|
|
156
|
-
responses:
|
|
157
|
-
"201":
|
|
158
|
-
description: Item created
|
|
159
|
-
content:
|
|
160
|
-
application/json:
|
|
161
|
-
schema:
|
|
162
|
-
$ref: "#/components/schemas/ItemResponse"
|
|
163
|
-
examples:
|
|
164
|
-
valid:
|
|
165
|
-
value:
|
|
166
|
-
success: true
|
|
167
|
-
data:
|
|
168
|
-
id: itm_a1b2c3d4
|
|
169
|
-
user_id: 42
|
|
170
|
-
name: My Item
|
|
171
|
-
description: A sample item description
|
|
172
|
-
created_at: 2026-05-14T12:00:00Z
|
|
173
|
-
updated_at: 2026-05-14T12:00:00Z
|
|
174
|
-
"401":
|
|
175
|
-
$ref: "#/components/responses/Unauthorized"
|
|
176
|
-
/api/v1/items/{id}:
|
|
177
|
-
get:
|
|
178
|
-
operationId: getItem
|
|
179
|
-
tags:
|
|
180
|
-
- Items
|
|
181
|
-
summary: Get item
|
|
182
|
-
description: Returns a single item by ID.
|
|
183
|
-
parameters:
|
|
184
|
-
- name: id
|
|
185
|
-
in: path
|
|
186
|
-
required: true
|
|
187
|
-
schema:
|
|
188
|
-
type: string
|
|
189
|
-
examples:
|
|
190
|
-
valid:
|
|
191
|
-
value: itm_a1b2c3d4
|
|
192
|
-
responses:
|
|
193
|
-
"200":
|
|
194
|
-
description: Item details
|
|
195
|
-
content:
|
|
196
|
-
application/json:
|
|
197
|
-
schema:
|
|
198
|
-
$ref: "#/components/schemas/ItemResponse"
|
|
199
|
-
examples:
|
|
200
|
-
valid:
|
|
201
|
-
value:
|
|
202
|
-
success: true
|
|
203
|
-
data:
|
|
204
|
-
id: itm_a1b2c3d4
|
|
205
|
-
user_id: 42
|
|
206
|
-
name: My Item
|
|
207
|
-
description: A sample item description
|
|
208
|
-
created_at: 2026-05-14T12:00:00Z
|
|
209
|
-
updated_at: 2026-05-14T12:00:00Z
|
|
210
|
-
"401":
|
|
211
|
-
$ref: "#/components/responses/Unauthorized"
|
|
212
|
-
"404":
|
|
213
|
-
$ref: "#/components/responses/NotFound"
|
|
214
|
-
put:
|
|
215
|
-
operationId: updateItem
|
|
216
|
-
tags:
|
|
217
|
-
- Items
|
|
218
|
-
summary: Update item
|
|
219
|
-
description: Updates an existing item.
|
|
220
|
-
parameters:
|
|
221
|
-
- name: id
|
|
222
|
-
in: path
|
|
223
|
-
required: true
|
|
224
|
-
schema:
|
|
225
|
-
type: string
|
|
226
|
-
examples:
|
|
227
|
-
valid:
|
|
228
|
-
value: itm_a1b2c3d4
|
|
229
|
-
requestBody:
|
|
230
|
-
required: true
|
|
231
|
-
content:
|
|
232
|
-
application/json:
|
|
233
|
-
schema:
|
|
234
|
-
$ref: "#/components/schemas/UpdateItemInput"
|
|
235
|
-
examples:
|
|
236
|
-
valid:
|
|
237
|
-
value:
|
|
238
|
-
name: Updated Item
|
|
239
|
-
description: An updated item description
|
|
240
|
-
responses:
|
|
241
|
-
"200":
|
|
242
|
-
description: Item updated
|
|
243
|
-
content:
|
|
244
|
-
application/json:
|
|
245
|
-
schema:
|
|
246
|
-
$ref: "#/components/schemas/ItemResponse"
|
|
247
|
-
examples:
|
|
248
|
-
valid:
|
|
249
|
-
value:
|
|
250
|
-
success: true
|
|
251
|
-
data:
|
|
252
|
-
id: itm_a1b2c3d4
|
|
253
|
-
user_id: 42
|
|
254
|
-
name: Updated Item
|
|
255
|
-
description: An updated item description
|
|
256
|
-
created_at: 2026-05-14T12:00:00Z
|
|
257
|
-
updated_at: 2026-05-15T09:30:00Z
|
|
258
|
-
"401":
|
|
259
|
-
$ref: "#/components/responses/Unauthorized"
|
|
260
|
-
"404":
|
|
261
|
-
$ref: "#/components/responses/NotFound"
|
|
262
|
-
delete:
|
|
263
|
-
operationId: deleteItem
|
|
264
|
-
tags:
|
|
265
|
-
- Items
|
|
266
|
-
summary: Delete item
|
|
267
|
-
description: Deletes an item by ID.
|
|
268
|
-
parameters:
|
|
269
|
-
- name: id
|
|
270
|
-
in: path
|
|
271
|
-
required: true
|
|
272
|
-
schema:
|
|
273
|
-
type: string
|
|
274
|
-
examples:
|
|
275
|
-
valid:
|
|
276
|
-
value: itm_a1b2c3d4
|
|
277
|
-
responses:
|
|
278
|
-
"200":
|
|
279
|
-
description: Item deleted
|
|
280
|
-
content:
|
|
281
|
-
application/json:
|
|
282
|
-
schema:
|
|
283
|
-
$ref: "#/components/schemas/SuccessResponse"
|
|
284
|
-
examples:
|
|
285
|
-
valid:
|
|
286
|
-
value:
|
|
287
|
-
success: true
|
|
288
|
-
data:
|
|
289
|
-
success: true
|
|
290
|
-
"401":
|
|
291
|
-
$ref: "#/components/responses/Unauthorized"
|
|
292
|
-
"404":
|
|
293
|
-
$ref: "#/components/responses/NotFound"
|
|
294
|
-
components:
|
|
295
|
-
securitySchemes:
|
|
296
|
-
BearerAuth:
|
|
297
|
-
type: http
|
|
298
|
-
scheme: bearer
|
|
299
|
-
bearerFormat: JWT or PAT
|
|
300
|
-
description: Use either an Auth0 JWT token or a Personal Access Token (PAT) starting with `nbpat_`.
|
|
301
|
-
schemas:
|
|
302
|
-
User:
|
|
303
|
-
type: object
|
|
304
|
-
required:
|
|
305
|
-
- id
|
|
306
|
-
- auth0_sub
|
|
307
|
-
- email
|
|
308
|
-
- name
|
|
309
|
-
- language
|
|
310
|
-
- plan
|
|
311
|
-
properties:
|
|
312
|
-
id:
|
|
313
|
-
type: integer
|
|
314
|
-
auth0_sub:
|
|
315
|
-
type: string
|
|
316
|
-
email:
|
|
317
|
-
type: string
|
|
318
|
-
format: email
|
|
319
|
-
name:
|
|
320
|
-
type: string
|
|
321
|
-
language:
|
|
322
|
-
type: string
|
|
323
|
-
enum:
|
|
324
|
-
- ja
|
|
325
|
-
- en
|
|
326
|
-
plan:
|
|
327
|
-
type: string
|
|
328
|
-
enum:
|
|
329
|
-
- free
|
|
330
|
-
- pro
|
|
331
|
-
- family
|
|
332
|
-
- enterprise
|
|
333
|
-
subscription_status:
|
|
334
|
-
type: string
|
|
335
|
-
enum:
|
|
336
|
-
- active
|
|
337
|
-
- canceled
|
|
338
|
-
- past_due
|
|
339
|
-
- trialing
|
|
340
|
-
- null
|
|
341
|
-
nullable: true
|
|
342
|
-
subscription_expires_at:
|
|
343
|
-
type: string
|
|
344
|
-
format: date-time
|
|
345
|
-
nullable: true
|
|
346
|
-
UserResponse:
|
|
347
|
-
type: object
|
|
348
|
-
required:
|
|
349
|
-
- success
|
|
350
|
-
- data
|
|
351
|
-
properties:
|
|
352
|
-
success:
|
|
353
|
-
type: boolean
|
|
354
|
-
enum:
|
|
355
|
-
- true
|
|
356
|
-
data:
|
|
357
|
-
$ref: "#/components/schemas/User"
|
|
358
|
-
Item:
|
|
359
|
-
type: object
|
|
360
|
-
required:
|
|
361
|
-
- id
|
|
362
|
-
- user_id
|
|
363
|
-
- name
|
|
364
|
-
- created_at
|
|
365
|
-
- updated_at
|
|
366
|
-
properties:
|
|
367
|
-
id:
|
|
368
|
-
type: string
|
|
369
|
-
user_id:
|
|
370
|
-
type: integer
|
|
371
|
-
name:
|
|
372
|
-
type: string
|
|
373
|
-
description:
|
|
374
|
-
type: string
|
|
375
|
-
nullable: true
|
|
376
|
-
created_at:
|
|
377
|
-
type: string
|
|
378
|
-
format: date-time
|
|
379
|
-
updated_at:
|
|
380
|
-
type: string
|
|
381
|
-
format: date-time
|
|
382
|
-
ItemResponse:
|
|
383
|
-
type: object
|
|
384
|
-
required:
|
|
385
|
-
- success
|
|
386
|
-
- data
|
|
387
|
-
properties:
|
|
388
|
-
success:
|
|
389
|
-
type: boolean
|
|
390
|
-
enum:
|
|
391
|
-
- true
|
|
392
|
-
data:
|
|
393
|
-
$ref: "#/components/schemas/Item"
|
|
394
|
-
CreateItemInput:
|
|
395
|
-
type: object
|
|
396
|
-
required:
|
|
397
|
-
- name
|
|
398
|
-
properties:
|
|
399
|
-
name:
|
|
400
|
-
type: string
|
|
401
|
-
minLength: 1
|
|
402
|
-
maxLength: 200
|
|
403
|
-
description:
|
|
404
|
-
type: string
|
|
405
|
-
maxLength: 1000
|
|
406
|
-
UpdateItemInput:
|
|
407
|
-
type: object
|
|
408
|
-
properties:
|
|
409
|
-
name:
|
|
410
|
-
type: string
|
|
411
|
-
minLength: 1
|
|
412
|
-
maxLength: 200
|
|
413
|
-
description:
|
|
414
|
-
type: string
|
|
415
|
-
maxLength: 1000
|
|
416
|
-
SuccessResponse:
|
|
417
|
-
type: object
|
|
418
|
-
required:
|
|
419
|
-
- success
|
|
420
|
-
- data
|
|
421
|
-
properties:
|
|
422
|
-
success:
|
|
423
|
-
type: boolean
|
|
424
|
-
enum:
|
|
425
|
-
- true
|
|
426
|
-
data:
|
|
427
|
-
type: object
|
|
428
|
-
required:
|
|
429
|
-
- success
|
|
430
|
-
properties:
|
|
431
|
-
success:
|
|
432
|
-
type: boolean
|
|
433
|
-
enum:
|
|
434
|
-
- true
|
|
435
|
-
Error:
|
|
436
|
-
type: object
|
|
437
|
-
required:
|
|
438
|
-
- success
|
|
439
|
-
- error
|
|
440
|
-
properties:
|
|
441
|
-
success:
|
|
442
|
-
type: boolean
|
|
443
|
-
enum:
|
|
444
|
-
- false
|
|
445
|
-
error:
|
|
446
|
-
type: object
|
|
447
|
-
required:
|
|
448
|
-
- code
|
|
449
|
-
- message
|
|
450
|
-
properties:
|
|
451
|
-
code:
|
|
452
|
-
type: string
|
|
453
|
-
message:
|
|
454
|
-
type: string
|
|
455
|
-
responses:
|
|
456
|
-
Unauthorized:
|
|
457
|
-
description: Authentication required or invalid token
|
|
458
|
-
content:
|
|
459
|
-
application/json:
|
|
460
|
-
schema:
|
|
461
|
-
$ref: "#/components/schemas/Error"
|
|
462
|
-
examples:
|
|
463
|
-
unauthorized:
|
|
464
|
-
value:
|
|
465
|
-
success: false
|
|
466
|
-
error:
|
|
467
|
-
code: AUTH_ERROR
|
|
468
|
-
message: No authentication token provided
|
|
469
|
-
NotFound:
|
|
470
|
-
description: Resource not found
|
|
471
|
-
content:
|
|
472
|
-
application/json:
|
|
473
|
-
schema:
|
|
474
|
-
$ref: "#/components/schemas/Error"
|
|
475
|
-
examples:
|
|
476
|
-
not_found:
|
|
477
|
-
value:
|
|
478
|
-
success: false
|
|
479
|
-
error:
|
|
480
|
-
code: NOT_FOUND
|
|
481
|
-
message: Resource not found
|
|
482
|
-
tags:
|
|
483
|
-
- name: Health
|
|
484
|
-
description: Health check endpoints
|
|
485
|
-
- name: User
|
|
486
|
-
description: User profile endpoints
|
|
487
|
-
- name: Items
|
|
488
|
-
description: Sample CRUD endpoints (replace with your domain)
|