diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 43273caf361d53bed6488c4b75e1405ad6e7bd7f..40c0215de4b6ddbb83084761a19bb4dc88473471 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -19,6 +19,7 @@ lint: - prepare script: - npm run lint + - npm run type-check build: stage: build diff --git a/index.html b/index.html index 6ff0a1461d0d1a572ff8011c9bd4aeca2c5749fc..821596ca95408ab9601050d24f8f38e6163dbf94 100644 --- a/index.html +++ b/index.html @@ -3,7 +3,7 @@ <head> <meta charset="UTF-8" /> <link rel="icon" href="/favicon.ico" /> - <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous"> + <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-gH2yIJqKdNHPEq0n4Mqa/HGKIhSkIHeL5AyhkYV8i59U5AR6csBvApHHNl/vI1Bx" crossorigin="anonymous"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.9.1/font/bootstrap-icons.css"> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>S3 Proxy</title> @@ -11,6 +11,6 @@ <body> <div id="app"></div> <script type="module" src="/src/main.ts"></script> - <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script> + <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-A3rJD856KowSb7dwlZdYEkO39Gagi7vIsF0jrRAoQmDKKtQBHUuLZ9AsSv4jD4Xa" crossorigin="anonymous"></script> </body> </html> diff --git a/package-lock.json b/package-lock.json index ef9b13f01e3f1db36ad6deb386dc08dbe17b2cb9..1e2aa7ba92f47efa04d3295a92616e3a77c58774 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,8 @@ "version": "0.0.0", "dependencies": { "@aws-sdk/client-s3": "^3.131.0", - "bootstrap": "^5.1.3", + "axios": "^0.27.2", + "bootstrap": "^5.2.0", "bootstrap-icons": "^1.9.1", "pinia": "^2.0.16", "vue": "^3.2.37", @@ -2169,6 +2170,20 @@ "node": ">=8" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", + "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", + "dependencies": { + "follow-redirects": "^1.14.9", + "form-data": "^4.0.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -2182,15 +2197,21 @@ "dev": true }, "node_modules/bootstrap": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.1.3.tgz", - "integrity": "sha512-fcQztozJ8jToQWXxVuEyXWW+dSo8AiXWKwiSSrKWsRB/Qt+Ewwza+JWoLKiTuQLaEPhdNAJ7+Dosc9DOIqNy7Q==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/bootstrap" - }, + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.2.0.tgz", + "integrity": "sha512-qlnS9GL6YZE6Wnef46GxGv1UpGGzAwO0aPL1yOjzDIJpeApeMvqV24iL+pjr2kU4dduoBA9fINKWKgMToobx9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], "peerDependencies": { - "@popperjs/core": "^2.10.2" + "@popperjs/core": "^2.11.5" } }, "node_modules/bootstrap-icons": { @@ -2299,6 +2320,17 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "9.4.0", "resolved": "https://registry.npmjs.org/commander/-/commander-9.4.0.tgz", @@ -2384,6 +2416,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -3218,6 +3258,38 @@ "integrity": "sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ==", "dev": true }, + "node_modules/follow-redirects": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", + "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fs-extra": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", @@ -3905,6 +3977,25 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -7107,6 +7198,20 @@ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "axios": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", + "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", + "requires": { + "follow-redirects": "^1.14.9", + "form-data": "^4.0.0" + } + }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -7120,9 +7225,9 @@ "dev": true }, "bootstrap": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.1.3.tgz", - "integrity": "sha512-fcQztozJ8jToQWXxVuEyXWW+dSo8AiXWKwiSSrKWsRB/Qt+Ewwza+JWoLKiTuQLaEPhdNAJ7+Dosc9DOIqNy7Q==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.2.0.tgz", + "integrity": "sha512-qlnS9GL6YZE6Wnef46GxGv1UpGGzAwO0aPL1yOjzDIJpeApeMvqV24iL+pjr2kU4dduoBA9fINKWKgMToobx9A==", "requires": {} }, "bootstrap-icons": { @@ -7207,6 +7312,14 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, "commander": { "version": "9.4.0", "resolved": "https://registry.npmjs.org/commander/-/commander-9.4.0.tgz", @@ -7266,6 +7379,11 @@ "object-keys": "^1.1.1" } }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" + }, "dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -7797,6 +7915,21 @@ "integrity": "sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ==", "dev": true }, + "follow-redirects": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", + "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==" + }, + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, "fs-extra": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", @@ -8293,6 +8426,19 @@ "picomatch": "^2.3.1" } }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, "minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", diff --git a/package.json b/package.json index 78fd112981ad532033699b7833c67c790cdc72bd..6452065bba865ff368b1105e888bf4e0c82ddf4d 100644 --- a/package.json +++ b/package.json @@ -8,11 +8,11 @@ "build-only": "vite build", "type-check": "vue-tsc --noEmit", "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore", - "generate-client": "openapi --input http://localhost:8000/api/openapi.json --output src/client --client axios" + "generate-client": "openapi --input http://localhost:9999/api/openapi.json --output src/client --client axios" }, "dependencies": { "@aws-sdk/client-s3": "^3.131.0", - "bootstrap": "^5.1.3", + "bootstrap": "^5.2.0", "bootstrap-icons": "^1.9.1", "pinia": "^2.0.16", "vue": "^3.2.37", @@ -29,10 +29,11 @@ "eslint": "^8.5.0", "eslint-plugin-vue": "^9.0.0", "npm-run-all": "^4.1.5", + "openapi-typescript-codegen": "^0.23.0", "prettier": "^2.5.1", "typescript": "~4.7.4", "vite": "^3.0.1", "vue-tsc": "^0.38.8", - "openapi-typescript-codegen": "^0.23.0" + "axios": "^0.27.2" } } diff --git a/src/client/core/ApiError.ts b/src/client/core/ApiError.ts new file mode 100644 index 0000000000000000000000000000000000000000..99d792996765118614adf6a232bc8766b95710e9 --- /dev/null +++ b/src/client/core/ApiError.ts @@ -0,0 +1,24 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ApiRequestOptions } from './ApiRequestOptions'; +import type { ApiResult } from './ApiResult'; + +export class ApiError extends Error { + public readonly url: string; + public readonly status: number; + public readonly statusText: string; + public readonly body: any; + public readonly request: ApiRequestOptions; + + constructor(request: ApiRequestOptions, response: ApiResult, message: string) { + super(message); + + this.name = 'ApiError'; + this.url = response.url; + this.status = response.status; + this.statusText = response.statusText; + this.body = response.body; + this.request = request; + } +} diff --git a/src/client/core/ApiRequestOptions.ts b/src/client/core/ApiRequestOptions.ts new file mode 100644 index 0000000000000000000000000000000000000000..c7b77538c5ce6aa48f141541392121fbcafc4821 --- /dev/null +++ b/src/client/core/ApiRequestOptions.ts @@ -0,0 +1,16 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type ApiRequestOptions = { + readonly method: 'GET' | 'PUT' | 'POST' | 'DELETE' | 'OPTIONS' | 'HEAD' | 'PATCH'; + readonly url: string; + readonly path?: Record<string, any>; + readonly cookies?: Record<string, any>; + readonly headers?: Record<string, any>; + readonly query?: Record<string, any>; + readonly formData?: Record<string, any>; + readonly body?: any; + readonly mediaType?: string; + readonly responseHeader?: string; + readonly errors?: Record<number, string>; +}; diff --git a/src/client/core/ApiResult.ts b/src/client/core/ApiResult.ts new file mode 100644 index 0000000000000000000000000000000000000000..b095dc7708af2aa405a6dc7ad08f423334bc7f24 --- /dev/null +++ b/src/client/core/ApiResult.ts @@ -0,0 +1,10 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type ApiResult = { + readonly url: string; + readonly ok: boolean; + readonly status: number; + readonly statusText: string; + readonly body: any; +}; diff --git a/src/client/core/CancelablePromise.ts b/src/client/core/CancelablePromise.ts new file mode 100644 index 0000000000000000000000000000000000000000..26ad303915af161f170cc144a5e7684be3053778 --- /dev/null +++ b/src/client/core/CancelablePromise.ts @@ -0,0 +1,128 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export class CancelError extends Error { + + constructor(message: string) { + super(message); + this.name = 'CancelError'; + } + + public get isCancelled(): boolean { + return true; + } +} + +export interface OnCancel { + readonly isResolved: boolean; + readonly isRejected: boolean; + readonly isCancelled: boolean; + + (cancelHandler: () => void): void; +} + +export class CancelablePromise<T> implements Promise<T> { + readonly [Symbol.toStringTag]!: string; + + private _isResolved: boolean; + private _isRejected: boolean; + private _isCancelled: boolean; + private readonly _cancelHandlers: (() => void)[]; + private readonly _promise: Promise<T>; + private _resolve?: (value: T | PromiseLike<T>) => void; + private _reject?: (reason?: any) => void; + + constructor( + executor: ( + resolve: (value: T | PromiseLike<T>) => void, + reject: (reason?: any) => void, + onCancel: OnCancel + ) => void + ) { + this._isResolved = false; + this._isRejected = false; + this._isCancelled = false; + this._cancelHandlers = []; + this._promise = new Promise<T>((resolve, reject) => { + this._resolve = resolve; + this._reject = reject; + + const onResolve = (value: T | PromiseLike<T>): void => { + if (this._isResolved || this._isRejected || this._isCancelled) { + return; + } + this._isResolved = true; + this._resolve?.(value); + }; + + const onReject = (reason?: any): void => { + if (this._isResolved || this._isRejected || this._isCancelled) { + return; + } + this._isRejected = true; + this._reject?.(reason); + }; + + const onCancel = (cancelHandler: () => void): void => { + if (this._isResolved || this._isRejected || this._isCancelled) { + return; + } + this._cancelHandlers.push(cancelHandler); + }; + + Object.defineProperty(onCancel, 'isResolved', { + get: (): boolean => this._isResolved, + }); + + Object.defineProperty(onCancel, 'isRejected', { + get: (): boolean => this._isRejected, + }); + + Object.defineProperty(onCancel, 'isCancelled', { + get: (): boolean => this._isCancelled, + }); + + return executor(onResolve, onReject, onCancel as OnCancel); + }); + } + + public then<TResult1 = T, TResult2 = never>( + onFulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null, + onRejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null + ): Promise<TResult1 | TResult2> { + return this._promise.then(onFulfilled, onRejected); + } + + public catch<TResult = never>( + onRejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null + ): Promise<T | TResult> { + return this._promise.catch(onRejected); + } + + public finally(onFinally?: (() => void) | null): Promise<T> { + return this._promise.finally(onFinally); + } + + public cancel(): void { + if (this._isResolved || this._isRejected || this._isCancelled) { + return; + } + this._isCancelled = true; + if (this._cancelHandlers.length) { + try { + for (const cancelHandler of this._cancelHandlers) { + cancelHandler(); + } + } catch (error) { + console.warn('Cancellation threw an error', error); + return; + } + } + this._cancelHandlers.length = 0; + this._reject?.(new CancelError('Request aborted')); + } + + public get isCancelled(): boolean { + return this._isCancelled; + } +} diff --git a/src/client/core/OpenAPI.ts b/src/client/core/OpenAPI.ts new file mode 100644 index 0000000000000000000000000000000000000000..dba8bdb3010f83c6abfb91a17471cbb60d40a938 --- /dev/null +++ b/src/client/core/OpenAPI.ts @@ -0,0 +1,31 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ApiRequestOptions } from './ApiRequestOptions'; + +type Resolver<T> = (options: ApiRequestOptions) => Promise<T>; +type Headers = Record<string, string>; + +export type OpenAPIConfig = { + BASE: string; + VERSION: string; + WITH_CREDENTIALS: boolean; + CREDENTIALS: 'include' | 'omit' | 'same-origin'; + TOKEN?: string | Resolver<string>; + USERNAME?: string | Resolver<string>; + PASSWORD?: string | Resolver<string>; + HEADERS?: Headers | Resolver<Headers>; + ENCODE_PATH?: (path: string) => string; +}; + +export const OpenAPI: OpenAPIConfig = { + BASE: 'http://localhost:9999/api', + VERSION: '1.0.0', + WITH_CREDENTIALS: false, + CREDENTIALS: 'include', + TOKEN: undefined, + USERNAME: undefined, + PASSWORD: undefined, + HEADERS: undefined, + ENCODE_PATH: undefined, +}; diff --git a/src/client/core/request.ts b/src/client/core/request.ts new file mode 100644 index 0000000000000000000000000000000000000000..2b5375ab44ec20863646a5debca820b42c2b7500 --- /dev/null +++ b/src/client/core/request.ts @@ -0,0 +1,304 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import axios from 'axios'; +import type { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios'; +import FormData from 'form-data'; + +import { ApiError } from './ApiError'; +import type { ApiRequestOptions } from './ApiRequestOptions'; +import type { ApiResult } from './ApiResult'; +import { CancelablePromise } from './CancelablePromise'; +import type { OnCancel } from './CancelablePromise'; +import type { OpenAPIConfig } from './OpenAPI'; + +const isDefined = <T>(value: T | null | undefined): value is Exclude<T, null | undefined> => { + return value !== undefined && value !== null; +}; + +const isString = (value: any): value is string => { + return typeof value === 'string'; +}; + +const isStringWithValue = (value: any): value is string => { + return isString(value) && value !== ''; +}; + +const isBlob = (value: any): value is Blob => { + return ( + typeof value === 'object' && + typeof value.type === 'string' && + typeof value.stream === 'function' && + typeof value.arrayBuffer === 'function' && + typeof value.constructor === 'function' && + typeof value.constructor.name === 'string' && + /^(Blob|File)$/.test(value.constructor.name) && + /^(Blob|File)$/.test(value[Symbol.toStringTag]) + ); +}; + +const isFormData = (value: any): value is FormData => { + return value instanceof FormData; +}; + +const isSuccess = (status: number): boolean => { + return status >= 200 && status < 300; +}; + +const base64 = (str: string): string => { + try { + return btoa(str); + } catch (err) { + // @ts-ignore + return Buffer.from(str).toString('base64'); + } +}; + +const getQueryString = (params: Record<string, any>): string => { + const qs: string[] = []; + + const append = (key: string, value: any) => { + qs.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`); + }; + + const process = (key: string, value: any) => { + if (isDefined(value)) { + if (Array.isArray(value)) { + value.forEach(v => { + process(key, v); + }); + } else if (typeof value === 'object') { + Object.entries(value).forEach(([k, v]) => { + process(`${key}[${k}]`, v); + }); + } else { + append(key, value); + } + } + }; + + Object.entries(params).forEach(([key, value]) => { + process(key, value); + }); + + if (qs.length > 0) { + return `?${qs.join('&')}`; + } + + return ''; +}; + +const getUrl = (config: OpenAPIConfig, options: ApiRequestOptions): string => { + const encoder = config.ENCODE_PATH || encodeURI; + + const path = options.url + .replace('{api-version}', config.VERSION) + .replace(/{(.*?)}/g, (substring: string, group: string) => { + if (options.path?.hasOwnProperty(group)) { + return encoder(String(options.path[group])); + } + return substring; + }); + + const url = `${config.BASE}${path}`; + if (options.query) { + return `${url}${getQueryString(options.query)}`; + } + return url; +}; + +const getFormData = (options: ApiRequestOptions): FormData | undefined => { + if (options.formData) { + const formData = new FormData(); + + const process = (key: string, value: any) => { + if (isString(value) || isBlob(value)) { + formData.append(key, value); + } else { + formData.append(key, JSON.stringify(value)); + } + }; + + Object.entries(options.formData) + .filter(([_, value]) => isDefined(value)) + .forEach(([key, value]) => { + if (Array.isArray(value)) { + value.forEach(v => process(key, v)); + } else { + process(key, value); + } + }); + + return formData; + } + return undefined; +}; + +type Resolver<T> = (options: ApiRequestOptions) => Promise<T>; + +const resolve = async <T>(options: ApiRequestOptions, resolver?: T | Resolver<T>): Promise<T | undefined> => { + if (typeof resolver === 'function') { + return (resolver as Resolver<T>)(options); + } + return resolver; +}; + +const getHeaders = async (config: OpenAPIConfig, options: ApiRequestOptions, formData?: FormData): Promise<Record<string, string>> => { + const token = await resolve(options, config.TOKEN); + const username = await resolve(options, config.USERNAME); + const password = await resolve(options, config.PASSWORD); + const additionalHeaders = await resolve(options, config.HEADERS); + const formHeaders = typeof formData?.getHeaders === 'function' && formData?.getHeaders() || {} + + const headers = Object.entries({ + Accept: 'application/json', + ...additionalHeaders, + ...options.headers, + ...formHeaders, + }) + .filter(([_, value]) => isDefined(value)) + .reduce((headers, [key, value]) => ({ + ...headers, + [key]: String(value), + }), {} as Record<string, string>); + + if (isStringWithValue(token)) { + headers['Authorization'] = `Bearer ${token}`; + } + + if (isStringWithValue(username) && isStringWithValue(password)) { + const credentials = base64(`${username}:${password}`); + headers['Authorization'] = `Basic ${credentials}`; + } + + if (options.body) { + if (options.mediaType) { + headers['Content-Type'] = options.mediaType; + } else if (isBlob(options.body)) { + headers['Content-Type'] = options.body.type || 'application/octet-stream'; + } else if (isString(options.body)) { + headers['Content-Type'] = 'text/plain'; + } else if (!isFormData(options.body)) { + headers['Content-Type'] = 'application/json'; + } + } + + return headers; +}; + +const getRequestBody = (options: ApiRequestOptions): any => { + if (options.body) { + return options.body; + } + return undefined; +}; + +const sendRequest = async <T>( + config: OpenAPIConfig, + options: ApiRequestOptions, + url: string, + body: any, + formData: FormData | undefined, + headers: Record<string, string>, + onCancel: OnCancel +): Promise<AxiosResponse<T>> => { + const source = axios.CancelToken.source(); + + const requestConfig: AxiosRequestConfig = { + url, + headers, + data: body ?? formData, + method: options.method, + withCredentials: config.WITH_CREDENTIALS, + cancelToken: source.token, + }; + + onCancel(() => source.cancel('The user aborted a request.')); + + try { + return await axios.request(requestConfig); + } catch (error) { + const axiosError = error as AxiosError<T>; + if (axiosError.response) { + return axiosError.response; + } + throw error; + } +}; + +const getResponseHeader = (response: AxiosResponse<any>, responseHeader?: string): string | undefined => { + if (responseHeader) { + const content = response.headers[responseHeader]; + if (isString(content)) { + return content; + } + } + return undefined; +}; + +const getResponseBody = (response: AxiosResponse<any>): any => { + if (response.status !== 204) { + return response.data; + } + return undefined; +}; + +const catchErrorCodes = (options: ApiRequestOptions, result: ApiResult): void => { + const errors: Record<number, string> = { + 400: 'Bad Request', + 401: 'Unauthorized', + 403: 'Forbidden', + 404: 'Not Found', + 500: 'Internal Server Error', + 502: 'Bad Gateway', + 503: 'Service Unavailable', + ...options.errors, + } + + const error = errors[result.status]; + if (error) { + throw new ApiError(options, result, error); + } + + if (!result.ok) { + throw new ApiError(options, result, 'Generic Error'); + } +}; + +/** + * Request method + * @param config The OpenAPI configuration object + * @param options The request options from the service + * @returns CancelablePromise<T> + * @throws ApiError + */ +export const request = <T>(config: OpenAPIConfig, options: ApiRequestOptions): CancelablePromise<T> => { + return new CancelablePromise(async (resolve, reject, onCancel) => { + try { + const url = getUrl(config, options); + const formData = getFormData(options); + const body = getRequestBody(options); + const headers = await getHeaders(config, options, formData); + + if (!onCancel.isCancelled) { + const response = await sendRequest<T>(config, options, url, body, formData, headers, onCancel); + const responseBody = getResponseBody(response); + const responseHeader = getResponseHeader(response, options.responseHeader); + + const result: ApiResult = { + url, + ok: isSuccess(response.status), + status: response.status, + statusText: response.statusText, + body: responseHeader ?? responseBody, + }; + + catchErrorCodes(options, result); + + resolve(result.body); + } + } catch (error) { + reject(error); + } + }); +}; diff --git a/src/client/index.ts b/src/client/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..9f41f3e7ede88b28944d1b09ce2271ac74f81662 --- /dev/null +++ b/src/client/index.ts @@ -0,0 +1,27 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export { ApiError } from './core/ApiError'; +export { CancelablePromise, CancelError } from './core/CancelablePromise'; +export { OpenAPI } from './core/OpenAPI'; +export type { OpenAPIConfig } from './core/OpenAPI'; + +export type { BucketIn } from './models/BucketIn'; +export type { BucketOut } from './models/BucketOut'; +export type { BucketPermission } from './models/BucketPermission'; +export type { BucketPermissionParameters } from './models/BucketPermissionParameters'; +export type { ErrorDetail } from './models/ErrorDetail'; +export type { HTTPValidationError } from './models/HTTPValidationError'; +export { PermissionEnum } from './models/PermissionEnum'; +export type { S3Key } from './models/S3Key'; +export type { S3ObjectMetaInformation } from './models/S3ObjectMetaInformation'; +export type { User } from './models/User'; +export type { ValidationError } from './models/ValidationError'; + +export { AuthService } from './services/AuthService'; +export { BucketService } from './services/BucketService'; +export { BucketPermissionsService } from './services/BucketPermissionsService'; +export { KeyService } from './services/KeyService'; +export { MiscellaneousService } from './services/MiscellaneousService'; +export { ObjectService } from './services/ObjectService'; +export { UserService } from './services/UserService'; diff --git a/src/client/models/BucketIn.ts b/src/client/models/BucketIn.ts new file mode 100644 index 0000000000000000000000000000000000000000..530031e6c9db2eed482ac3b2a05f512112fdcb3e --- /dev/null +++ b/src/client/models/BucketIn.ts @@ -0,0 +1,18 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * Schema for creating a new bucket. + */ +export type BucketIn = { + /** + * Name of the bucket + */ + name: string; + /** + * Description of the bucket + */ + description: string; +}; + diff --git a/src/client/models/BucketOut.ts b/src/client/models/BucketOut.ts new file mode 100644 index 0000000000000000000000000000000000000000..d3c68512a9a8cee4ca8681ee5fa4af98fe7a8268 --- /dev/null +++ b/src/client/models/BucketOut.ts @@ -0,0 +1,26 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * Schema for answering a request with a bucket. + */ +export type BucketOut = { + /** + * Name of the bucket + */ + name: string; + /** + * Description of the bucket + */ + description: string; + /** + * Time when the bucket was created + */ + created_at: string; + /** + * Username of the owner + */ + owner: string; +}; + diff --git a/src/client/models/BucketPermission.ts b/src/client/models/BucketPermission.ts new file mode 100644 index 0000000000000000000000000000000000000000..78679591d26554041688efc915bbc8c1a3d1e113 --- /dev/null +++ b/src/client/models/BucketPermission.ts @@ -0,0 +1,36 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { PermissionEnum } from './PermissionEnum'; + +/** + * Schema for the bucket permissions. + */ +export type BucketPermission = { + /** + * Start date of permission + */ + from_timestamp?: string; + /** + * End date of permission + */ + to_timestamp?: string; + /** + * Prefix of subfolder + */ + file_prefix?: string; + /** + * Permission + */ + permission?: (PermissionEnum | string); + /** + * Name of User + */ + username: string; + /** + * Name of Bucket + */ + bucket_name: string; +}; + diff --git a/src/client/models/BucketPermissionParameters.ts b/src/client/models/BucketPermissionParameters.ts new file mode 100644 index 0000000000000000000000000000000000000000..6fb78dd8d4861faddaef3191fd8b3c91bc690dd2 --- /dev/null +++ b/src/client/models/BucketPermissionParameters.ts @@ -0,0 +1,28 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { PermissionEnum } from './PermissionEnum'; + +/** + * Schema for the parameters of a bucket permission. + */ +export type BucketPermissionParameters = { + /** + * Start date of permission + */ + from_timestamp?: string; + /** + * End date of permission + */ + to_timestamp?: string; + /** + * Prefix of subfolder + */ + file_prefix?: string; + /** + * Permission + */ + permission?: (PermissionEnum | string); +}; + diff --git a/src/client/models/ErrorDetail.ts b/src/client/models/ErrorDetail.ts new file mode 100644 index 0000000000000000000000000000000000000000..b01e5aa28734c901620e98f8795e9528004d1513 --- /dev/null +++ b/src/client/models/ErrorDetail.ts @@ -0,0 +1,14 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * Schema for a error due to a rejected request. + */ +export type ErrorDetail = { + /** + * Detail about the occurred error + */ + detail: string; +}; + diff --git a/src/client/models/HTTPValidationError.ts b/src/client/models/HTTPValidationError.ts new file mode 100644 index 0000000000000000000000000000000000000000..a8c21354123500873c7548194a36421eb8316c78 --- /dev/null +++ b/src/client/models/HTTPValidationError.ts @@ -0,0 +1,10 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ValidationError } from './ValidationError'; + +export type HTTPValidationError = { + detail?: Array<ValidationError>; +}; + diff --git a/src/client/models/PermissionEnum.ts b/src/client/models/PermissionEnum.ts new file mode 100644 index 0000000000000000000000000000000000000000..ae0d72869da4a2291f3362cae30e2376b46af3f9 --- /dev/null +++ b/src/client/models/PermissionEnum.ts @@ -0,0 +1,12 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * Enumeration for the possible permission on a bucket. + */ +export enum PermissionEnum { + READ = 'READ', + WRITE = 'WRITE', + READWRITE = 'READWRITE', +} diff --git a/src/client/models/S3Key.ts b/src/client/models/S3Key.ts new file mode 100644 index 0000000000000000000000000000000000000000..5b3d7909c65cfa03f76252074b0b68bebd41826c --- /dev/null +++ b/src/client/models/S3Key.ts @@ -0,0 +1,22 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * Schema for a S3 key associated with a user. + */ +export type S3Key = { + /** + * Username of the user of that access key + */ + user: string; + /** + * ID of the S3 access key + */ + access_key: string; + /** + * Secret of the S3 access key + */ + secret_key: string; +}; + diff --git a/src/client/models/S3ObjectMetaInformation.ts b/src/client/models/S3ObjectMetaInformation.ts new file mode 100644 index 0000000000000000000000000000000000000000..4292c7e82fe0982d788527cd355e4c6f0595ebc3 --- /dev/null +++ b/src/client/models/S3ObjectMetaInformation.ts @@ -0,0 +1,26 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * Schema for the meta-information about a S3 object. + */ +export type S3ObjectMetaInformation = { + /** + * Key of the Object in the S3 store + */ + key: string; + /** + * Name of the Bucket in which the object is + */ + bucket: string; + /** + * Size of the object in Bytes + */ + size: number; + /** + * Last time the object was modified + */ + last_modified: string; +}; + diff --git a/src/client/models/User.ts b/src/client/models/User.ts new file mode 100644 index 0000000000000000000000000000000000000000..58137cd888487ba4aff980746431f0e8c35e943c --- /dev/null +++ b/src/client/models/User.ts @@ -0,0 +1,22 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * Schema for a user. + */ +export type User = { + /** + * ID of the user + */ + uid: string; + /** + * Username of the user + */ + username: string; + /** + * Full Name of the user + */ + display_name: string; +}; + diff --git a/src/client/models/ValidationError.ts b/src/client/models/ValidationError.ts new file mode 100644 index 0000000000000000000000000000000000000000..16dc86a6807d9ad1f7e9d06638d4bd002682dc42 --- /dev/null +++ b/src/client/models/ValidationError.ts @@ -0,0 +1,10 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +export type ValidationError = { + loc: Array<(string | number)>; + msg: string; + type: string; +}; + diff --git a/src/client/services/AuthService.ts b/src/client/services/AuthService.ts new file mode 100644 index 0000000000000000000000000000000000000000..0a4680da8ea0e3d7b2a02aca271961c34c424ec3 --- /dev/null +++ b/src/client/services/AuthService.ts @@ -0,0 +1,36 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { CancelablePromise } from '../core/CancelablePromise'; +import { OpenAPI } from '../core/OpenAPI'; +import { request as __request } from '../core/request'; + +export class AuthService { + + /** + * Life Science Login Callback + * Callback for the Life Science Identity Provider. + * + * To start the login process visit the route [login route](/api/auth/login/) + * + * If the user is already known to the system, then a JWT token will be created and sent via the 'set-cookie' header. + * The key for this Cookie is 'bearer'. + * + * If the user is new, he will be created and then a JWT token is issued. + * + * This JWT has to be sent to all authorized endpoints via the HTTPBearer scheme. + * + * @returns void + * @throws ApiError + */ + public static authLoginCallback(): CancelablePromise<void> { + return __request(OpenAPI, { + method: 'GET', + url: '/auth/callback', + errors: { + 302: `Successful Response`, + }, + }); + } + +} diff --git a/src/client/services/BucketPermissionsService.ts b/src/client/services/BucketPermissionsService.ts new file mode 100644 index 0000000000000000000000000000000000000000..871549bd7b3a928aa0332c178ca5d6e70f3da0b1 --- /dev/null +++ b/src/client/services/BucketPermissionsService.ts @@ -0,0 +1,185 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { BucketPermission } from '../models/BucketPermission'; +import type { BucketPermissionParameters } from '../models/BucketPermissionParameters'; + +import type { CancelablePromise } from '../core/CancelablePromise'; +import { OpenAPI } from '../core/OpenAPI'; +import { request as __request } from '../core/request'; + +export class BucketPermissionsService { + + /** + * Get permission for bucket and user combination. + * Get the bucket permissions for the specific combination of bucket and user. + * + * The owner of the bucket and the grantee of the permission can view it. + * + * @param bucketName Name of bucket + * @param username Username of a user + * @returns BucketPermission Successful Response + * @throws ApiError + */ + public static bucketPermissionsGetPermissionForBucket( + bucketName: string, + username: string, + ): CancelablePromise<BucketPermission> { + return __request(OpenAPI, { + method: 'GET', + url: '/permissions/bucket/{bucket_name}/user/{username}', + path: { + 'bucket_name': bucketName, + 'username': username, + }, + errors: { + 400: `Error decoding JWT Token`, + 403: `Not authenticated`, + 404: `Entity not Found`, + 422: `Validation Error`, + }, + }); + } + + /** + * Update a bucket permission + * Update a permission for a bucket and user. + * + * @param username Username of a user + * @param bucketName Name of bucket + * @param requestBody + * @returns BucketPermission Successful Response + * @throws ApiError + */ + public static bucketPermissionsUpdatePermission( + username: string, + bucketName: string, + requestBody: BucketPermissionParameters, + ): CancelablePromise<BucketPermission> { + return __request(OpenAPI, { + method: 'PUT', + url: '/permissions/bucket/{bucket_name}/user/{username}', + path: { + 'username': username, + 'bucket_name': bucketName, + }, + body: requestBody, + mediaType: 'application/json', + errors: { + 400: `Error decoding JWT Token`, + 403: `Not authenticated`, + 404: `Entity not Found`, + 422: `Validation Error`, + }, + }); + } + + /** + * Delete a bucket permission + * Delete the bucket permissions for the specific combination of bucket and user. + * + * The owner of the bucket and the grantee of the permission can delete it. + * + * @param bucketName Name of bucket + * @param username Username of a user + * @returns void + * @throws ApiError + */ + public static bucketPermissionsDeletePermissionForBucket( + bucketName: string, + username: string, + ): CancelablePromise<void> { + return __request(OpenAPI, { + method: 'DELETE', + url: '/permissions/bucket/{bucket_name}/user/{username}', + path: { + 'bucket_name': bucketName, + 'username': username, + }, + errors: { + 400: `Error decoding JWT Token`, + 403: `Not authenticated`, + 404: `Entity not Found`, + 422: `Validation Error`, + }, + }); + } + + /** + * Get all permissions for a bucket. + * List all the bucket permissions for the given bucket. + * + * @param bucketName Name of bucket + * @returns BucketPermission Successful Response + * @throws ApiError + */ + public static bucketPermissionsListPermissionsPerBucket( + bucketName: string, + ): CancelablePromise<Array<BucketPermission>> { + return __request(OpenAPI, { + method: 'GET', + url: '/permissions/bucket/{bucket_name}', + path: { + 'bucket_name': bucketName, + }, + errors: { + 400: `Error decoding JWT Token`, + 403: `Not authenticated`, + 404: `Entity not Found`, + 422: `Validation Error`, + }, + }); + } + + /** + * Get all permissions for a user. + * List all the bucket permissions for the given user. + * + * @param username Username of a user + * @returns BucketPermission Successful Response + * @throws ApiError + */ + public static bucketPermissionsListPermissionsPerUser( + username: string, + ): CancelablePromise<Array<BucketPermission>> { + return __request(OpenAPI, { + method: 'GET', + url: '/permissions/user/{username}', + path: { + 'username': username, + }, + errors: { + 400: `Error decoding JWT Token`, + 403: `Not authenticated`, + 404: `Entity not Found`, + 422: `Validation Error`, + }, + }); + } + + /** + * Create a permission. + * Create a permission for a bucket and user. + * + * @param requestBody + * @returns BucketPermission Successful Response + * @throws ApiError + */ + public static bucketPermissionsCreatePermission( + requestBody: BucketPermission, + ): CancelablePromise<BucketPermission> { + return __request(OpenAPI, { + method: 'POST', + url: '/permissions/', + body: requestBody, + mediaType: 'application/json', + errors: { + 400: `Error decoding JWT Token`, + 403: `Not authenticated`, + 404: `Entity not Found`, + 422: `Validation Error`, + }, + }); + } + +} diff --git a/src/client/services/BucketService.ts b/src/client/services/BucketService.ts new file mode 100644 index 0000000000000000000000000000000000000000..d0b4457b9d0f110dcf92600a17780e821b60538c --- /dev/null +++ b/src/client/services/BucketService.ts @@ -0,0 +1,173 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { BucketIn } from '../models/BucketIn'; +import type { BucketOut } from '../models/BucketOut'; +import type { S3ObjectMetaInformation } from '../models/S3ObjectMetaInformation'; + +import type { CancelablePromise } from '../core/CancelablePromise'; +import { OpenAPI } from '../core/OpenAPI'; +import { request as __request } from '../core/request'; + +export class BucketService { + + /** + * List buckets of user + * List the buckets of the current user where the user has READ permissions for. + * + * @returns BucketOut Successful Response + * @throws ApiError + */ + public static bucketListBuckets(): CancelablePromise<Array<BucketOut>> { + return __request(OpenAPI, { + method: 'GET', + url: '/buckets/', + errors: { + 400: `Error decoding JWT Token`, + 403: `Not authenticated`, + 404: `Entity not Found`, + }, + }); + } + + /** + * Create a bucket for the current user + * Create a bucket for the current user. + * + * The name of the bucket has some constraints. + * For more information see the [Ceph documentation](https://docs.ceph.com/en/quincy/radosgw/s3/bucketops/#constraints) + * + * @param requestBody + * @returns BucketOut Successful Response + * @throws ApiError + */ + public static bucketCreateBucket( + requestBody: BucketIn, + ): CancelablePromise<BucketOut> { + return __request(OpenAPI, { + method: 'POST', + url: '/buckets/', + body: requestBody, + mediaType: 'application/json', + errors: { + 400: `Error decoding JWT Token`, + 403: `Not authenticated`, + 404: `Entity not Found`, + 422: `Validation Error`, + }, + }); + } + + /** + * Get a bucket by its name + * Get a bucket by its name if the current user has READ permissions for the bucket. + * + * @param bucketName Name of bucket + * @returns BucketOut Successful Response + * @throws ApiError + */ + public static bucketGetBucket( + bucketName: string, + ): CancelablePromise<BucketOut> { + return __request(OpenAPI, { + method: 'GET', + url: '/buckets/{bucket_name}', + path: { + 'bucket_name': bucketName, + }, + errors: { + 400: `Error decoding JWT Token`, + 403: `Not authenticated`, + 404: `Entity not Found`, + 422: `Validation Error`, + }, + }); + } + + /** + * Delete a bucket + * Delete a bucket by its name. Only the owner of the bucket can delete the bucket. + * + * @param bucketName Name of bucket + * @param forceDelete Delete even non-empty bucket + * @returns void + * @throws ApiError + */ + public static bucketDeleteBucket( + bucketName: string, + forceDelete: boolean = false, + ): CancelablePromise<void> { + return __request(OpenAPI, { + method: 'DELETE', + url: '/buckets/{bucket_name}', + path: { + 'bucket_name': bucketName, + }, + query: { + 'force_delete': forceDelete, + }, + errors: { + 400: `Error decoding JWT Token`, + 403: `Not authenticated`, + 404: `Entity not Found`, + 422: `Validation Error`, + }, + }); + } + + /** + * Get the metadata of the objects in the bucket + * Get the metadata of the objects in the bucket. + * + * @param bucketName Name of bucket + * @returns S3ObjectMetaInformation Successful Response + * @throws ApiError + */ + public static objectGetBucketObjects( + bucketName: string, + ): CancelablePromise<Array<S3ObjectMetaInformation>> { + return __request(OpenAPI, { + method: 'GET', + url: '/buckets/{bucket_name}/objects', + path: { + 'bucket_name': bucketName, + }, + errors: { + 400: `Error decoding JWT Token`, + 403: `Not authenticated`, + 404: `Entity not Found`, + 422: `Validation Error`, + }, + }); + } + + /** + * Get the metadata about a specific object + * Get the metadata of a specific object in a bucket. + * + * @param objectPath + * @param bucketName Name of bucket + * @returns S3ObjectMetaInformation Successful Response + * @throws ApiError + */ + public static objectGetBucketObject( + objectPath: string, + bucketName: string, + ): CancelablePromise<S3ObjectMetaInformation> { + return __request(OpenAPI, { + method: 'GET', + url: '/buckets/{bucket_name}/objects/{object_path}', + path: { + 'object_path': objectPath, + 'bucket_name': bucketName, + }, + errors: { + 400: `Error decoding JWT Token`, + 403: `Not authenticated`, + 404: `Entity not Found`, + 422: `Validation Error`, + }, + }); + } + +} diff --git a/src/client/services/KeyService.ts b/src/client/services/KeyService.ts new file mode 100644 index 0000000000000000000000000000000000000000..6f6957867d3e7369fecd3a3dbd1463849a3159a3 --- /dev/null +++ b/src/client/services/KeyService.ts @@ -0,0 +1,122 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { S3Key } from '../models/S3Key'; + +import type { CancelablePromise } from '../core/CancelablePromise'; +import { OpenAPI } from '../core/OpenAPI'; +import { request as __request } from '../core/request'; + +export class KeyService { + + /** + * Get the S3 Access keys from a user + * Get all the S3 Access keys for a specific user. + * + * @param username Username of a user + * @returns S3Key Successful Response + * @throws ApiError + */ + public static keyGetUserKeys( + username: string, + ): CancelablePromise<Array<S3Key>> { + return __request(OpenAPI, { + method: 'GET', + url: '/users/{username}/keys', + path: { + 'username': username, + }, + errors: { + 400: `Error decoding JWT Token`, + 403: `Not authenticated`, + 404: `Entity not Found`, + 422: `Validation Error`, + }, + }); + } + + /** + * Create a Access key for a user + * Create a S3 Access key for a specific user. + * + * @param username Username of a user + * @returns S3Key Successful Response + * @throws ApiError + */ + public static keyCreateUserKey( + username: string, + ): CancelablePromise<S3Key> { + return __request(OpenAPI, { + method: 'POST', + url: '/users/{username}/keys', + path: { + 'username': username, + }, + errors: { + 400: `Error decoding JWT Token`, + 403: `Not authenticated`, + 404: `Entity not Found`, + 422: `Validation Error`, + }, + }); + } + + /** + * Get a specific S3 Access key from a user + * Get a specific S3 Access Key for a specific user. + * + * @param accessId ID of the S3 access key + * @param username Username of a user + * @returns S3Key Successful Response + * @throws ApiError + */ + public static keyGetUserKey( + accessId: string, + username: string, + ): CancelablePromise<S3Key> { + return __request(OpenAPI, { + method: 'GET', + url: '/users/{username}/keys/{access_id}', + path: { + 'access_id': accessId, + 'username': username, + }, + errors: { + 400: `Error decoding JWT Token`, + 403: `Not authenticated`, + 404: `Entity not Found`, + 422: `Validation Error`, + }, + }); + } + + /** + * Delete a specific S3 Access key from a user + * Delete a specific S3 Access key for a specific user. + * + * @param accessId ID of the S3 access key + * @param username Username of a user + * @returns void + * @throws ApiError + */ + public static keyDeleteUserKey( + accessId: string, + username: string, + ): CancelablePromise<void> { + return __request(OpenAPI, { + method: 'DELETE', + url: '/users/{username}/keys/{access_id}', + path: { + 'access_id': accessId, + 'username': username, + }, + errors: { + 400: `Error decoding JWT Token`, + 403: `Not authenticated`, + 404: `Entity not Found`, + 422: `Validation Error`, + }, + }); + } + +} diff --git a/src/client/services/MiscellaneousService.ts b/src/client/services/MiscellaneousService.ts new file mode 100644 index 0000000000000000000000000000000000000000..b8b0fc0229ea08bc8e78cf9f78ee2dd06ad1051a --- /dev/null +++ b/src/client/services/MiscellaneousService.ts @@ -0,0 +1,27 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { CancelablePromise } from '../core/CancelablePromise'; +import { OpenAPI } from '../core/OpenAPI'; +import { request as __request } from '../core/request'; + +export class MiscellaneousService { + + /** + * Health Check + * Check the health of the service. + * + * @returns any Service Health is OK + * @throws ApiError + */ + public static miscellaneousHealthCheck(): CancelablePromise<any> { + return __request(OpenAPI, { + method: 'GET', + url: '/health', + errors: { + 500: `Service Health is not OK`, + }, + }); + } + +} diff --git a/src/client/services/ObjectService.ts b/src/client/services/ObjectService.ts new file mode 100644 index 0000000000000000000000000000000000000000..84598b91af3530fd612d76dca2a9233150711b6b --- /dev/null +++ b/src/client/services/ObjectService.ts @@ -0,0 +1,67 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { S3ObjectMetaInformation } from '../models/S3ObjectMetaInformation'; + +import type { CancelablePromise } from '../core/CancelablePromise'; +import { OpenAPI } from '../core/OpenAPI'; +import { request as __request } from '../core/request'; + +export class ObjectService { + + /** + * Get the metadata of the objects in the bucket + * Get the metadata of the objects in the bucket. + * + * @param bucketName Name of bucket + * @returns S3ObjectMetaInformation Successful Response + * @throws ApiError + */ + public static objectGetBucketObjects( + bucketName: string, + ): CancelablePromise<Array<S3ObjectMetaInformation>> { + return __request(OpenAPI, { + method: 'GET', + url: '/buckets/{bucket_name}/objects', + path: { + 'bucket_name': bucketName, + }, + errors: { + 400: `Error decoding JWT Token`, + 403: `Not authenticated`, + 404: `Entity not Found`, + 422: `Validation Error`, + }, + }); + } + + /** + * Get the metadata about a specific object + * Get the metadata of a specific object in a bucket. + * + * @param objectPath + * @param bucketName Name of bucket + * @returns S3ObjectMetaInformation Successful Response + * @throws ApiError + */ + public static objectGetBucketObject( + objectPath: string, + bucketName: string, + ): CancelablePromise<S3ObjectMetaInformation> { + return __request(OpenAPI, { + method: 'GET', + url: '/buckets/{bucket_name}/objects/{object_path}', + path: { + 'object_path': objectPath, + 'bucket_name': bucketName, + }, + errors: { + 400: `Error decoding JWT Token`, + 403: `Not authenticated`, + 404: `Entity not Found`, + 422: `Validation Error`, + }, + }); + } + +} diff --git a/src/client/services/UserService.ts b/src/client/services/UserService.ts new file mode 100644 index 0000000000000000000000000000000000000000..fd37cb612103acb573849874d4fa6e620372f0d0 --- /dev/null +++ b/src/client/services/UserService.ts @@ -0,0 +1,168 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { S3Key } from '../models/S3Key'; +import type { User } from '../models/User'; + +import type { CancelablePromise } from '../core/CancelablePromise'; +import { OpenAPI } from '../core/OpenAPI'; +import { request as __request } from '../core/request'; + +export class UserService { + + /** + * Get the logged in user + * Return the user associated with the used JWT. + * + * @returns User Successful Response + * @throws ApiError + */ + public static userGetLoggedInUser(): CancelablePromise<User> { + return __request(OpenAPI, { + method: 'GET', + url: '/users/me', + errors: { + 400: `Error decoding JWT Token`, + 403: `Not authenticated`, + 404: `Entity not Found`, + }, + }); + } + + /** + * Get a user by its username + * Return the user with the specific username. A user can only view himself. + * + * @param username Username of a user + * @returns User Successful Response + * @throws ApiError + */ + public static userGetUser( + username: string, + ): CancelablePromise<User> { + return __request(OpenAPI, { + method: 'GET', + url: '/users/{username}', + path: { + 'username': username, + }, + errors: { + 400: `Error decoding JWT Token`, + 403: `Not authenticated`, + 404: `Entity not Found`, + 422: `Validation Error`, + }, + }); + } + + /** + * Get the S3 Access keys from a user + * Get all the S3 Access keys for a specific user. + * + * @param username Username of a user + * @returns S3Key Successful Response + * @throws ApiError + */ + public static keyGetUserKeys( + username: string, + ): CancelablePromise<Array<S3Key>> { + return __request(OpenAPI, { + method: 'GET', + url: '/users/{username}/keys', + path: { + 'username': username, + }, + errors: { + 400: `Error decoding JWT Token`, + 403: `Not authenticated`, + 404: `Entity not Found`, + 422: `Validation Error`, + }, + }); + } + + /** + * Create a Access key for a user + * Create a S3 Access key for a specific user. + * + * @param username Username of a user + * @returns S3Key Successful Response + * @throws ApiError + */ + public static keyCreateUserKey( + username: string, + ): CancelablePromise<S3Key> { + return __request(OpenAPI, { + method: 'POST', + url: '/users/{username}/keys', + path: { + 'username': username, + }, + errors: { + 400: `Error decoding JWT Token`, + 403: `Not authenticated`, + 404: `Entity not Found`, + 422: `Validation Error`, + }, + }); + } + + /** + * Get a specific S3 Access key from a user + * Get a specific S3 Access Key for a specific user. + * + * @param accessId ID of the S3 access key + * @param username Username of a user + * @returns S3Key Successful Response + * @throws ApiError + */ + public static keyGetUserKey( + accessId: string, + username: string, + ): CancelablePromise<S3Key> { + return __request(OpenAPI, { + method: 'GET', + url: '/users/{username}/keys/{access_id}', + path: { + 'access_id': accessId, + 'username': username, + }, + errors: { + 400: `Error decoding JWT Token`, + 403: `Not authenticated`, + 404: `Entity not Found`, + 422: `Validation Error`, + }, + }); + } + + /** + * Delete a specific S3 Access key from a user + * Delete a specific S3 Access key for a specific user. + * + * @param accessId ID of the S3 access key + * @param username Username of a user + * @returns void + * @throws ApiError + */ + public static keyDeleteUserKey( + accessId: string, + username: string, + ): CancelablePromise<void> { + return __request(OpenAPI, { + method: 'DELETE', + url: '/users/{username}/keys/{access_id}', + path: { + 'access_id': accessId, + 'username': username, + }, + errors: { + 400: `Error decoding JWT Token`, + 403: `Not authenticated`, + 404: `Entity not Found`, + 422: `Validation Error`, + }, + }); + } + +} diff --git a/src/components/NavbarTop.vue b/src/components/NavbarTop.vue index 040a4f677b61dda6b1cc12add772a07f2d3ddc69..edf18f4400735c1f171ae6eab84b1bdf42cec32b 100644 --- a/src/components/NavbarTop.vue +++ b/src/components/NavbarTop.vue @@ -1,11 +1,41 @@ <script setup lang="ts"> import BootstrapIcon from "./BootstrapIcon.vue"; +import { reactive, onBeforeUnmount, onMounted } from "vue"; +import { MiscellaneousService } from "@/client"; + +const api_connection = reactive({ connected: false, timer: null }); +let timer: ReturnType<typeof setInterval> | undefined = undefined; +function checkApiHealth() { + MiscellaneousService.miscellaneousHealthCheck() + .then(() => { + api_connection.connected = true; + }) + .catch(() => { + api_connection.connected = false; + }); +} + +onMounted(() => { + checkApiHealth(); + timer = setInterval(checkApiHealth, 10000); +}); +onBeforeUnmount(() => { + clearInterval(timer); +}); </script> <template> <nav class="navbar fixed-top navbar-expand-lg bg-dark"> <div class="container-fluid"> - <router-link class="navbar-brand ms-3" to="/">S3 Proxy</router-link> + <router-link class="navbar-brand ms-3 text-light" to="/" + >S3 Proxy</router-link + > + <span + v-if="!api_connection.connected" + class="navbar-text text-light text-bg-danger p-1" + > + Backend not reachable + </span> <div class="dropdown d-flex me-3"> <a href="#" diff --git a/tsconfig.json b/tsconfig.json index 8d2359999ec907a1ff44010999ff814fde4897a5..0f216f1416aff7a546a0218b93976f2282c8460f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,6 +2,8 @@ "extends": "@vue/tsconfig/tsconfig.web.json", "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], "compilerOptions": { + "lib": ["...", "dom"], + "allowSyntheticDefaultImports": true, "baseUrl": ".", "paths": { "@/*": ["./src/*"]