From dcfddc88c7a2a4e2435fb61e83ee7804e475dbb2 Mon Sep 17 00:00:00 2001 From: Anders Olsson Date: Wed, 3 Jun 2026 22:25:10 +0200 Subject: [PATCH] feat(web): generated OpenAPI types + typed openapi-fetch client with 401 redirect Co-Authored-By: Claude Sonnet 4.6 --- web/package.json | 4 +- web/pnpm-lock.yaml | 199 +++++- web/src/api/auth-redirect.ts | 7 + web/src/api/client.ts | 18 + web/src/api/schema.d.ts | 1232 ++++++++++++++++++++++++++++++++++ 5 files changed, 1438 insertions(+), 22 deletions(-) create mode 100644 web/src/api/auth-redirect.ts create mode 100644 web/src/api/client.ts create mode 100644 web/src/api/schema.d.ts diff --git a/web/package.json b/web/package.json index 828b494..d6ee085 100644 --- a/web/package.json +++ b/web/package.json @@ -19,6 +19,7 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "lucide-react": "^1.17.0", + "openapi-fetch": "^0.17.0", "react": "^19.1.0", "react-dom": "^19.1.0", "tailwind-merge": "^3.6.0", @@ -34,12 +35,13 @@ "@types/react": "^19.1.5", "@types/react-dom": "^19.1.3", "@vitejs/plugin-react": "^4.5.2", - "shadcn": "^4.10.0", "eslint": "^10.4.1", "eslint-plugin-react-hooks": "^7.1.1", "eslint-plugin-react-refresh": "^0.5.2", "globals": "^17.6.0", "jsdom": "^26.1.0", + "openapi-typescript": "^7.13.0", + "shadcn": "^4.10.0", "tailwindcss": "^4.3.0", "typescript": "~5.8.3", "typescript-eslint": "^8.60.1", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 889b16e..16730ad 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -23,6 +23,9 @@ importers: lucide-react: specifier: ^1.17.0 version: 1.17.0(react@19.2.7) + openapi-fetch: + specifier: ^0.17.0 + version: 0.17.0 react: specifier: ^19.1.0 version: 19.2.7 @@ -78,6 +81,9 @@ importers: jsdom: specifier: ^26.1.0 version: 26.1.0 + openapi-typescript: + specifier: ^7.13.0 + version: 7.13.0(typescript@5.8.3) shadcn: specifier: ^4.10.0 version: 4.10.0(@types/node@25.9.1)(typescript@5.8.3) @@ -655,6 +661,16 @@ packages: '@open-draft/until@2.1.0': resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} + '@redocly/ajv@8.11.2': + resolution: {integrity: sha512-io1JpnwtIcvojV7QKDUSIuMN/ikdOUd1ReEnUnMKGfDVridQZ31J0MmIuqwuRjWDZfmvr+Q0MqCcfHM2gTivOg==} + + '@redocly/config@0.22.0': + resolution: {integrity: sha512-gAy93Ddo01Z3bHuVdPWfCwzgfaYgMdaZPcfL7JZ7hWJoK9V0lXDbigTWkhiPFAaLWzbOJ+kbUQG1+XwIm0KRGQ==} + + '@redocly/openapi-core@1.34.15': + resolution: {integrity: sha512-HAwCnNyKcs5XGQqms+9t7OdAPM/5TDstmhF+0i7tdCFato2QKuYIlyWETwkXd8c5zbltr1oB+6y9NTeQLr2d6Q==} + engines: {node: '>=18.17.0', npm: '>=9.5.0'} + '@rolldown/pluginutils@1.0.0-beta.27': resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} @@ -1143,6 +1159,9 @@ packages: resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==} engines: {node: '>=4'} + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + balanced-match@4.0.4: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} @@ -1156,6 +1175,9 @@ packages: resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} engines: {node: '>=18'} + brace-expansion@2.1.1: + resolution: {integrity: sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==} + brace-expansion@5.0.6: resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} engines: {node: 18 || 20 || >=22} @@ -1204,6 +1226,9 @@ packages: resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + change-case@5.4.4: + resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==} + check-error@2.1.3: resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} engines: {node: '>= 16'} @@ -1241,6 +1266,9 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + colorette@1.4.0: + resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==} + commander@11.1.0: resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} engines: {node: '>=16'} @@ -1777,6 +1805,10 @@ packages: resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} engines: {node: '>=8'} + index-to-position@1.2.0: + resolution: {integrity: sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==} + engines: {node: '>=18'} + inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -1880,12 +1912,20 @@ packages: jose@6.2.3: resolution: {integrity: sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==} + js-levenshtein@1.1.6: + resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==} + engines: {node: '>=0.10.0'} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} js-tokens@9.0.1: resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + js-yaml@4.2.0: resolution: {integrity: sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==} hasBin: true @@ -2098,6 +2138,10 @@ packages: resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} engines: {node: 18 || 20 || >=22} + minimatch@5.1.9: + resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==} + engines: {node: '>=10'} + minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} @@ -2185,6 +2229,18 @@ packages: resolution: {integrity: sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==} engines: {node: '>=20'} + openapi-fetch@0.17.0: + resolution: {integrity: sha512-PsbZR1wAPcG91eEthKhN+Zn92FMHxv+/faECIwjXdxfTODGSGegYv0sc1Olz+HYPvKOuoXfp+0pA2XVt2cI0Ig==} + + openapi-typescript-helpers@0.1.0: + resolution: {integrity: sha512-OKTGPthhivLw/fHz6c3OPtg72vi86qaMlqbJuVJ23qOvQ+53uw1n7HdmkJFibloF7QEjDrDkzJiOJuockM/ljw==} + + openapi-typescript@7.13.0: + resolution: {integrity: sha512-EFP392gcqXS7ntPvbhBzbF8TyBA+baIYEm791Hy5YkjDYKTnk/Tn5OQeKm5BIZvJihpp8Zzr4hzx0Irde1LNGQ==} + hasBin: true + peerDependencies: + typescript: ^5.x + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -2212,6 +2268,10 @@ packages: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} + parse-json@8.3.0: + resolution: {integrity: sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==} + engines: {node: '>=18'} + parse-ms@4.0.0: resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} engines: {node: '>=18'} @@ -2266,6 +2326,10 @@ packages: resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} engines: {node: '>=16.20.0'} + pluralize@8.0.0: + resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} + engines: {node: '>=4'} + postcss-selector-parser@7.1.1: resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==} engines: {node: '>=4'} @@ -2524,6 +2588,10 @@ packages: strip-literal@3.1.0: resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} + supports-color@10.2.2: + resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} + engines: {node: '>=18'} + symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} @@ -2623,6 +2691,10 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} + type-fest@4.41.0: + resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} + engines: {node: '>=16'} + type-fest@5.7.0: resolution: {integrity: sha512-1URUxUqfHFM1c+zfSPsa3gnkO7Aq21qyH75SIduNYz4SzY964rn1X2vCMQaHSHhktiw+0kPa2iyb6PUpXqB6Vg==} engines: {node: '>=20'} @@ -2667,6 +2739,9 @@ packages: peerDependencies: browserslist: '>= 4.21.0' + uri-js-replace@1.0.1: + resolution: {integrity: sha512-W+C9NWNLFOoBI2QWDp4UT9pv65r2w5Cx+3sTYFvtMdDBxkKt1syCqsUdSFAChbEe1uK5TfS04wt/nGwmaeIQ0g==} + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -2840,6 +2915,9 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yaml-ast-parser@0.0.43: + resolution: {integrity: sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==} + yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} @@ -2907,7 +2985,7 @@ snapshots: '@babel/types': 7.29.7 '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -3075,7 +3153,7 @@ snapshots: '@babel/parser': 7.29.7 '@babel/template': 7.29.7 '@babel/types': 7.29.7 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) transitivePeerDependencies: - supports-color @@ -3233,7 +3311,7 @@ snapshots: '@eslint/config-array@0.23.5': dependencies: '@eslint/object-schema': 3.0.5 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) minimatch: 10.2.5 transitivePeerDependencies: - supports-color @@ -3404,6 +3482,29 @@ snapshots: '@open-draft/until@2.1.0': {} + '@redocly/ajv@8.11.2': + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js-replace: 1.0.1 + + '@redocly/config@0.22.0': {} + + '@redocly/openapi-core@1.34.15(supports-color@10.2.2)': + dependencies: + '@redocly/ajv': 8.11.2 + '@redocly/config': 0.22.0 + colorette: 1.4.0 + https-proxy-agent: 7.0.6(supports-color@10.2.2) + js-levenshtein: 1.1.6 + js-yaml: 4.1.1 + minimatch: 5.1.9 + pluralize: 8.0.0 + yaml-ast-parser: 0.0.43 + transitivePeerDependencies: + - supports-color + '@rolldown/pluginutils@1.0.0-beta.27': {} '@rollup/rollup-android-arm-eabi@4.61.0': @@ -3671,7 +3772,7 @@ snapshots: '@typescript-eslint/types': 8.60.1 '@typescript-eslint/typescript-estree': 8.60.1(typescript@5.8.3) '@typescript-eslint/visitor-keys': 8.60.1 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) eslint: 10.4.1(jiti@2.7.0) typescript: 5.8.3 transitivePeerDependencies: @@ -3681,7 +3782,7 @@ snapshots: dependencies: '@typescript-eslint/tsconfig-utils': 8.60.1(typescript@5.8.3) '@typescript-eslint/types': 8.60.1 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) typescript: 5.8.3 transitivePeerDependencies: - supports-color @@ -3700,7 +3801,7 @@ snapshots: '@typescript-eslint/types': 8.60.1 '@typescript-eslint/typescript-estree': 8.60.1(typescript@5.8.3) '@typescript-eslint/utils': 8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@5.8.3) - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) eslint: 10.4.1(jiti@2.7.0) ts-api-utils: 2.5.0(typescript@5.8.3) typescript: 5.8.3 @@ -3715,7 +3816,7 @@ snapshots: '@typescript-eslint/tsconfig-utils': 8.60.1(typescript@5.8.3) '@typescript-eslint/types': 8.60.1 '@typescript-eslint/visitor-keys': 8.60.1 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) minimatch: 10.2.5 semver: 7.8.1 tinyglobby: 0.2.17 @@ -3852,6 +3953,8 @@ snapshots: dependencies: tslib: 2.8.1 + balanced-match@1.0.2: {} + balanced-match@4.0.4: {} baseline-browser-mapping@2.10.33: {} @@ -3860,7 +3963,7 @@ snapshots: dependencies: bytes: 3.1.2 content-type: 1.0.5 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) http-errors: 2.0.1 iconv-lite: 0.7.2 on-finished: 2.4.1 @@ -3870,6 +3973,10 @@ snapshots: transitivePeerDependencies: - supports-color + brace-expansion@2.1.1: + dependencies: + balanced-match: 1.0.2 + brace-expansion@5.0.6: dependencies: balanced-match: 4.0.4 @@ -3918,6 +4025,8 @@ snapshots: chalk@5.6.2: {} + change-case@5.4.4: {} + check-error@2.1.3: {} class-variance-authority@0.7.1: @@ -3948,6 +4057,8 @@ snapshots: color-name@1.1.4: {} + colorette@1.4.0: {} + commander@11.1.0: {} commander@14.0.3: {} @@ -4004,9 +4115,11 @@ snapshots: whatwg-mimetype: 4.0.0 whatwg-url: 14.2.0 - debug@4.4.3: + debug@4.4.3(supports-color@10.2.2): dependencies: ms: 2.1.3 + optionalDependencies: + supports-color: 10.2.2 decimal.js@10.6.0: {} @@ -4167,7 +4280,7 @@ snapshots: '@types/estree': 1.0.9 ajv: 6.15.0 cross-spawn: 7.0.6 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) escape-string-regexp: 4.0.0 eslint-scope: 9.1.2 eslint-visitor-keys: 5.0.1 @@ -4264,7 +4377,7 @@ snapshots: content-type: 1.0.5 cookie: 0.7.2 cookie-signature: 1.2.2 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) depd: 2.0.0 encodeurl: 2.0.0 escape-html: 1.0.3 @@ -4342,7 +4455,7 @@ snapshots: finalhandler@2.1.1: dependencies: - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) encodeurl: 2.0.0 escape-html: 1.0.3 on-finished: 2.4.1 @@ -4467,14 +4580,14 @@ snapshots: http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.4 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) transitivePeerDependencies: - supports-color - https-proxy-agent@7.0.6: + https-proxy-agent@7.0.6(supports-color@10.2.2): dependencies: agent-base: 7.1.4 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) transitivePeerDependencies: - supports-color @@ -4503,6 +4616,8 @@ snapshots: indent-string@4.0.0: {} + index-to-position@1.2.0: {} + inherits@2.0.4: {} ip-address@10.2.0: {} @@ -4563,10 +4678,16 @@ snapshots: jose@6.2.3: {} + js-levenshtein@1.1.6: {} + js-tokens@4.0.0: {} js-tokens@9.0.1: {} + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + js-yaml@4.2.0: dependencies: argparse: 2.0.1 @@ -4578,7 +4699,7 @@ snapshots: decimal.js: 10.6.0 html-encoding-sniffer: 4.0.0 http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.6 + https-proxy-agent: 7.0.6(supports-color@10.2.2) is-potential-custom-element-name: 1.0.1 nwsapi: 2.2.23 parse5: 7.3.0 @@ -4742,6 +4863,10 @@ snapshots: dependencies: brace-expansion: 5.0.6 + minimatch@5.1.9: + dependencies: + brace-expansion: 2.1.1 + minimist@1.2.8: {} ms@2.1.3: {} @@ -4831,6 +4956,22 @@ snapshots: powershell-utils: 0.1.0 wsl-utils: 0.3.1 + openapi-fetch@0.17.0: + dependencies: + openapi-typescript-helpers: 0.1.0 + + openapi-typescript-helpers@0.1.0: {} + + openapi-typescript@7.13.0(typescript@5.8.3): + dependencies: + '@redocly/openapi-core': 1.34.15(supports-color@10.2.2) + ansi-colors: 4.1.3 + change-case: 5.4.4 + parse-json: 8.3.0 + supports-color: 10.2.2 + typescript: 5.8.3 + yargs-parser: 21.1.1 + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -4873,6 +5014,12 @@ snapshots: json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 + parse-json@8.3.0: + dependencies: + '@babel/code-frame': 7.29.7 + index-to-position: 1.2.0 + type-fest: 4.41.0 + parse-ms@4.0.0: {} parse5@7.3.0: @@ -4905,6 +5052,8 @@ snapshots: pkce-challenge@5.0.1: {} + pluralize@8.0.0: {} + postcss-selector-parser@7.1.1: dependencies: cssesc: 3.0.0 @@ -5031,7 +5180,7 @@ snapshots: router@2.2.0: dependencies: - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) depd: 2.0.0 is-promise: 4.0.0 parseurl: 1.3.3 @@ -5061,7 +5210,7 @@ snapshots: send@1.2.1: dependencies: - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 @@ -5107,7 +5256,7 @@ snapshots: fast-glob: 3.3.3 fs-extra: 11.3.5 fuzzysort: 3.1.0 - https-proxy-agent: 7.0.6 + https-proxy-agent: 7.0.6(supports-color@10.2.2) kleur: 4.1.5 msw: 2.14.6(@types/node@25.9.1)(typescript@5.8.3) node-fetch: 3.3.2 @@ -5227,6 +5376,8 @@ snapshots: dependencies: js-tokens: 9.0.1 + supports-color@10.2.2: {} + symbol-tree@3.2.4: {} tagged-tag@1.0.0: {} @@ -5307,6 +5458,8 @@ snapshots: dependencies: prelude-ls: 1.2.1 + type-fest@4.41.0: {} + type-fest@5.7.0: dependencies: tagged-tag: 1.0.0 @@ -5346,6 +5499,8 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 + uri-js-replace@1.0.1: {} + uri-js@4.4.1: dependencies: punycode: 2.3.1 @@ -5363,7 +5518,7 @@ snapshots: vite-node@3.2.4(@types/node@25.9.1)(jiti@2.7.0)(lightningcss@1.32.0): dependencies: cac: 6.7.14 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) es-module-lexer: 1.7.0 pathe: 2.0.3 vite: 6.4.3(@types/node@25.9.1)(jiti@2.7.0)(lightningcss@1.32.0) @@ -5406,7 +5561,7 @@ snapshots: '@vitest/spy': 3.2.6 '@vitest/utils': 3.2.6 chai: 5.3.3 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) expect-type: 1.3.0 magic-string: 0.30.21 pathe: 2.0.3 @@ -5494,6 +5649,8 @@ snapshots: yallist@3.1.1: {} + yaml-ast-parser@0.0.43: {} + yargs-parser@21.1.1: {} yargs@17.7.2: diff --git a/web/src/api/auth-redirect.ts b/web/src/api/auth-redirect.ts new file mode 100644 index 0000000..269a4bf --- /dev/null +++ b/web/src/api/auth-redirect.ts @@ -0,0 +1,7 @@ +/** Hard-navigate to login. Isolated so it can be spied/mocked in tests and swapped + * for a router navigation if needed. */ +export function redirectToLogin(): void { + if (window.location.pathname !== "/login") { + window.location.assign("/login"); + } +} diff --git a/web/src/api/client.ts b/web/src/api/client.ts new file mode 100644 index 0000000..ec510e3 --- /dev/null +++ b/web/src/api/client.ts @@ -0,0 +1,18 @@ +import createClient, { type Middleware } from "openapi-fetch"; + +import type { paths } from "./schema"; +import { redirectToLogin } from "./auth-redirect"; + +const onUnauthorized: Middleware = { + async onResponse({ response }) { + if (response.status === 401) { + redirectToLogin(); + } + + return response; + }, +}; + +export const api = createClient({ credentials: "include" }); + +api.use(onUnauthorized); diff --git a/web/src/api/schema.d.ts b/web/src/api/schema.d.ts new file mode 100644 index 0000000..4e271b6 --- /dev/null +++ b/web/src/api/schema.d.ts @@ -0,0 +1,1232 @@ +/** + * This file was auto-generated by openapi-typescript. + * Do not make direct changes to the file. + */ + +export interface paths { + "/api/admin/authorities": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["list_authorities"]; + put?: never; + post: operations["create_authority"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/admin/field-definitions": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List all field definitions. Requires `ViewInternal`. */ + get: operations["list_field_definitions"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/admin/login": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Log in with email + password. On success establishes a session (Set-Cookie) and + * returns 204; on failure 401 with no detail (no user enumeration). + */ + post: operations["login"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/admin/logout": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Log out: clear the session. */ + post: operations["logout"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/admin/me": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** The current authenticated user. */ + get: operations["me"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/admin/objects": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List objects (paginated, all visibility levels). Requires `ViewInternal`. */ + get: operations["list_objects"]; + put?: never; + /** Create an object (initial visibility Draft or Internal). Requires `EditCatalogue`. */ + post: operations["create_object"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/admin/objects/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get one object (any visibility). Requires `ViewInternal`. 404 if missing. */ + get: operations["get_object"]; + /** Update an object's inventory-minimum fields (NOT visibility). Requires `EditCatalogue`. */ + put: operations["update_object"]; + post?: never; + /** Delete an object. Requires `EditCatalogue`. 404 if it did not exist. */ + delete: operations["delete_object"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/admin/objects/{id}/fields": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + /** + * Replace an object's flexible-field values (validated against the registry). + * @description **Replace semantics:** the body is the *complete* desired field set. Omitting a key + * that was previously set removes it — send every key the caller wants to retain. + * + * Requires `EditCatalogue`. + */ + put: operations["set_fields"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/admin/objects/{id}/visibility": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Change an object's visibility (publish/unpublish). Requires `PublishObjects`. */ + post: operations["set_visibility"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/admin/users": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List all users (Admin only). */ + get: operations["list_users"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/admin/vocabularies": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["list_vocabularies"]; + put?: never; + post: operations["create_vocabulary"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/admin/vocabularies/{id}/terms": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["list_terms"]; + put?: never; + post: operations["add_term"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/public/objects": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List public objects (paginated). */ + get: operations["list_objects"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/public/objects/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get one public object by id. Returns 404 if missing OR not public. */ + get: operations["get_object"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/health/live": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Liveness probe — no dependencies checked. */ + get: operations["live"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/health/ready": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Readiness probe — confirms the database answers. */ + get: operations["ready"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; +} +export type webhooks = Record; +export interface components { + schemas: { + /** @description A page of admin objects. */ + AdminObjectPage: { + items: components["schemas"]["AdminObjectView"][]; + /** Format: int64 */ + limit: number; + /** Format: int64 */ + offset: number; + /** Format: int64 */ + total: number; + }; + /** @description Full admin view of a catalogue object (all fields, all visibility levels). */ + AdminObjectView: { + brief_description?: string | null; + current_location?: string | null; + current_owner?: string | null; + /** @description Flexible field values (key -> value). */ + fields: Record; + id: string; + /** Format: int32 */ + number_of_objects: number; + object_name: string; + object_number: string; + recorder?: string | null; + /** @description `YYYY-MM-DD` or null. */ + recording_date?: string | null; + /** @description "draft" | "internal" | "public". */ + visibility: string; + }; + AuthorityView: { + external_uri?: string | null; + id: string; + kind: string; + labels: components["schemas"]["LabelView"][]; + }; + CreatedId: { + id: string; + }; + /** @description The id of a newly created object. */ + CreatedObject: { + id: string; + }; + /** @description Field-definition descriptor for the UI to render forms. */ + FieldDefinitionView: { + authority_kind?: string | null; + /** @description "text" | "localized_text" | "integer" | "date" | "boolean" | "term" | "authority". */ + data_type: string; + group?: string | null; + key: string; + labels: components["schemas"]["LabelView"][]; + required: boolean; + vocabulary_id?: string | null; + }; + LabelInput: { + label: string; + lang: string; + }; + /** @description A localized label `{ lang, label }` (shared across admin views). */ + LabelView: { + label: string; + lang: string; + }; + /** @description Liveness payload: the process is running. */ + Live: { + /** @description Always `"ok"` when the process serves requests. */ + status: string; + }; + /** @description Credentials for password login. */ + LoginRequest: { + email: string; + password: string; + }; + NewAuthorityRequest: { + external_uri?: string | null; + /** @description "person" | "organisation" | "place". */ + kind: string; + labels: components["schemas"]["LabelInput"][]; + }; + NewTermRequest: { + external_uri?: string | null; + labels: components["schemas"]["LabelInput"][]; + }; + NewVocabularyRequest: { + key: string; + }; + /** @description Inventory-minimum fields for create. `recording_date` is `YYYY-MM-DD`. */ + ObjectCreateRequest: { + brief_description?: string | null; + current_location?: string | null; + current_owner?: string | null; + /** Format: int32 */ + number_of_objects: number; + object_name: string; + object_number: string; + recorder?: string | null; + recording_date?: string | null; + /** @description "draft" | "internal" (public is rejected — publish via the visibility endpoint). */ + visibility: string; + }; + /** + * @description Inventory-minimum fields for update. Visibility is intentionally absent — it changes + * only through the stepwise publish endpoint. + */ + ObjectUpdateRequest: { + brief_description?: string | null; + current_location?: string | null; + current_owner?: string | null; + /** Format: int32 */ + number_of_objects: number; + object_name: string; + object_number: string; + recorder?: string | null; + recording_date?: string | null; + }; + /** @description A page of public objects. */ + PublicObjectPage: { + items: components["schemas"]["PublicView"][]; + /** Format: int64 */ + limit: number; + /** Format: int64 */ + offset: number; + /** + * Format: int64 + * @description Total number of public objects (independent of paging). + */ + total: number; + }; + /** @description A catalogue object as exposed on the public surface (public-safe fields only). */ + PublicView: { + brief_description?: string | null; + /** @description Stable object id (UUID). */ + id: string; + object_name: string; + object_number: string; + }; + /** @description Readiness payload: dependencies were checked. */ + Ready: { + /** @description Whether the database responded to a ping. */ + database: boolean; + /** @description `"ok"` when ready, `"degraded"` otherwise. */ + status: string; + }; + TermView: { + external_uri?: string | null; + id: string; + labels: components["schemas"]["LabelView"][]; + }; + /** @description A user as exposed on the admin surface (no password material). */ + UserView: { + email: string; + id: string; + role: string; + }; + /** @description Desired visibility for a publish/unpublish request. */ + VisibilityRequest: { + visibility: string; + }; + VocabularyView: { + id: string; + key: string; + }; + }; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; +} +export type $defs = Record; +export interface operations { + list_authorities: { + parameters: { + query: { + /** @description person | organisation | place */ + kind: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AuthorityView"][]; + }; + }; + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 403: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 422: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + create_authority: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["NewAuthorityRequest"]; + }; + }; + responses: { + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CreatedId"]; + }; + }; + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 403: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 422: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + list_field_definitions: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["FieldDefinitionView"][]; + }; + }; + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 403: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + login: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["LoginRequest"]; + }; + }; + responses: { + /** @description Logged in */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Invalid credentials */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + logout: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Logged out */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + me: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["UserView"]; + }; + }; + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + list_objects: { + parameters: { + query?: { + /** @description 1..=200, default 50 */ + limit?: number; + /** @description default 0 */ + offset?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AdminObjectPage"]; + }; + }; + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 403: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + create_object: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ObjectCreateRequest"]; + }; + }; + responses: { + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CreatedObject"]; + }; + }; + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 403: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Invalid input (e.g. visibility=public or bad date) */ + 422: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + get_object: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Object id (UUID) */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AdminObjectView"]; + }; + }; + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 403: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + update_object: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Object id (UUID) */ + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ObjectUpdateRequest"]; + }; + }; + responses: { + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 403: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 422: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + delete_object: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Object id (UUID) */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 403: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + set_fields: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Object id (UUID) */ + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": Record; + }; + }; + responses: { + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 403: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Object not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Unknown field, type mismatch, or unresolved reference */ + 422: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + set_visibility: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Object id (UUID) */ + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["VisibilityRequest"]; + }; + }; + responses: { + /** @description Visibility changed */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 403: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description No such object */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Illegal visibility transition */ + 409: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + list_users: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["UserView"][]; + }; + }; + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 403: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + list_vocabularies: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["VocabularyView"][]; + }; + }; + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 403: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + create_vocabulary: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["NewVocabularyRequest"]; + }; + }; + responses: { + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["VocabularyView"]; + }; + }; + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 403: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + list_terms: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Vocabulary id (UUID) */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TermView"][]; + }; + }; + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 403: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + add_term: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Vocabulary id (UUID) */ + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["NewTermRequest"]; + }; + }; + responses: { + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CreatedId"]; + }; + }; + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 403: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + list_objects: { + parameters: { + query?: { + /** @description Max items (1..=200, default 50) */ + limit?: number; + /** @description Items to skip (default 0) */ + offset?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PublicObjectPage"]; + }; + }; + }; + }; + get_object: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Object id (UUID) */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PublicView"]; + }; + }; + /** @description No public object with that id */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + live: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Live"]; + }; + }; + }; + }; + ready: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Ready */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Ready"]; + }; + }; + /** @description A dependency is unavailable */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Ready"]; + }; + }; + }; + }; +}