From 32e7fb844622a960f1f35f0489f26e92e5241c02 Mon Sep 17 00:00:00 2001 From: Romain Le Cellier Date: Mon, 20 Feb 2023 15:50:06 +0100 Subject: [PATCH 01/12] =?UTF-8?q?=E2=9C=85(frontend)=20add=20test=20for=20?= =?UTF-8?q?test/deferred.ts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../js/utils/test/deferred/index.spec.ts | 20 +++++++++++++++++++ .../test/{deferred.ts => deferred/index.ts} | 0 2 files changed, 20 insertions(+) create mode 100644 src/frontend/js/utils/test/deferred/index.spec.ts rename src/frontend/js/utils/test/{deferred.ts => deferred/index.ts} (100%) diff --git a/src/frontend/js/utils/test/deferred/index.spec.ts b/src/frontend/js/utils/test/deferred/index.spec.ts new file mode 100644 index 0000000000..5b071ea900 --- /dev/null +++ b/src/frontend/js/utils/test/deferred/index.spec.ts @@ -0,0 +1,20 @@ +import fetchMock from 'fetch-mock'; +import { Deferred } from 'utils/test/deferred'; + +describe('test::deferred', () => { + afterEach(() => { + jest.clearAllMocks(); + fetchMock.restore(); + }); + + it('handles error codes', async () => { + // const todo: Todo = TodoFactory.generate(); + const responseDeferred = new Deferred(); + fetchMock.get('https://demo.endpoint/deferred/error', responseDeferred.promise); + responseDeferred.resolve({ + status: 500, + }); + const response = await fetch('https://demo.endpoint/deferred/error'); + expect(response.status).toBe(500); + }); +}); diff --git a/src/frontend/js/utils/test/deferred.ts b/src/frontend/js/utils/test/deferred/index.ts similarity index 100% rename from src/frontend/js/utils/test/deferred.ts rename to src/frontend/js/utils/test/deferred/index.ts From 2fd52d2a2d4f4eb6cbc15e962fef9a5af35ddb94 Mon Sep 17 00:00:00 2001 From: Romain Le Cellier Date: Mon, 6 Feb 2023 16:46:49 +0100 Subject: [PATCH 02/12] (chore) add openapi-typescript-codegen@0.23.0 --- src/frontend/package.json | 6 ++- .../generate_api_client_local.sh | 2 + src/frontend/yarn.lock | 42 ++++++++++++++++++- 3 files changed, 47 insertions(+), 3 deletions(-) create mode 100755 src/frontend/scripts/openapi-typescript-codegen/generate_api_client_local.sh diff --git a/src/frontend/package.json b/src/frontend/package.json index 8baae1c331..e48ee0a6ae 100644 --- a/src/frontend/package.json +++ b/src/frontend/package.json @@ -16,7 +16,8 @@ "watch-sass": "nodemon -e scss -x 'yarn build-sass'", "watch-ts": "yarn build-ts --watch", "storybook": "start-storybook -p 6006", - "build-storybook": "build-storybook" + "build-storybook": "build-storybook", + "generate:api:client:local": "./scripts/openapi-typescript-codegen/generate_api_client_local.sh" }, "repository": { "type": "git", @@ -134,5 +135,8 @@ "resolutions": { "@types/react": "18.0.27", "@types/react-dom": "18.0.10" + }, + "devDependencies": { + "openapi-typescript-codegen": "^0.23.0" } } diff --git a/src/frontend/scripts/openapi-typescript-codegen/generate_api_client_local.sh b/src/frontend/scripts/openapi-typescript-codegen/generate_api_client_local.sh new file mode 100755 index 0000000000..9c950ac029 --- /dev/null +++ b/src/frontend/scripts/openapi-typescript-codegen/generate_api_client_local.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +openapi --input http://localhost:8071/v1.0/swagger/?format=openapi --output js/api/joanie/gen --indent='2' --name ApiClientJoanie diff --git a/src/frontend/yarn.lock b/src/frontend/yarn.lock index 15488331f7..7950e1f182 100644 --- a/src/frontend/yarn.lock +++ b/src/frontend/yarn.lock @@ -15,6 +15,16 @@ "@jridgewell/gen-mapping" "^0.1.0" "@jridgewell/trace-mapping" "^0.3.9" +"@apidevtools/json-schema-ref-parser@9.0.9": + version "9.0.9" + resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.0.9.tgz#d720f9256e3609621280584f2b47ae165359268b" + integrity sha512-GBD2Le9w2+lVFoc4vswGI/TjkNIZSVp7+9xPf+X3uidBfWnAeUWmquteSyt0+VCrhNMWj/FTABISQrD3Z/YA+w== + dependencies: + "@jsdevtools/ono" "^7.1.3" + "@types/json-schema" "^7.0.6" + call-me-maybe "^1.0.1" + js-yaml "^4.1.0" + "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.18.6", "@babel/code-frame@^7.5.5", "@babel/code-frame@^7.8.3": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a" @@ -1834,6 +1844,11 @@ "@jridgewell/resolve-uri" "3.1.0" "@jridgewell/sourcemap-codec" "1.4.14" +"@jsdevtools/ono@^7.1.3": + version "7.1.3" + resolved "https://registry.yarnpkg.com/@jsdevtools/ono/-/ono-7.1.3.tgz#9df03bbd7c696a5c58885c34aa06da41c8543796" + integrity sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg== + "@mdn/browser-compat-data@^3.3.14": version "3.3.14" resolved "https://registry.yarnpkg.com/@mdn/browser-compat-data/-/browser-compat-data-3.3.14.tgz#b72a37c654e598f9ae6f8335faaee182bebc6b28" @@ -3317,7 +3332,7 @@ "@types/tough-cookie" "*" parse5 "^7.0.0" -"@types/json-schema@*", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": +"@types/json-schema@*", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.6", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": version "7.0.11" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== @@ -5034,7 +5049,7 @@ camelcase@^5.3.1: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -camelcase@^6.2.0: +camelcase@^6.2.0, camelcase@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== @@ -5357,6 +5372,11 @@ commander@^8.3.0: resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== +commander@^9.3.0: + version "9.5.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-9.5.0.tgz#bc08d1eb5cedf7ccb797a96199d41c7bc3e60d30" + integrity sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ== + commander@^9.4.1: version "9.4.1" resolved "https://registry.yarnpkg.com/commander/-/commander-9.4.1.tgz#d1dd8f2ce6faf93147295c0df13c7c21141cfbdd" @@ -9015,6 +9035,13 @@ json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^2.3.1: resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== +json-schema-ref-parser@^9.0.9: + version "9.0.9" + resolved "https://registry.yarnpkg.com/json-schema-ref-parser/-/json-schema-ref-parser-9.0.9.tgz#66ea538e7450b12af342fa3d5b8458bc1e1e013f" + integrity sha512-qcP2lmGy+JUoQJ4DOQeLaZDqH9qSkeGCK3suKWxJXS82dg728Mn3j97azDMaOUmJAN4uCq91LdPx4K7E8F1a7Q== + dependencies: + "@apidevtools/json-schema-ref-parser" "9.0.9" + json-schema-traverse@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" @@ -10090,6 +10117,17 @@ open@^8.4.0: is-docker "^2.1.1" is-wsl "^2.2.0" +openapi-typescript-codegen@^0.23.0: + version "0.23.0" + resolved "https://registry.yarnpkg.com/openapi-typescript-codegen/-/openapi-typescript-codegen-0.23.0.tgz#702a651eefc536b27e87e4ad54a80a31d36487f0" + integrity sha512-gOJXy5g3H3HlLpVNN+USrNK2i2KYBmDczk9Xk34u6JorwrGiDJZUj+al4S+i9TXdfUQ/ZaLxE59Xf3wqkxGfqA== + dependencies: + camelcase "^6.3.0" + commander "^9.3.0" + fs-extra "^10.1.0" + handlebars "^4.7.7" + json-schema-ref-parser "^9.0.9" + optionator@^0.8.1: version "0.8.3" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" From c98afaf38a1d495a1603173de7de52938598e9e1 Mon Sep 17 00:00:00 2001 From: Romain Le Cellier Date: Tue, 7 Feb 2023 16:41:40 +0100 Subject: [PATCH 03/12] =?UTF-8?q?=F0=9F=91=BD=EF=B8=8F(chore):=20generate?= =?UTF-8?q?=20joanie=20api=20client?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/frontend/.prettierignore | 1 + .../js/api/joanie/gen/ApiClientJoanie.ts | 58 ++++ .../js/api/joanie/gen/core/ApiError.ts | 24 ++ .../api/joanie/gen/core/ApiRequestOptions.ts | 16 + .../js/api/joanie/gen/core/ApiResult.ts | 10 + .../js/api/joanie/gen/core/BaseHttpRequest.ts | 13 + .../api/joanie/gen/core/CancelablePromise.ts | 128 ++++++++ .../api/joanie/gen/core/FetchHttpRequest.ts | 25 ++ .../js/api/joanie/gen/core/OpenAPI.ts | 31 ++ .../js/api/joanie/gen/core/request.ts | 306 ++++++++++++++++++ src/frontend/js/api/joanie/gen/index.ts | 30 ++ .../js/api/joanie/gen/models/Address.ts | 273 ++++++++++++++++ .../js/api/joanie/gen/models/Certificate.ts | 8 + .../gen/models/CertificationDefinition.ts | 10 + .../js/api/joanie/gen/models/Course.ts | 9 + .../js/api/joanie/gen/models/CourseRun.ts | 21 ++ .../js/api/joanie/gen/models/CreditCard.ts | 14 + .../js/api/joanie/gen/models/Enrollment.ts | 31 ++ .../js/api/joanie/gen/models/Order.ts | 35 ++ .../js/api/joanie/gen/models/Product.ts | 29 ++ .../joanie/gen/services/AddressesService.ts | 281 ++++++++++++++++ .../gen/services/CertificatesService.ts | 73 +++++ .../joanie/gen/services/CourseRunsService.ts | 31 ++ .../gen/services/CourseRunsSyncService.ts | 34 ++ .../joanie/gen/services/CreditCardsService.ts | 153 +++++++++ .../joanie/gen/services/EnrollmentsService.ts | 118 +++++++ .../api/joanie/gen/services/OrdersService.ts | 135 ++++++++ .../joanie/gen/services/PaymentsService.ts | 24 ++ .../joanie/gen/services/ProductsService.ts | 36 +++ src/frontend/js/api/joanie/index.ts | 29 ++ 30 files changed, 1986 insertions(+) create mode 100644 src/frontend/js/api/joanie/gen/ApiClientJoanie.ts create mode 100644 src/frontend/js/api/joanie/gen/core/ApiError.ts create mode 100644 src/frontend/js/api/joanie/gen/core/ApiRequestOptions.ts create mode 100644 src/frontend/js/api/joanie/gen/core/ApiResult.ts create mode 100644 src/frontend/js/api/joanie/gen/core/BaseHttpRequest.ts create mode 100644 src/frontend/js/api/joanie/gen/core/CancelablePromise.ts create mode 100644 src/frontend/js/api/joanie/gen/core/FetchHttpRequest.ts create mode 100644 src/frontend/js/api/joanie/gen/core/OpenAPI.ts create mode 100644 src/frontend/js/api/joanie/gen/core/request.ts create mode 100644 src/frontend/js/api/joanie/gen/index.ts create mode 100644 src/frontend/js/api/joanie/gen/models/Address.ts create mode 100644 src/frontend/js/api/joanie/gen/models/Certificate.ts create mode 100644 src/frontend/js/api/joanie/gen/models/CertificationDefinition.ts create mode 100644 src/frontend/js/api/joanie/gen/models/Course.ts create mode 100644 src/frontend/js/api/joanie/gen/models/CourseRun.ts create mode 100644 src/frontend/js/api/joanie/gen/models/CreditCard.ts create mode 100644 src/frontend/js/api/joanie/gen/models/Enrollment.ts create mode 100644 src/frontend/js/api/joanie/gen/models/Order.ts create mode 100644 src/frontend/js/api/joanie/gen/models/Product.ts create mode 100644 src/frontend/js/api/joanie/gen/services/AddressesService.ts create mode 100644 src/frontend/js/api/joanie/gen/services/CertificatesService.ts create mode 100644 src/frontend/js/api/joanie/gen/services/CourseRunsService.ts create mode 100644 src/frontend/js/api/joanie/gen/services/CourseRunsSyncService.ts create mode 100644 src/frontend/js/api/joanie/gen/services/CreditCardsService.ts create mode 100644 src/frontend/js/api/joanie/gen/services/EnrollmentsService.ts create mode 100644 src/frontend/js/api/joanie/gen/services/OrdersService.ts create mode 100644 src/frontend/js/api/joanie/gen/services/PaymentsService.ts create mode 100644 src/frontend/js/api/joanie/gen/services/ProductsService.ts create mode 100644 src/frontend/js/api/joanie/index.ts diff --git a/src/frontend/.prettierignore b/src/frontend/.prettierignore index 2802991431..7d77f3b08b 100644 --- a/src/frontend/.prettierignore +++ b/src/frontend/.prettierignore @@ -1,3 +1,4 @@ js/translations/* i18n/**/* public-path.js +js/api/**/gen/**/*.ts diff --git a/src/frontend/js/api/joanie/gen/ApiClientJoanie.ts b/src/frontend/js/api/joanie/gen/ApiClientJoanie.ts new file mode 100644 index 0000000000..d9a5a693ab --- /dev/null +++ b/src/frontend/js/api/joanie/gen/ApiClientJoanie.ts @@ -0,0 +1,58 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { BaseHttpRequest } from './core/BaseHttpRequest'; +import type { OpenAPIConfig } from './core/OpenAPI'; +import { FetchHttpRequest } from './core/FetchHttpRequest'; + +import { AddressesService } from './services/AddressesService'; +import { CertificatesService } from './services/CertificatesService'; +import { CourseRunsService } from './services/CourseRunsService'; +import { CourseRunsSyncService } from './services/CourseRunsSyncService'; +import { CreditCardsService } from './services/CreditCardsService'; +import { EnrollmentsService } from './services/EnrollmentsService'; +import { OrdersService } from './services/OrdersService'; +import { PaymentsService } from './services/PaymentsService'; +import { ProductsService } from './services/ProductsService'; + +type HttpRequestConstructor = new (config: OpenAPIConfig) => BaseHttpRequest; + +export class ApiClientJoanie { + + public readonly addresses: AddressesService; + public readonly certificates: CertificatesService; + public readonly courseRuns: CourseRunsService; + public readonly courseRunsSync: CourseRunsSyncService; + public readonly creditCards: CreditCardsService; + public readonly enrollments: EnrollmentsService; + public readonly orders: OrdersService; + public readonly payments: PaymentsService; + public readonly products: ProductsService; + + public readonly request: BaseHttpRequest; + + constructor(config?: Partial, HttpRequest: HttpRequestConstructor = FetchHttpRequest) { + this.request = new HttpRequest({ + BASE: config?.BASE ?? 'http://localhost:8071/api/v1.0', + VERSION: config?.VERSION ?? '1.0', + WITH_CREDENTIALS: config?.WITH_CREDENTIALS ?? false, + CREDENTIALS: config?.CREDENTIALS ?? 'include', + TOKEN: config?.TOKEN, + USERNAME: config?.USERNAME, + PASSWORD: config?.PASSWORD, + HEADERS: config?.HEADERS, + ENCODE_PATH: config?.ENCODE_PATH, + }); + + this.addresses = new AddressesService(this.request); + this.certificates = new CertificatesService(this.request); + this.courseRuns = new CourseRunsService(this.request); + this.courseRunsSync = new CourseRunsSyncService(this.request); + this.creditCards = new CreditCardsService(this.request); + this.enrollments = new EnrollmentsService(this.request); + this.orders = new OrdersService(this.request); + this.payments = new PaymentsService(this.request); + this.products = new ProductsService(this.request); + } +} + diff --git a/src/frontend/js/api/joanie/gen/core/ApiError.ts b/src/frontend/js/api/joanie/gen/core/ApiError.ts new file mode 100644 index 0000000000..41a9605a3a --- /dev/null +++ b/src/frontend/js/api/joanie/gen/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/frontend/js/api/joanie/gen/core/ApiRequestOptions.ts b/src/frontend/js/api/joanie/gen/core/ApiRequestOptions.ts new file mode 100644 index 0000000000..c9350406a1 --- /dev/null +++ b/src/frontend/js/api/joanie/gen/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; + readonly cookies?: Record; + readonly headers?: Record; + readonly query?: Record; + readonly formData?: Record; + readonly body?: any; + readonly mediaType?: string; + readonly responseHeader?: string; + readonly errors?: Record; +}; diff --git a/src/frontend/js/api/joanie/gen/core/ApiResult.ts b/src/frontend/js/api/joanie/gen/core/ApiResult.ts new file mode 100644 index 0000000000..91f60ae082 --- /dev/null +++ b/src/frontend/js/api/joanie/gen/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/frontend/js/api/joanie/gen/core/BaseHttpRequest.ts b/src/frontend/js/api/joanie/gen/core/BaseHttpRequest.ts new file mode 100644 index 0000000000..489f1d10bf --- /dev/null +++ b/src/frontend/js/api/joanie/gen/core/BaseHttpRequest.ts @@ -0,0 +1,13 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ApiRequestOptions } from './ApiRequestOptions'; +import type { CancelablePromise } from './CancelablePromise'; +import type { OpenAPIConfig } from './OpenAPI'; + +export abstract class BaseHttpRequest { + + constructor(public readonly config: OpenAPIConfig) {} + + public abstract request(options: ApiRequestOptions): CancelablePromise; +} diff --git a/src/frontend/js/api/joanie/gen/core/CancelablePromise.ts b/src/frontend/js/api/joanie/gen/core/CancelablePromise.ts new file mode 100644 index 0000000000..b923479fea --- /dev/null +++ b/src/frontend/js/api/joanie/gen/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 implements Promise { + readonly [Symbol.toStringTag]!: string; + + private _isResolved: boolean; + private _isRejected: boolean; + private _isCancelled: boolean; + private readonly _cancelHandlers: (() => void)[]; + private readonly _promise: Promise; + private _resolve?: (value: T | PromiseLike) => void; + private _reject?: (reason?: any) => void; + + constructor( + executor: ( + resolve: (value: T | PromiseLike) => void, + reject: (reason?: any) => void, + onCancel: OnCancel + ) => void + ) { + this._isResolved = false; + this._isRejected = false; + this._isCancelled = false; + this._cancelHandlers = []; + this._promise = new Promise((resolve, reject) => { + this._resolve = resolve; + this._reject = reject; + + const onResolve = (value: T | PromiseLike): 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( + onFulfilled?: ((value: T) => TResult1 | PromiseLike) | null, + onRejected?: ((reason: any) => TResult2 | PromiseLike) | null + ): Promise { + return this._promise.then(onFulfilled, onRejected); + } + + public catch( + onRejected?: ((reason: any) => TResult | PromiseLike) | null + ): Promise { + return this._promise.catch(onRejected); + } + + public finally(onFinally?: (() => void) | null): Promise { + 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/frontend/js/api/joanie/gen/core/FetchHttpRequest.ts b/src/frontend/js/api/joanie/gen/core/FetchHttpRequest.ts new file mode 100644 index 0000000000..9444c54b8c --- /dev/null +++ b/src/frontend/js/api/joanie/gen/core/FetchHttpRequest.ts @@ -0,0 +1,25 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ApiRequestOptions } from './ApiRequestOptions'; +import { BaseHttpRequest } from './BaseHttpRequest'; +import type { CancelablePromise } from './CancelablePromise'; +import type { OpenAPIConfig } from './OpenAPI'; +import { request as __request } from './request'; + +export class FetchHttpRequest extends BaseHttpRequest { + + constructor(config: OpenAPIConfig) { + super(config); + } + + /** + * Request method + * @param options The request options from the service + * @returns CancelablePromise + * @throws ApiError + */ + public override request(options: ApiRequestOptions): CancelablePromise { + return __request(this.config, options); + } +} diff --git a/src/frontend/js/api/joanie/gen/core/OpenAPI.ts b/src/frontend/js/api/joanie/gen/core/OpenAPI.ts new file mode 100644 index 0000000000..b4b0bfbb3e --- /dev/null +++ b/src/frontend/js/api/joanie/gen/core/OpenAPI.ts @@ -0,0 +1,31 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ApiRequestOptions } from './ApiRequestOptions'; + +type Resolver = (options: ApiRequestOptions) => Promise; +type Headers = Record; + +export type OpenAPIConfig = { + BASE: string; + VERSION: string; + WITH_CREDENTIALS: boolean; + CREDENTIALS: 'include' | 'omit' | 'same-origin'; + TOKEN?: string | Resolver; + USERNAME?: string | Resolver; + PASSWORD?: string | Resolver; + HEADERS?: Headers | Resolver; + ENCODE_PATH?: (path: string) => string; +}; + +export const OpenAPI: OpenAPIConfig = { + BASE: 'http://localhost:8071/api/v1.0', + VERSION: '1.0', + WITH_CREDENTIALS: false, + CREDENTIALS: 'include', + TOKEN: undefined, + USERNAME: undefined, + PASSWORD: undefined, + HEADERS: undefined, + ENCODE_PATH: undefined, +}; diff --git a/src/frontend/js/api/joanie/gen/core/request.ts b/src/frontend/js/api/joanie/gen/core/request.ts new file mode 100644 index 0000000000..2f887e58ae --- /dev/null +++ b/src/frontend/js/api/joanie/gen/core/request.ts @@ -0,0 +1,306 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +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 = (value: T | null | undefined): value is Exclude => { + 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 base64 = (str: string): string => { + try { + return btoa(str); + } catch (err) { + // @ts-ignore + return Buffer.from(str).toString('base64'); + } +}; + +const getQueryString = (params: Record): 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 = (options: ApiRequestOptions) => Promise; + +const resolve = async (options: ApiRequestOptions, resolver?: T | Resolver): Promise => { + if (typeof resolver === 'function') { + return (resolver as Resolver)(options); + } + return resolver; +}; + +const getHeaders = async (config: OpenAPIConfig, options: ApiRequestOptions): Promise => { + 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 headers = Object.entries({ + Accept: 'application/json', + ...additionalHeaders, + ...options.headers, + }) + .filter(([_, value]) => isDefined(value)) + .reduce((headers, [key, value]) => ({ + ...headers, + [key]: String(value), + }), {} as Record); + + 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 new Headers(headers); +}; + +const getRequestBody = (options: ApiRequestOptions): any => { + if (options.body) { + if (options.mediaType?.includes('/json')) { + return JSON.stringify(options.body) + } else if (isString(options.body) || isBlob(options.body) || isFormData(options.body)) { + return options.body; + } else { + return JSON.stringify(options.body); + } + } + return undefined; +}; + +export const sendRequest = async ( + config: OpenAPIConfig, + options: ApiRequestOptions, + url: string, + body: any, + formData: FormData | undefined, + headers: Headers, + onCancel: OnCancel +): Promise => { + const controller = new AbortController(); + + const request: RequestInit = { + headers, + body: body ?? formData, + method: options.method, + signal: controller.signal, + }; + + if (config.WITH_CREDENTIALS) { + request.credentials = config.CREDENTIALS; + } + + onCancel(() => controller.abort()); + + return await fetch(url, request); +}; + +const getResponseHeader = (response: Response, responseHeader?: string): string | undefined => { + if (responseHeader) { + const content = response.headers.get(responseHeader); + if (isString(content)) { + return content; + } + } + return undefined; +}; + +const getResponseBody = async (response: Response): Promise => { + if (response.status !== 204) { + try { + const contentType = response.headers.get('Content-Type'); + if (contentType) { + const isJSON = contentType.toLowerCase().startsWith('application/json'); + if (isJSON) { + return await response.json(); + } else { + return await response.text(); + } + } + } catch (error) { + console.error(error); + } + } + return undefined; +}; + +const catchErrorCodes = (options: ApiRequestOptions, result: ApiResult): void => { + const errors: Record = { + 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 + * @throws ApiError + */ +export const request = (config: OpenAPIConfig, options: ApiRequestOptions): CancelablePromise => { + 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); + + if (!onCancel.isCancelled) { + const response = await sendRequest(config, options, url, body, formData, headers, onCancel); + const responseBody = await getResponseBody(response); + const responseHeader = getResponseHeader(response, options.responseHeader); + + const result: ApiResult = { + url, + ok: response.ok, + status: response.status, + statusText: response.statusText, + body: responseHeader ?? responseBody, + }; + + catchErrorCodes(options, result); + + resolve(result.body); + } + } catch (error) { + reject(error); + } + }); +}; diff --git a/src/frontend/js/api/joanie/gen/index.ts b/src/frontend/js/api/joanie/gen/index.ts new file mode 100644 index 0000000000..3c1be5461a --- /dev/null +++ b/src/frontend/js/api/joanie/gen/index.ts @@ -0,0 +1,30 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export { ApiClientJoanie } from './ApiClientJoanie'; + +export { ApiError } from './core/ApiError'; +export { BaseHttpRequest } from './core/BaseHttpRequest'; +export { CancelablePromise, CancelError } from './core/CancelablePromise'; +export { OpenAPI } from './core/OpenAPI'; +export type { OpenAPIConfig } from './core/OpenAPI'; + +export { Address } from './models/Address'; +export type { Certificate } from './models/Certificate'; +export type { CertificationDefinition } from './models/CertificationDefinition'; +export type { Course } from './models/Course'; +export type { CourseRun } from './models/CourseRun'; +export type { CreditCard } from './models/CreditCard'; +export { Enrollment } from './models/Enrollment'; +export { Order } from './models/Order'; +export { Product } from './models/Product'; + +export { AddressesService } from './services/AddressesService'; +export { CertificatesService } from './services/CertificatesService'; +export { CourseRunsService } from './services/CourseRunsService'; +export { CourseRunsSyncService } from './services/CourseRunsSyncService'; +export { CreditCardsService } from './services/CreditCardsService'; +export { EnrollmentsService } from './services/EnrollmentsService'; +export { OrdersService } from './services/OrdersService'; +export { PaymentsService } from './services/PaymentsService'; +export { ProductsService } from './services/ProductsService'; diff --git a/src/frontend/js/api/joanie/gen/models/Address.ts b/src/frontend/js/api/joanie/gen/models/Address.ts new file mode 100644 index 0000000000..8de84c97cb --- /dev/null +++ b/src/frontend/js/api/joanie/gen/models/Address.ts @@ -0,0 +1,273 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +export type Address = { + address: string; + city: string; + country: Address.country; + first_name: string; + last_name: string; + readonly id?: string; + is_main?: boolean; + postcode: string; + title: string; +}; + +export namespace Address { + + export enum country { + AF = 'AF', + AX = 'AX', + AL = 'AL', + DZ = 'DZ', + AS = 'AS', + AD = 'AD', + AO = 'AO', + AI = 'AI', + AQ = 'AQ', + AG = 'AG', + AR = 'AR', + AM = 'AM', + AW = 'AW', + AU = 'AU', + AT = 'AT', + AZ = 'AZ', + BS = 'BS', + BH = 'BH', + BD = 'BD', + BB = 'BB', + BY = 'BY', + BE = 'BE', + BZ = 'BZ', + BJ = 'BJ', + BM = 'BM', + BT = 'BT', + BO = 'BO', + BQ = 'BQ', + BA = 'BA', + BW = 'BW', + BV = 'BV', + BR = 'BR', + IO = 'IO', + BN = 'BN', + BG = 'BG', + BF = 'BF', + BI = 'BI', + CV = 'CV', + KH = 'KH', + CM = 'CM', + CA = 'CA', + KY = 'KY', + CF = 'CF', + TD = 'TD', + CL = 'CL', + CN = 'CN', + CX = 'CX', + CC = 'CC', + CO = 'CO', + KM = 'KM', + CG = 'CG', + CD = 'CD', + CK = 'CK', + CR = 'CR', + CI = 'CI', + HR = 'HR', + CU = 'CU', + CW = 'CW', + CY = 'CY', + CZ = 'CZ', + DK = 'DK', + DJ = 'DJ', + DM = 'DM', + DO = 'DO', + EC = 'EC', + EG = 'EG', + SV = 'SV', + GQ = 'GQ', + ER = 'ER', + EE = 'EE', + SZ = 'SZ', + ET = 'ET', + FK = 'FK', + FO = 'FO', + FJ = 'FJ', + FI = 'FI', + FR = 'FR', + GF = 'GF', + PF = 'PF', + TF = 'TF', + GA = 'GA', + GM = 'GM', + GE = 'GE', + DE = 'DE', + GH = 'GH', + GI = 'GI', + GR = 'GR', + GL = 'GL', + GD = 'GD', + GP = 'GP', + GU = 'GU', + GT = 'GT', + GG = 'GG', + GN = 'GN', + GW = 'GW', + GY = 'GY', + HT = 'HT', + HM = 'HM', + VA = 'VA', + HN = 'HN', + HK = 'HK', + HU = 'HU', + IS = 'IS', + IN = 'IN', + ID = 'ID', + IR = 'IR', + IQ = 'IQ', + IE = 'IE', + IM = 'IM', + IL = 'IL', + IT = 'IT', + JM = 'JM', + JP = 'JP', + JE = 'JE', + JO = 'JO', + KZ = 'KZ', + KE = 'KE', + KI = 'KI', + KW = 'KW', + KG = 'KG', + LA = 'LA', + LV = 'LV', + LB = 'LB', + LS = 'LS', + LR = 'LR', + LY = 'LY', + LI = 'LI', + LT = 'LT', + LU = 'LU', + MO = 'MO', + MG = 'MG', + MW = 'MW', + MY = 'MY', + MV = 'MV', + ML = 'ML', + MT = 'MT', + MH = 'MH', + MQ = 'MQ', + MR = 'MR', + MU = 'MU', + YT = 'YT', + MX = 'MX', + FM = 'FM', + MD = 'MD', + MC = 'MC', + MN = 'MN', + ME = 'ME', + MS = 'MS', + MA = 'MA', + MZ = 'MZ', + MM = 'MM', + NA = 'NA', + NR = 'NR', + NP = 'NP', + NL = 'NL', + NC = 'NC', + NZ = 'NZ', + NI = 'NI', + NE = 'NE', + NG = 'NG', + NU = 'NU', + NF = 'NF', + KP = 'KP', + MK = 'MK', + MP = 'MP', + NO = 'NO', + OM = 'OM', + PK = 'PK', + PW = 'PW', + PS = 'PS', + PA = 'PA', + PG = 'PG', + PY = 'PY', + PE = 'PE', + PH = 'PH', + PN = 'PN', + PL = 'PL', + PT = 'PT', + PR = 'PR', + QA = 'QA', + RE = 'RE', + RO = 'RO', + RU = 'RU', + RW = 'RW', + BL = 'BL', + SH = 'SH', + KN = 'KN', + LC = 'LC', + MF = 'MF', + PM = 'PM', + VC = 'VC', + WS = 'WS', + SM = 'SM', + ST = 'ST', + SA = 'SA', + SN = 'SN', + RS = 'RS', + SC = 'SC', + SL = 'SL', + SG = 'SG', + SX = 'SX', + SK = 'SK', + SI = 'SI', + SB = 'SB', + SO = 'SO', + ZA = 'ZA', + GS = 'GS', + KR = 'KR', + SS = 'SS', + ES = 'ES', + LK = 'LK', + SD = 'SD', + SR = 'SR', + SJ = 'SJ', + SE = 'SE', + CH = 'CH', + SY = 'SY', + TW = 'TW', + TJ = 'TJ', + TZ = 'TZ', + TH = 'TH', + TL = 'TL', + TG = 'TG', + TK = 'TK', + TO = 'TO', + TT = 'TT', + TN = 'TN', + TR = 'TR', + TM = 'TM', + TC = 'TC', + TV = 'TV', + UG = 'UG', + UA = 'UA', + AE = 'AE', + GB = 'GB', + UM = 'UM', + US = 'US', + UY = 'UY', + UZ = 'UZ', + VU = 'VU', + VE = 'VE', + VN = 'VN', + VG = 'VG', + VI = 'VI', + WF = 'WF', + EH = 'EH', + YE = 'YE', + ZM = 'ZM', + ZW = 'ZW', + } + + +} + diff --git a/src/frontend/js/api/joanie/gen/models/Certificate.ts b/src/frontend/js/api/joanie/gen/models/Certificate.ts new file mode 100644 index 0000000000..3494a95f1c --- /dev/null +++ b/src/frontend/js/api/joanie/gen/models/Certificate.ts @@ -0,0 +1,8 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +export type Certificate = { + readonly id?: string; +}; + diff --git a/src/frontend/js/api/joanie/gen/models/CertificationDefinition.ts b/src/frontend/js/api/joanie/gen/models/CertificationDefinition.ts new file mode 100644 index 0000000000..8b83993a5d --- /dev/null +++ b/src/frontend/js/api/joanie/gen/models/CertificationDefinition.ts @@ -0,0 +1,10 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +export type CertificationDefinition = { + readonly description?: string; + readonly name?: string; + readonly title?: string; +}; + diff --git a/src/frontend/js/api/joanie/gen/models/Course.ts b/src/frontend/js/api/joanie/gen/models/Course.ts new file mode 100644 index 0000000000..bf6cb79896 --- /dev/null +++ b/src/frontend/js/api/joanie/gen/models/Course.ts @@ -0,0 +1,9 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +export type Course = { + readonly code?: string; + readonly title?: string; +}; + diff --git a/src/frontend/js/api/joanie/gen/models/CourseRun.ts b/src/frontend/js/api/joanie/gen/models/CourseRun.ts new file mode 100644 index 0000000000..ccc6ebf780 --- /dev/null +++ b/src/frontend/js/api/joanie/gen/models/CourseRun.ts @@ -0,0 +1,21 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { Course } from './Course'; + +export type CourseRun = { + course?: Course; + readonly end?: string | null; + readonly enrollment_end?: string | null; + readonly enrollment_start?: string | null; + /** + * primary key for the record as UUID + */ + readonly id?: string; + readonly resource_link?: string; + readonly start?: string | null; + readonly title?: string; + readonly state?: string; +}; + diff --git a/src/frontend/js/api/joanie/gen/models/CreditCard.ts b/src/frontend/js/api/joanie/gen/models/CreditCard.ts new file mode 100644 index 0000000000..a1fe0894c9 --- /dev/null +++ b/src/frontend/js/api/joanie/gen/models/CreditCard.ts @@ -0,0 +1,14 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +export type CreditCard = { + readonly id?: string; + title?: string | null; + readonly brand?: string | null; + readonly expiration_month?: number; + readonly expiration_year?: number; + readonly last_numbers?: string; + is_main?: boolean; +}; + diff --git a/src/frontend/js/api/joanie/gen/models/Enrollment.ts b/src/frontend/js/api/joanie/gen/models/Enrollment.ts new file mode 100644 index 0000000000..c3fd388787 --- /dev/null +++ b/src/frontend/js/api/joanie/gen/models/Enrollment.ts @@ -0,0 +1,31 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { CourseRun } from './CourseRun'; + +export type Enrollment = { + readonly id?: string; + course_run?: CourseRun; + /** + * date and time at which a record was created + */ + readonly created_on?: string; + /** + * Ticked if the user is enrolled to the course run. + */ + is_active: boolean; + readonly state?: Enrollment.state; + was_created_by_order: boolean; +}; + +export namespace Enrollment { + + export enum state { + SET = 'set', + FAILED = 'failed', + } + + +} + diff --git a/src/frontend/js/api/joanie/gen/models/Order.ts b/src/frontend/js/api/joanie/gen/models/Order.ts new file mode 100644 index 0000000000..972500d6d7 --- /dev/null +++ b/src/frontend/js/api/joanie/gen/models/Order.ts @@ -0,0 +1,35 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +export type Order = { + course: string; + /** + * date and time at which a record was created + */ + readonly created_on?: string; + readonly certificate?: string; + readonly enrollments?: string; + readonly id?: string; + readonly main_invoice?: string; + organization?: string; + readonly owner?: string; + readonly total?: number; + readonly total_currency?: string; + product: string; + readonly state?: Order.state; + readonly target_courses?: string; +}; + +export namespace Order { + + export enum state { + PENDING = 'pending', + CANCELED = 'canceled', + FAILED = 'failed', + VALIDATED = 'validated', + } + + +} + diff --git a/src/frontend/js/api/joanie/gen/models/Product.ts b/src/frontend/js/api/joanie/gen/models/Product.ts new file mode 100644 index 0000000000..d421627871 --- /dev/null +++ b/src/frontend/js/api/joanie/gen/models/Product.ts @@ -0,0 +1,29 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { CertificationDefinition } from './CertificationDefinition'; + +export type Product = { + readonly call_to_action?: string; + certificate?: CertificationDefinition; + readonly id?: string; + readonly organizations?: string; + readonly price?: number; + readonly price_currency?: string; + readonly target_courses?: string; + readonly title?: string; + readonly type?: Product.type; +}; + +export namespace Product { + + export enum type { + CREDENTIAL = 'credential', + ENROLLMENT = 'enrollment', + CERTIFICATE = 'certificate', + } + + +} + diff --git a/src/frontend/js/api/joanie/gen/services/AddressesService.ts b/src/frontend/js/api/joanie/gen/services/AddressesService.ts new file mode 100644 index 0000000000..c7c3c68046 --- /dev/null +++ b/src/frontend/js/api/joanie/gen/services/AddressesService.ts @@ -0,0 +1,281 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { Address } from '../models/Address'; + +import type { CancelablePromise } from '../core/CancelablePromise'; +import type { BaseHttpRequest } from '../core/BaseHttpRequest'; + +export class AddressesService { + + constructor(public readonly httpRequest: BaseHttpRequest) {} + + /** + * API view allows to get all addresses or create or update a new one for a user. + * GET /api/addresses/ + * Return list of all addresses for a user + * + * POST /api/addresses/ with expected data: + * - address: str + * - city: str + * - country: str, country code + * - first_name: str, recipient first name + * - last_name: str, recipient last name + * - is_main?: bool, if True set address as main + * - postcode: str + * - title: str, address title + * Return new address just created + * + * PUT /api/addresses// with expected data: + * - address: str + * - city: str + * - country: str, country code + * - first_name: str, recipient first name + * - last_name: str, recipient last name + * - is_main?: bool, if True set address as main + * - postcode: str + * - title: str, address title + * Return address just updated + * + * DELETE /api/addresses// + * Delete selected address + * @returns Address + * @throws ApiError + */ + public addressesList(): CancelablePromise> { + return this.httpRequest.request({ + method: 'GET', + url: '/addresses/', + }); + } + + /** + * API view allows to get all addresses or create or update a new one for a user. + * GET /api/addresses/ + * Return list of all addresses for a user + * + * POST /api/addresses/ with expected data: + * - address: str + * - city: str + * - country: str, country code + * - first_name: str, recipient first name + * - last_name: str, recipient last name + * - is_main?: bool, if True set address as main + * - postcode: str + * - title: str, address title + * Return new address just created + * + * PUT /api/addresses// with expected data: + * - address: str + * - city: str + * - country: str, country code + * - first_name: str, recipient first name + * - last_name: str, recipient last name + * - is_main?: bool, if True set address as main + * - postcode: str + * - title: str, address title + * Return address just updated + * + * DELETE /api/addresses// + * Delete selected address + * @param data + * @returns Address + * @throws ApiError + */ + public addressesCreate( + data: Address, + ): CancelablePromise
{ + return this.httpRequest.request({ + method: 'POST', + url: '/addresses/', + body: data, + }); + } + + /** + * API view allows to get all addresses or create or update a new one for a user. + * GET /api/addresses/ + * Return list of all addresses for a user + * + * POST /api/addresses/ with expected data: + * - address: str + * - city: str + * - country: str, country code + * - first_name: str, recipient first name + * - last_name: str, recipient last name + * - is_main?: bool, if True set address as main + * - postcode: str + * - title: str, address title + * Return new address just created + * + * PUT /api/addresses// with expected data: + * - address: str + * - city: str + * - country: str, country code + * - first_name: str, recipient first name + * - last_name: str, recipient last name + * - is_main?: bool, if True set address as main + * - postcode: str + * - title: str, address title + * Return address just updated + * + * DELETE /api/addresses// + * Delete selected address + * @param id + * @returns Address + * @throws ApiError + */ + public addressesRead( + id: string, + ): CancelablePromise
{ + return this.httpRequest.request({ + method: 'GET', + url: '/addresses/{id}/', + path: { + 'id': id, + }, + }); + } + + /** + * API view allows to get all addresses or create or update a new one for a user. + * GET /api/addresses/ + * Return list of all addresses for a user + * + * POST /api/addresses/ with expected data: + * - address: str + * - city: str + * - country: str, country code + * - first_name: str, recipient first name + * - last_name: str, recipient last name + * - is_main?: bool, if True set address as main + * - postcode: str + * - title: str, address title + * Return new address just created + * + * PUT /api/addresses// with expected data: + * - address: str + * - city: str + * - country: str, country code + * - first_name: str, recipient first name + * - last_name: str, recipient last name + * - is_main?: bool, if True set address as main + * - postcode: str + * - title: str, address title + * Return address just updated + * + * DELETE /api/addresses// + * Delete selected address + * @param id + * @param data + * @returns Address + * @throws ApiError + */ + public addressesUpdate( + id: string, + data: Address, + ): CancelablePromise
{ + return this.httpRequest.request({ + method: 'PUT', + url: '/addresses/{id}/', + path: { + 'id': id, + }, + body: data, + }); + } + + /** + * API view allows to get all addresses or create or update a new one for a user. + * GET /api/addresses/ + * Return list of all addresses for a user + * + * POST /api/addresses/ with expected data: + * - address: str + * - city: str + * - country: str, country code + * - first_name: str, recipient first name + * - last_name: str, recipient last name + * - is_main?: bool, if True set address as main + * - postcode: str + * - title: str, address title + * Return new address just created + * + * PUT /api/addresses// with expected data: + * - address: str + * - city: str + * - country: str, country code + * - first_name: str, recipient first name + * - last_name: str, recipient last name + * - is_main?: bool, if True set address as main + * - postcode: str + * - title: str, address title + * Return address just updated + * + * DELETE /api/addresses// + * Delete selected address + * @param id + * @param data + * @returns Address + * @throws ApiError + */ + public addressesPartialUpdate( + id: string, + data: Address, + ): CancelablePromise
{ + return this.httpRequest.request({ + method: 'PATCH', + url: '/addresses/{id}/', + path: { + 'id': id, + }, + body: data, + }); + } + + /** + * API view allows to get all addresses or create or update a new one for a user. + * GET /api/addresses/ + * Return list of all addresses for a user + * + * POST /api/addresses/ with expected data: + * - address: str + * - city: str + * - country: str, country code + * - first_name: str, recipient first name + * - last_name: str, recipient last name + * - is_main?: bool, if True set address as main + * - postcode: str + * - title: str, address title + * Return new address just created + * + * PUT /api/addresses// with expected data: + * - address: str + * - city: str + * - country: str, country code + * - first_name: str, recipient first name + * - last_name: str, recipient last name + * - is_main?: bool, if True set address as main + * - postcode: str + * - title: str, address title + * Return address just updated + * + * DELETE /api/addresses// + * Delete selected address + * @param id + * @returns void + * @throws ApiError + */ + public addressesDelete( + id: string, + ): CancelablePromise { + return this.httpRequest.request({ + method: 'DELETE', + url: '/addresses/{id}/', + path: { + 'id': id, + }, + }); + } + +} diff --git a/src/frontend/js/api/joanie/gen/services/CertificatesService.ts b/src/frontend/js/api/joanie/gen/services/CertificatesService.ts new file mode 100644 index 0000000000..a4e44edc38 --- /dev/null +++ b/src/frontend/js/api/joanie/gen/services/CertificatesService.ts @@ -0,0 +1,73 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { Certificate } from '../models/Certificate'; + +import type { CancelablePromise } from '../core/CancelablePromise'; +import type { BaseHttpRequest } from '../core/BaseHttpRequest'; + +export class CertificatesService { + + constructor(public readonly httpRequest: BaseHttpRequest) {} + + /** + * API views to get all certificates for a user + * GET /api/certificates/:certificate_id + * Return list of all certificates for a user or one certificate if an id is + * provided. + * + * GET /api/certificates/:certificate_id/download + * Return the certificate document in PDF format. + * @returns Certificate + * @throws ApiError + */ + public certificatesList(): CancelablePromise> { + return this.httpRequest.request({ + method: 'GET', + url: '/certificates/', + }); + } + + /** + * API views to get all certificates for a user + * GET /api/certificates/:certificate_id + * Return list of all certificates for a user or one certificate if an id is + * provided. + * + * GET /api/certificates/:certificate_id/download + * Return the certificate document in PDF format. + * @param id + * @returns Certificate + * @throws ApiError + */ + public certificatesRead( + id: string, + ): CancelablePromise { + return this.httpRequest.request({ + method: 'GET', + url: '/certificates/{id}/', + path: { + 'id': id, + }, + }); + } + + /** + * Retrieve a certificate through its id if it is owned by the authenticated user. + * @param id + * @returns Certificate + * @throws ApiError + */ + public certificatesDownload( + id: string, + ): CancelablePromise { + return this.httpRequest.request({ + method: 'GET', + url: '/certificates/{id}/download/', + path: { + 'id': id, + }, + }); + } + +} diff --git a/src/frontend/js/api/joanie/gen/services/CourseRunsService.ts b/src/frontend/js/api/joanie/gen/services/CourseRunsService.ts new file mode 100644 index 0000000000..080dfd0cd4 --- /dev/null +++ b/src/frontend/js/api/joanie/gen/services/CourseRunsService.ts @@ -0,0 +1,31 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { CourseRun } from '../models/CourseRun'; + +import type { CancelablePromise } from '../core/CancelablePromise'; +import type { BaseHttpRequest } from '../core/BaseHttpRequest'; + +export class CourseRunsService { + + constructor(public readonly httpRequest: BaseHttpRequest) {} + + /** + * API ViewSet for all interactions with course runs. + * @param id primary key for the record as UUID + * @returns CourseRun + * @throws ApiError + */ + public courseRunsRead( + id: string, + ): CancelablePromise { + return this.httpRequest.request({ + method: 'GET', + url: '/course-runs/{id}/', + path: { + 'id': id, + }, + }); + } + +} diff --git a/src/frontend/js/api/joanie/gen/services/CourseRunsSyncService.ts b/src/frontend/js/api/joanie/gen/services/CourseRunsSyncService.ts new file mode 100644 index 0000000000..6439b290e9 --- /dev/null +++ b/src/frontend/js/api/joanie/gen/services/CourseRunsSyncService.ts @@ -0,0 +1,34 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { CancelablePromise } from '../core/CancelablePromise'; +import type { BaseHttpRequest } from '../core/BaseHttpRequest'; + +export class CourseRunsSyncService { + + constructor(public readonly httpRequest: BaseHttpRequest) {} + + /** + * View for the web hook to create or update course runs based on their resource link. + * - A new course run is created or the existing course run is updated + * + * Parameters + * ---------- + * request : Type[django.http.request.HttpRequest] + * The request on the API endpoint, it should contain a payload with course run fields. + * + * Returns + * ------- + * Type[rest_framework.response.Response] + * HttpResponse acknowledging the success or failure of the synchronization operation. + * @returns any + * @throws ApiError + */ + public courseRunsSyncCreate(): CancelablePromise { + return this.httpRequest.request({ + method: 'POST', + url: '/course-runs-sync/', + }); + } + +} diff --git a/src/frontend/js/api/joanie/gen/services/CreditCardsService.ts b/src/frontend/js/api/joanie/gen/services/CreditCardsService.ts new file mode 100644 index 0000000000..af5816297b --- /dev/null +++ b/src/frontend/js/api/joanie/gen/services/CreditCardsService.ts @@ -0,0 +1,153 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { CreditCard } from '../models/CreditCard'; + +import type { CancelablePromise } from '../core/CancelablePromise'; +import type { BaseHttpRequest } from '../core/BaseHttpRequest'; + +export class CreditCardsService { + + constructor(public readonly httpRequest: BaseHttpRequest) {} + + /** + * API views allows to get all credit cards, update or delete one + * for the authenticated user. + * GET /api/credit-cards/ + * Return the list of all credit cards owned by the authenticated user + * + * PUT /api/credit-cards/ with expected data: + * - title: str + * - is_main?: bool + * + * DELETE /api/credit-cards/ + * Delete the selected credit card + * @returns CreditCard + * @throws ApiError + */ + public creditCardsList(): CancelablePromise> { + return this.httpRequest.request({ + method: 'GET', + url: '/credit-cards/', + }); + } + + /** + * API views allows to get all credit cards, update or delete one + * for the authenticated user. + * GET /api/credit-cards/ + * Return the list of all credit cards owned by the authenticated user + * + * PUT /api/credit-cards/ with expected data: + * - title: str + * - is_main?: bool + * + * DELETE /api/credit-cards/ + * Delete the selected credit card + * @param id + * @returns CreditCard + * @throws ApiError + */ + public creditCardsRead( + id: string, + ): CancelablePromise { + return this.httpRequest.request({ + method: 'GET', + url: '/credit-cards/{id}/', + path: { + 'id': id, + }, + }); + } + + /** + * API views allows to get all credit cards, update or delete one + * for the authenticated user. + * GET /api/credit-cards/ + * Return the list of all credit cards owned by the authenticated user + * + * PUT /api/credit-cards/ with expected data: + * - title: str + * - is_main?: bool + * + * DELETE /api/credit-cards/ + * Delete the selected credit card + * @param id + * @param data + * @returns CreditCard + * @throws ApiError + */ + public creditCardsUpdate( + id: string, + data: CreditCard, + ): CancelablePromise { + return this.httpRequest.request({ + method: 'PUT', + url: '/credit-cards/{id}/', + path: { + 'id': id, + }, + body: data, + }); + } + + /** + * API views allows to get all credit cards, update or delete one + * for the authenticated user. + * GET /api/credit-cards/ + * Return the list of all credit cards owned by the authenticated user + * + * PUT /api/credit-cards/ with expected data: + * - title: str + * - is_main?: bool + * + * DELETE /api/credit-cards/ + * Delete the selected credit card + * @param id + * @param data + * @returns CreditCard + * @throws ApiError + */ + public creditCardsPartialUpdate( + id: string, + data: CreditCard, + ): CancelablePromise { + return this.httpRequest.request({ + method: 'PATCH', + url: '/credit-cards/{id}/', + path: { + 'id': id, + }, + body: data, + }); + } + + /** + * API views allows to get all credit cards, update or delete one + * for the authenticated user. + * GET /api/credit-cards/ + * Return the list of all credit cards owned by the authenticated user + * + * PUT /api/credit-cards/ with expected data: + * - title: str + * - is_main?: bool + * + * DELETE /api/credit-cards/ + * Delete the selected credit card + * @param id + * @returns void + * @throws ApiError + */ + public creditCardsDelete( + id: string, + ): CancelablePromise { + return this.httpRequest.request({ + method: 'DELETE', + url: '/credit-cards/{id}/', + path: { + 'id': id, + }, + }); + } + +} diff --git a/src/frontend/js/api/joanie/gen/services/EnrollmentsService.ts b/src/frontend/js/api/joanie/gen/services/EnrollmentsService.ts new file mode 100644 index 0000000000..f5ad9bd6f9 --- /dev/null +++ b/src/frontend/js/api/joanie/gen/services/EnrollmentsService.ts @@ -0,0 +1,118 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { Enrollment } from '../models/Enrollment'; + +import type { CancelablePromise } from '../core/CancelablePromise'; +import type { BaseHttpRequest } from '../core/BaseHttpRequest'; + +export class EnrollmentsService { + + constructor(public readonly httpRequest: BaseHttpRequest) {} + + /** + * API ViewSet for all interactions with enrollments. + * @param courseRun + * @param wasCreatedByOrder + * @param page A page number within the paginated result set. + * @returns any + * @throws ApiError + */ + public enrollmentsList( + courseRun?: string, + wasCreatedByOrder?: string, + page?: number, + ): CancelablePromise<{ + count: number; + next?: string | null; + previous?: string | null; + results: Array; + }> { + return this.httpRequest.request({ + method: 'GET', + url: '/enrollments/', + query: { + 'course_run': courseRun, + 'was_created_by_order': wasCreatedByOrder, + 'page': page, + }, + }); + } + + /** + * API ViewSet for all interactions with enrollments. + * @param data + * @returns Enrollment + * @throws ApiError + */ + public enrollmentsCreate( + data: Enrollment, + ): CancelablePromise { + return this.httpRequest.request({ + method: 'POST', + url: '/enrollments/', + body: data, + }); + } + + /** + * API ViewSet for all interactions with enrollments. + * @param id + * @returns Enrollment + * @throws ApiError + */ + public enrollmentsRead( + id: string, + ): CancelablePromise { + return this.httpRequest.request({ + method: 'GET', + url: '/enrollments/{id}/', + path: { + 'id': id, + }, + }); + } + + /** + * API ViewSet for all interactions with enrollments. + * @param id + * @param data + * @returns Enrollment + * @throws ApiError + */ + public enrollmentsUpdate( + id: string, + data: Enrollment, + ): CancelablePromise { + return this.httpRequest.request({ + method: 'PUT', + url: '/enrollments/{id}/', + path: { + 'id': id, + }, + body: data, + }); + } + + /** + * API ViewSet for all interactions with enrollments. + * @param id + * @param data + * @returns Enrollment + * @throws ApiError + */ + public enrollmentsPartialUpdate( + id: string, + data: Enrollment, + ): CancelablePromise { + return this.httpRequest.request({ + method: 'PATCH', + url: '/enrollments/{id}/', + path: { + 'id': id, + }, + body: data, + }); + } + +} diff --git a/src/frontend/js/api/joanie/gen/services/OrdersService.ts b/src/frontend/js/api/joanie/gen/services/OrdersService.ts new file mode 100644 index 0000000000..196c99157c --- /dev/null +++ b/src/frontend/js/api/joanie/gen/services/OrdersService.ts @@ -0,0 +1,135 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { Order } from '../models/Order'; + +import type { CancelablePromise } from '../core/CancelablePromise'; +import type { BaseHttpRequest } from '../core/BaseHttpRequest'; + +export class OrdersService { + + constructor(public readonly httpRequest: BaseHttpRequest) {} + + /** + * API view for a user to consult the orders he/she owns or create a new one. + * GET /api/orders/ + * Return list of all orders for a user with pagination + * + * POST /api/orders/ with expected data: + * - course: course code + * - product: product id (product must be associated to the course. Otherwise, + * a 400 error is returned) + * Return new order just created + * @param product + * @param course + * @param state + * @param page A page number within the paginated result set. + * @returns any + * @throws ApiError + */ + public ordersList( + product?: string, + course?: string, + state?: string, + page?: number, + ): CancelablePromise<{ + count: number; + next?: string | null; + previous?: string | null; + results: Array; + }> { + return this.httpRequest.request({ + method: 'GET', + url: '/orders/', + query: { + 'product': product, + 'course': course, + 'state': state, + 'page': page, + }, + }); + } + + /** + * Try to create an order and a related payment if the payment is fee. + * @param data + * @returns Order + * @throws ApiError + */ + public ordersCreate( + data: Order, + ): CancelablePromise { + return this.httpRequest.request({ + method: 'POST', + url: '/orders/', + body: data, + }); + } + + /** + * API view for a user to consult the orders he/she owns or create a new one. + * GET /api/orders/ + * Return list of all orders for a user with pagination + * + * POST /api/orders/ with expected data: + * - course: course code + * - product: product id (product must be associated to the course. Otherwise, + * a 400 error is returned) + * Return new order just created + * @param id + * @returns Order + * @throws ApiError + */ + public ordersRead( + id: string, + ): CancelablePromise { + return this.httpRequest.request({ + method: 'GET', + url: '/orders/{id}/', + path: { + 'id': id, + }, + }); + } + + /** + * Abort a pending order and the related payment if there is one. + * @param id + * @param data + * @returns Order + * @throws ApiError + */ + public ordersAbort( + id: string, + data: Order, + ): CancelablePromise { + return this.httpRequest.request({ + method: 'POST', + url: '/orders/{id}/abort/', + path: { + 'id': id, + }, + body: data, + }); + } + + /** + * Retrieve an invoice through its reference if it is related to + * the order instance and owned by the authenticated user. + * @param id + * @returns Order + * @throws ApiError + */ + public ordersInvoice( + id: string, + ): CancelablePromise { + return this.httpRequest.request({ + method: 'GET', + url: '/orders/{id}/invoice/', + path: { + 'id': id, + }, + }); + } + +} diff --git a/src/frontend/js/api/joanie/gen/services/PaymentsService.ts b/src/frontend/js/api/joanie/gen/services/PaymentsService.ts new file mode 100644 index 0000000000..f41431163c --- /dev/null +++ b/src/frontend/js/api/joanie/gen/services/PaymentsService.ts @@ -0,0 +1,24 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { CancelablePromise } from '../core/CancelablePromise'; +import type { BaseHttpRequest } from '../core/BaseHttpRequest'; + +export class PaymentsService { + + constructor(public readonly httpRequest: BaseHttpRequest) {} + + /** + * The webhook called by payment provider + * when a payment has been created/updated/refunded... + * @returns any + * @throws ApiError + */ + public paymentsNotificationsCreate(): CancelablePromise { + return this.httpRequest.request({ + method: 'POST', + url: '/payments/notifications/', + }); + } + +} diff --git a/src/frontend/js/api/joanie/gen/services/ProductsService.ts b/src/frontend/js/api/joanie/gen/services/ProductsService.ts new file mode 100644 index 0000000000..4d85fd8395 --- /dev/null +++ b/src/frontend/js/api/joanie/gen/services/ProductsService.ts @@ -0,0 +1,36 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { Product } from '../models/Product'; + +import type { CancelablePromise } from '../core/CancelablePromise'; +import type { BaseHttpRequest } from '../core/BaseHttpRequest'; + +export class ProductsService { + + constructor(public readonly httpRequest: BaseHttpRequest) {} + + /** + * API ViewSet for all interactions with products. + * @param id primary key for the record as UUID + * @param course + * @returns Product + * @throws ApiError + */ + public productsRead( + id: string, + course?: string, + ): CancelablePromise { + return this.httpRequest.request({ + method: 'GET', + url: '/products/{id}/', + path: { + 'id': id, + }, + query: { + 'course': course, + }, + }); + } + +} diff --git a/src/frontend/js/api/joanie/index.ts b/src/frontend/js/api/joanie/index.ts new file mode 100644 index 0000000000..5e1a84898c --- /dev/null +++ b/src/frontend/js/api/joanie/index.ts @@ -0,0 +1,29 @@ +import context from 'utils/context'; +import { JOANIE_API_VERSION } from 'settings'; +import { ApiClientJoanie, OpenAPIConfig } from './gen'; + +/** + * Build Joanie API Routes interface. + */ +const getAPIEndpoint = () => { + const endpoint = context?.joanie_backend?.endpoint; + const version = JOANIE_API_VERSION; + + if (!endpoint) { + throw new Error('[JOANIE] - Joanie API endpoint is not defined.'); + } + + return `${endpoint}/api/${version}`; +}; + +// TODO add auth with jwt +const config: OpenAPIConfig = { + BASE: getAPIEndpoint(), + VERSION: '1', + WITH_CREDENTIALS: true, + CREDENTIALS: 'include', + // TOKEN: +}; + +export const joanieApi = new ApiClientJoanie(config); +export * from './hooks'; From 314152cb9bbfb7992d8f5ff94a03e0c3dc4fdd05 Mon Sep 17 00:00:00 2001 From: Romain Le Cellier Date: Mon, 20 Feb 2023 17:13:18 +0100 Subject: [PATCH 04/12] =?UTF-8?q?=E2=9C=A8(frontend)=20add=20jwt=20token?= =?UTF-8?q?=20into=20joanie=20generated=20api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../js/api/joanie/__specs__/index.spec.ts | 35 +++++++++++++++++++ src/frontend/js/api/joanie/index.ts | 18 ++++++---- 2 files changed, 46 insertions(+), 7 deletions(-) create mode 100644 src/frontend/js/api/joanie/__specs__/index.spec.ts diff --git a/src/frontend/js/api/joanie/__specs__/index.spec.ts b/src/frontend/js/api/joanie/__specs__/index.spec.ts new file mode 100644 index 0000000000..ff6c87c722 --- /dev/null +++ b/src/frontend/js/api/joanie/__specs__/index.spec.ts @@ -0,0 +1,35 @@ +import fetchMock from 'fetch-mock'; +import * as mockFactories from 'utils/test/factories'; +import { RICHIE_USER_TOKEN } from 'settings'; + +import { joanieApi } from '..'; + +jest.mock('utils/context', () => ({ + __esModule: true, + default: mockFactories + .ContextFactory({ + joanie_backend: { endpoint: 'https://joanie.endpoint' }, + }) + .generate(), +})); + +describe('joanieApi', () => { + it('test', async () => { + fetchMock.get('https://joanie.endpoint/api/v1.0/addresses/addressId/', []); + await joanieApi.addresses.addressesRead({ id: 'addressId' }); + + let lastCall = fetchMock.lastCall(); + const visitorHeader = lastCall && lastCall[1]?.headers; + // TS see visitorHeader has HeadersInit instead of Headers and + // didn't accept get() as a possible function. + // @ts-ignore + expect(visitorHeader?.get('Authorization')).toBeNull(); + + sessionStorage.setItem(RICHIE_USER_TOKEN, 'TEST_TOKEN'); + await joanieApi.addresses.addressesRead({ id: 'addressId' }); + lastCall = fetchMock.lastCall(); + const userHeader = lastCall && lastCall[1]?.headers; + // @ts-ignore + expect(userHeader?.get('Authorization')).toBe('Bearer TEST_TOKEN'); + }); +}); diff --git a/src/frontend/js/api/joanie/index.ts b/src/frontend/js/api/joanie/index.ts index 5e1a84898c..32fff2c654 100644 --- a/src/frontend/js/api/joanie/index.ts +++ b/src/frontend/js/api/joanie/index.ts @@ -1,6 +1,6 @@ import context from 'utils/context'; -import { JOANIE_API_VERSION } from 'settings'; -import { ApiClientJoanie, OpenAPIConfig } from './gen'; +import { JOANIE_API_VERSION, RICHIE_USER_TOKEN } from 'settings'; +import { ApiClientJoanie, ApiError, OpenAPIConfig } from './gen'; /** * Build Joanie API Routes interface. @@ -16,14 +16,18 @@ const getAPIEndpoint = () => { return `${endpoint}/api/${version}`; }; -// TODO add auth with jwt const config: OpenAPIConfig = { BASE: getAPIEndpoint(), VERSION: '1', - WITH_CREDENTIALS: true, - CREDENTIALS: 'include', - // TOKEN: + WITH_CREDENTIALS: false, + CREDENTIALS: 'omit', + TOKEN: async () => { + return sessionStorage.getItem(RICHIE_USER_TOKEN) || ''; + }, }; export const joanieApi = new ApiClientJoanie(config); -export * from './hooks'; + +export const isApiError = (error: unknown): error is ApiError => { + return (error as ApiError).name === 'ApiError'; +}; From 0ec4c1723d9f0490bcbc29df473be82b5618f954 Mon Sep 17 00:00:00 2001 From: Romain Le Cellier Date: Fri, 24 Feb 2023 14:02:15 +0100 Subject: [PATCH 05/12] =?UTF-8?q?=E2=9C=A8(frontend)=20useAddresses=20with?= =?UTF-8?q?=20generated=20api=20client?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../js/api/joanie/__specs__/index.spec.ts | 4 +- src/frontend/js/api/joanie/index.ts | 3 +- .../DashboardEditAddress.spec.tsx | 74 +++++++++---------- .../index.spec.tsx | 33 +++++---- .../JoanieSessionProvider.spec.tsx | 20 ++--- src/frontend/js/hooks/useAddresses.ts | 34 +++++++-- .../js/hooks/useResources/index.spec.tsx | 10 ++- src/frontend/js/hooks/useResources/index.tsx | 2 +- 8 files changed, 100 insertions(+), 80 deletions(-) diff --git a/src/frontend/js/api/joanie/__specs__/index.spec.ts b/src/frontend/js/api/joanie/__specs__/index.spec.ts index ff6c87c722..1df0199441 100644 --- a/src/frontend/js/api/joanie/__specs__/index.spec.ts +++ b/src/frontend/js/api/joanie/__specs__/index.spec.ts @@ -16,7 +16,7 @@ jest.mock('utils/context', () => ({ describe('joanieApi', () => { it('test', async () => { fetchMock.get('https://joanie.endpoint/api/v1.0/addresses/addressId/', []); - await joanieApi.addresses.addressesRead({ id: 'addressId' }); + await joanieApi.addresses.addressesRead('addressId'); let lastCall = fetchMock.lastCall(); const visitorHeader = lastCall && lastCall[1]?.headers; @@ -26,7 +26,7 @@ describe('joanieApi', () => { expect(visitorHeader?.get('Authorization')).toBeNull(); sessionStorage.setItem(RICHIE_USER_TOKEN, 'TEST_TOKEN'); - await joanieApi.addresses.addressesRead({ id: 'addressId' }); + await joanieApi.addresses.addressesRead('addressId'); lastCall = fetchMock.lastCall(); const userHeader = lastCall && lastCall[1]?.headers; // @ts-ignore diff --git a/src/frontend/js/api/joanie/index.ts b/src/frontend/js/api/joanie/index.ts index 32fff2c654..b582495e35 100644 --- a/src/frontend/js/api/joanie/index.ts +++ b/src/frontend/js/api/joanie/index.ts @@ -1,5 +1,6 @@ import context from 'utils/context'; import { JOANIE_API_VERSION, RICHIE_USER_TOKEN } from 'settings'; +import isTestEnv from 'utils/test/isTestEnv'; import { ApiClientJoanie, ApiError, OpenAPIConfig } from './gen'; /** @@ -9,7 +10,7 @@ const getAPIEndpoint = () => { const endpoint = context?.joanie_backend?.endpoint; const version = JOANIE_API_VERSION; - if (!endpoint) { + if (!endpoint && !isTestEnv) { throw new Error('[JOANIE] - Joanie API endpoint is not defined.'); } diff --git a/src/frontend/js/components/DashboardAddressesManagement/DashboardEditAddress.spec.tsx b/src/frontend/js/components/DashboardAddressesManagement/DashboardEditAddress.spec.tsx index e90cb20fcc..4d3fe9d7cc 100644 --- a/src/frontend/js/components/DashboardAddressesManagement/DashboardEditAddress.spec.tsx +++ b/src/frontend/js/components/DashboardAddressesManagement/DashboardEditAddress.spec.tsx @@ -2,6 +2,7 @@ import { QueryClientProvider } from '@tanstack/react-query'; import fetchMock from 'fetch-mock'; import { act, findByText, fireEvent, render, screen, waitFor } from '@testing-library/react'; import { IntlProvider } from 'react-intl'; +import userEvent from '@testing-library/user-event'; import * as mockFactories from 'utils/test/factories'; import { SessionProvider } from 'data/SessionProvider'; import { DashboardTest } from 'components/Dashboard/DashboardTest'; @@ -41,22 +42,20 @@ describe('', () => { const updateUrl = 'https://joanie.endpoint/api/v1.0/addresses/' + address.id + '/'; fetchMock.put(updateUrl, 200); - await act(async () => { - render( - - - - - - - , - ); - }); + render( + + + + + + + , + ); // It doesn't show any errors. expect(screen.queryByText('An error occurred', { exact: false })).toBeNull(); @@ -95,11 +94,11 @@ describe('', () => { // Submit of the form calls the API edit route. expect(fetchMock.called(updateUrl, { method: 'put' })).toBe(false); - await act(async () => { - // it is not necessary to update all fields as it is mocked above. - fireEvent.change(titleInput, { target: { value: addressUpdated.title } }); - fireEvent.click(button); - }); + + // it is not necessary to update all fields as it is mocked above. + await userEvent.clear(titleInput); + await userEvent.type(titleInput, addressUpdated.title); + await userEvent.click(button); expect(fetchMock.called(updateUrl, { method: 'put' })).toBe(true); // The API is called with correct body. @@ -123,26 +122,23 @@ describe('', () => { fetchMock.get('https://joanie.endpoint/api/v1.0/addresses/', [address]); // Mock the edit API route to return a 500 status. - const updateUrl = 'https://joanie.endpoint/api/v1.0/addresses/' + address.id + '/'; + const updateUrl = `https://joanie.endpoint/api/v1.0/addresses/${address.id}/`; fetchMock.put(updateUrl, { status: 500, body: 'Bad request' }); - let container: HTMLElement | undefined; - await act(async () => { - container = render( - - - - - - - , - ).container; - }); + const { container } = render( + + + + + + + , + ); await expectBreadcrumbsToEqualParts([ 'Back', diff --git a/src/frontend/js/components/DashboardAddressesManagement/index.spec.tsx b/src/frontend/js/components/DashboardAddressesManagement/index.spec.tsx index e9c5d73b2d..668d4b82b7 100644 --- a/src/frontend/js/components/DashboardAddressesManagement/index.spec.tsx +++ b/src/frontend/js/components/DashboardAddressesManagement/index.spec.tsx @@ -6,7 +6,9 @@ import { queryByText, render, screen, + waitFor, } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { IntlProvider } from 'react-intl'; import { QueryClientProvider } from '@tanstack/react-query'; import fetchMock from 'fetch-mock'; @@ -93,17 +95,16 @@ describe('', () => { it('deletes an address', async () => { const addresses = mockFactories.AddressFactory.generate(5); fetchMock.get('https://joanie.endpoint/api/v1.0/addresses/', addresses); - await act(async () => { - render( - - - - - - - , - ); - }); + render( + + + + + + + , + ); + // No error is shown. expect(screen.queryByText('An error occurred', { exact: false })).toBeNull(); @@ -116,7 +117,7 @@ describe('', () => { }); // Mock the delete route and the refresh route to returns `addresses` without the first one. - const deleteUrl = 'https://joanie.endpoint/api/v1.0/addresses/' + address.id + '/'; + const deleteUrl = `https://joanie.endpoint/api/v1.0/addresses/${address.id}/`; fetchMock.delete(deleteUrl, []); fetchMock.get('https://joanie.endpoint/api/v1.0/addresses/', addresses.splice(1), { overwriteRoutes: true, @@ -124,13 +125,13 @@ describe('', () => { // Clicking on the delete button calls the delete API route. expect(fetchMock.called(deleteUrl)).toBe(false); - await act(async () => { - fireEvent.click(deleteButton); - }); + await userEvent.click(deleteButton); expect(fetchMock.called(deleteUrl)).toBe(true); // The address does not appear anymore in the list. - expect(screen.queryByText(address.title)).toBeNull(); + await waitFor(() => { + expect(screen.queryByText(address.title)).toBeNull(); + }); // No error is shown. expect(screen.queryByText('An error occurred', { exact: false })).toBeNull(); diff --git a/src/frontend/js/data/SessionProvider/JoanieSessionProvider.spec.tsx b/src/frontend/js/data/SessionProvider/JoanieSessionProvider.spec.tsx index 7a88ee891f..86e5d647bd 100644 --- a/src/frontend/js/data/SessionProvider/JoanieSessionProvider.spec.tsx +++ b/src/frontend/js/data/SessionProvider/JoanieSessionProvider.spec.tsx @@ -1,7 +1,7 @@ import fetchMock from 'fetch-mock'; import { IntlProvider } from 'react-intl'; import { QueryClientProvider } from '@tanstack/react-query'; -import { act, render, renderHook, waitFor } from '@testing-library/react'; +import { act, render, renderHook, screen, waitFor } from '@testing-library/react'; import { PropsWithChildren } from 'react'; import { ContextFactory as mockContextFactory, FonzieUserFactory } from 'utils/test/factories'; import { Deferred } from 'utils/test/deferred'; @@ -72,20 +72,20 @@ describe('JoanieSessionProvider', () => { fetchMock.get('https://auth.endpoint.test/api/v1.0/user/me', deferredUser.promise); - await act(async () => { - render(); - }); + render(); await act(async () => deferredUser.resolve(user)); await waitFor(async () => { - const calls = fetchMock.calls(); - expect(calls).toHaveLength(4); - expect(calls[0][0]).toEqual('https://auth.endpoint.test/api/v1.0/user/me'); - expect(calls[1][0]).toEqual('https://joanie.endpoint.test/api/v1.0/addresses/'); - expect(calls[2][0]).toEqual('https://joanie.endpoint.test/api/v1.0/credit-cards/'); - expect(calls[3][0]).toEqual('https://joanie.endpoint.test/api/v1.0/orders/'); + expect(fetchMock.calls()).toHaveLength(4); }); + const callUrls = fetchMock.calls().map((apiCall) => apiCall[0]); + expect(callUrls.sort()).toEqual([ + 'https://auth.endpoint.test/api/v1.0/user/me', + 'https://joanie.endpoint.test/api/v1.0/addresses/', + 'https://joanie.endpoint.test/api/v1.0/credit-cards/', + 'https://joanie.endpoint.test/api/v1.0/orders/', + ]); }); it('does not prefetch address, credit-cards, and order when user is anonymous', async () => { diff --git a/src/frontend/js/hooks/useAddresses.ts b/src/frontend/js/hooks/useAddresses.ts index e7d850b913..1e4c93e875 100644 --- a/src/frontend/js/hooks/useAddresses.ts +++ b/src/frontend/js/hooks/useAddresses.ts @@ -1,8 +1,7 @@ import { defineMessages } from 'react-intl'; -import { MutateOptions } from '@tanstack/react-query'; -import { useJoanieApi } from 'data/JoanieApiProvider'; -import { Address, AddressCreationPayload, API } from 'types/Joanie'; -import { HttpError } from 'utils/errors/HttpError'; +import { joanieApi } from 'api/joanie'; +import { Address } from 'api/joanie/gen'; +import { ApiResourceInterface } from 'types/Joanie'; import { ResourcesQuery, useResource, useResources, UseResourcesProps } from './useResources'; const messages = defineMessages({ @@ -33,15 +32,34 @@ const messages = defineMessages({ }, }); -export type AddressesMutateOptions = MutateOptions; - /** * Joanie Api hook to retrieve/create/update/delete addresses * owned by the authenticated user. */ -const props: UseResourcesProps = { +const props: UseResourcesProps> = { queryKey: ['addresses'], - apiInterface: () => useJoanieApi().user.addresses, + apiInterface: () => ({ + get: async (filters?: ResourcesQuery) => { + if (filters?.id) { + return joanieApi.addresses.addressesRead(filters?.id); + } + return joanieApi.addresses.addressesList(); + }, + create: (data: Address) => joanieApi.addresses.addressesCreate(data), + update: (data: Address) => { + const { id, ...updatedData } = data; + if (id) { + return joanieApi.addresses.addressesUpdate(id, updatedData); + } + throw new Error('api.addressesUpdate need a id.'); + }, + delete: (id?: string) => { + if (id) { + return joanieApi.addresses.addressesDelete(id); + } + throw new Error('api.addressesDelete need a id.'); + }, + }), omniscient: true, session: true, messages, diff --git a/src/frontend/js/hooks/useResources/index.spec.tsx b/src/frontend/js/hooks/useResources/index.spec.tsx index 6bd7fec37f..950ae0ad7e 100644 --- a/src/frontend/js/hooks/useResources/index.spec.tsx +++ b/src/frontend/js/hooks/useResources/index.spec.tsx @@ -223,7 +223,9 @@ describe('useResources (omniscient)', () => { }, ); - expect(result.current.states.isLoading).toBe(true); + await waitFor(() => { + expect(result.current.states.isLoading).toBe(true); + }); expect(result.current.item).toBe(undefined); expect(fetchMock.called('https://example.com/api/todos')).toBe(false); @@ -232,7 +234,8 @@ describe('useResources (omniscient)', () => { await waitFor(() => expect(JSON.stringify(result.current.item)).toBe(JSON.stringify(expectedTodo)), ); - expect(fetchMock.lastUrl()).toBe('https://example.com/api/todos'); + + expect(fetchMock.called('https://example.com/api/todos')).toBe(true); expect(result.current.states.isLoading).toBe(false); expect(result.current.states.fetching).toBe(false); }); @@ -276,7 +279,8 @@ describe('useResources (omniscient)', () => { rerender(expectedTodo.id); await waitFor(() => expect(result.current.item).toEqual(expectedTodo)); - expect(fetchMock.lastUrl()).toBe('https://example.com/api/todos'); + + expect(fetchMock.called('https://example.com/api/todos')).toBe(true); expect(result.current.states.isLoading).toBe(false); expect(result.current.states.fetching).toBe(false); diff --git a/src/frontend/js/hooks/useResources/index.tsx b/src/frontend/js/hooks/useResources/index.tsx index 13b4149883..3c75ceff35 100644 --- a/src/frontend/js/hooks/useResources/index.tsx +++ b/src/frontend/js/hooks/useResources/index.tsx @@ -11,7 +11,7 @@ export interface ResourcesQuery { } export interface Resource { - id: string; + id?: string; } export type QueryOptions = Omit< From 3f8bf2afdb1df3b857cf98797b2e872752a0385a Mon Sep 17 00:00:00 2001 From: Romain Le Cellier Date: Fri, 24 Feb 2023 16:20:58 +0100 Subject: [PATCH 06/12] =?UTF-8?q?=E2=9C=A8(frontend)=20useOrders=20with=20?= =?UTF-8?q?generated=20api=20client?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/frontend/js/api/joanie/index.ts | 6 +-- src/frontend/js/api/joanie/typeguards.ts | 9 +++++ .../js/components/SaleTunnel/index.spec.tsx | 9 +++-- src/frontend/js/hooks/useOrders.ts | 37 ++++++++++++++----- 4 files changed, 44 insertions(+), 17 deletions(-) create mode 100644 src/frontend/js/api/joanie/typeguards.ts diff --git a/src/frontend/js/api/joanie/index.ts b/src/frontend/js/api/joanie/index.ts index b582495e35..d27fa26935 100644 --- a/src/frontend/js/api/joanie/index.ts +++ b/src/frontend/js/api/joanie/index.ts @@ -1,7 +1,7 @@ import context from 'utils/context'; import { JOANIE_API_VERSION, RICHIE_USER_TOKEN } from 'settings'; import isTestEnv from 'utils/test/isTestEnv'; -import { ApiClientJoanie, ApiError, OpenAPIConfig } from './gen'; +import { ApiClientJoanie, OpenAPIConfig } from './gen'; /** * Build Joanie API Routes interface. @@ -29,6 +29,4 @@ const config: OpenAPIConfig = { export const joanieApi = new ApiClientJoanie(config); -export const isApiError = (error: unknown): error is ApiError => { - return (error as ApiError).name === 'ApiError'; -}; +export * from './typeguards'; diff --git a/src/frontend/js/api/joanie/typeguards.ts b/src/frontend/js/api/joanie/typeguards.ts new file mode 100644 index 0000000000..7922b099d1 --- /dev/null +++ b/src/frontend/js/api/joanie/typeguards.ts @@ -0,0 +1,9 @@ +import { ApiError, Course } from './gen'; + +export const isApiError = (error: unknown): error is ApiError => { + return (error as ApiError).name === 'ApiError'; +}; + +export function isCourse(course: Course | string): course is Course { + return (course as Course).code !== undefined; +} diff --git a/src/frontend/js/components/SaleTunnel/index.spec.tsx b/src/frontend/js/components/SaleTunnel/index.spec.tsx index b57981b25e..021879cac8 100644 --- a/src/frontend/js/components/SaleTunnel/index.spec.tsx +++ b/src/frontend/js/components/SaleTunnel/index.spec.tsx @@ -1,4 +1,5 @@ import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import fetchMock from 'fetch-mock'; import { Fragment } from 'react'; import { IntlProvider } from 'react-intl'; @@ -95,13 +96,13 @@ describe('SaleTunnel', () => { screen.getByRole('heading', { level: 2, name: 'SaleTunnelStepValidation Component' }); // focus should be set to the current step await waitFor(() => expect(document.activeElement?.getAttribute('aria-current')).toBe('step')); - fireEvent.click(screen.getByRole('button', { name: 'Next' })); + await userEvent.click(screen.getByRole('button', { name: 'Next' })); // - Step 2 : Payment screen.getByRole('heading', { level: 2, name: 'SaleTunnelStepPayment Component' }); // focus should be set to the current step expect(document.activeElement?.getAttribute('aria-current')).toBe('step'); - fireEvent.click(screen.getByRole('button', { name: 'Next' })); + await userEvent.click(screen.getByRole('button', { name: 'Next' })); // - Step 3 : Resume screen.getByRole('heading', { level: 2, name: 'SaleTunnelStepResume Component' }); @@ -109,8 +110,8 @@ describe('SaleTunnel', () => { expect(document.activeElement?.getAttribute('aria-current')).toBe('step'); // - Terminated, resume.onExit callback is triggered, orders should have been refetched. - fireEvent.click(screen.getByRole('button', { name: 'Next' })); - expect(fetchMock.lastUrl()).toBe('https://joanie.test/api/v1.0/orders/'); + await userEvent.click(screen.getByRole('button', { name: 'Next' })); + expect(fetchMock.called('https://joanie.test/api/v1.0/orders/')).toBe(true); expect(onClose).toHaveBeenCalledTimes(1); }); diff --git a/src/frontend/js/hooks/useOrders.ts b/src/frontend/js/hooks/useOrders.ts index 42499f2409..4711e6f249 100644 --- a/src/frontend/js/hooks/useOrders.ts +++ b/src/frontend/js/hooks/useOrders.ts @@ -1,7 +1,8 @@ import { defineMessages } from 'react-intl'; -import { API, Order, Product, Course, OrderState } from 'types/Joanie'; -import { useJoanieApi } from 'data/JoanieApiProvider'; +import { ApiResourceInterface } from 'types/Joanie'; +import { Order, Product, Course } from 'api/joanie/gen'; import { useSessionMutation } from 'utils/react-query/useSessionMutation'; +import { joanieApi, isCourse } from 'api/joanie'; import { QueryOptions, ResourcesQuery, @@ -11,9 +12,9 @@ import { } from './useResources'; type OrderResourcesQuery = ResourcesQuery & { - course?: Course['code']; + course?: Course['code'] | Course; product?: Product['id']; - state?: OrderState[]; + state?: Order.state; }; const messages = defineMessages({ @@ -29,6 +30,7 @@ const messages = defineMessages({ }, }); +// FIXME: function omniscientFiltering(data: Order[], filter: OrderResourcesQuery): Order[] { if (!filter) return data; @@ -39,17 +41,26 @@ function omniscientFiltering(data: Order[], filter: OrderResourcesQuery): Order[ // If filter.course is defined filter by order.course (!filter.course || (typeof order.course === 'string' && order.course === filter.course) || - (typeof order.course === 'object' && order.course?.code === filter.course)) && + (isCourse(order.course) && order.course?.code === filter.course)) && // If filter.product is defined filter by order.product (!filter.product || order.product === filter.product) && // If filter.state is defined filter by order.state - (!filter.state || filter.state.includes(order.state)), + // FIXME: in joanie api, Order.state have a default value but it apear to be optional in our openapi schema + (!filter.state || filter.state.includes(order.state || Order.state.PENDING)), ); } -const props: UseResourcesProps = { +const props: UseResourcesProps> = { queryKey: ['orders'], - apiInterface: () => useJoanieApi().user.orders, + apiInterface: () => ({ + get: async (filters?: ResourcesQuery) => { + if (filters?.id) { + return joanieApi.orders.ordersRead(filters?.id); + } + return joanieApi.orders.ordersList(); + }, + create: (data: Order) => joanieApi.orders.ordersCreate(data), + }), messages, omniscient: true, omniscientFiltering, @@ -58,7 +69,15 @@ const props: UseResourcesProps) => { const custom = useResourcesCustom({ ...props, filters, queryOptions }); - const abortHandler = useSessionMutation(useJoanieApi().user.orders.abort); + + const abortHandler = useSessionMutation((data: Order) => { + const { id, ...updatedData } = data; + if (id) { + return joanieApi.orders.ordersAbort(id, updatedData); + } + throw new Error('api.ordersAbort need a id.'); + }); + return { ...custom, methods: { From 81c63d0535478f64ea9979b6a06af243f0fbcc1c Mon Sep 17 00:00:00 2001 From: Romain Le Cellier Date: Fri, 24 Feb 2023 16:29:57 +0100 Subject: [PATCH 07/12] =?UTF-8?q?=E2=9C=A8(frontend)=20useCreditCards=20wi?= =?UTF-8?q?th=20generated=20api=20client?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/frontend/js/hooks/useCreditCards/index.ts | 33 ++++++++++++++++--- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/src/frontend/js/hooks/useCreditCards/index.ts b/src/frontend/js/hooks/useCreditCards/index.ts index c1cec421a2..75afd98844 100644 --- a/src/frontend/js/hooks/useCreditCards/index.ts +++ b/src/frontend/js/hooks/useCreditCards/index.ts @@ -1,8 +1,9 @@ import { defineMessages } from 'react-intl'; -import { CreditCard } from 'types/Joanie'; - -import { useJoanieApi } from '../../data/JoanieApiProvider'; -import { useResource, useResources, UseResourcesProps } from '../useResources'; +// import { CreditCard } from 'types/Joanie'; +import { CreditCard } from 'api/joanie/gen'; +import { joanieApi } from 'api/joanie'; +import { useJoanieApi } from 'data/JoanieApiProvider'; +import { ResourcesQuery, useResource, useResources, UseResourcesProps } from '../useResources'; const messages = defineMessages({ errorUpdate: { @@ -38,7 +39,29 @@ const messages = defineMessages({ */ const props: UseResourcesProps = { queryKey: ['creditCards'], - apiInterface: () => useJoanieApi().user.creditCards, + apiInterface: () => ({ + get: async (filters?: ResourcesQuery) => { + if (filters?.id) { + return joanieApi.creditCards.creditCardsRead(filters?.id); + } + return joanieApi.creditCards.creditCardsList(); + }, + // FIXME: creditCards.creditCardsCreate doesn't aprear on joanie's openapi schema + create: useJoanieApi().user.creditCards.create, + update: (data: CreditCard) => { + const { id, ...updatedData } = data; + if (id) { + return joanieApi.creditCards.creditCardsUpdate(id, updatedData); + } + throw new Error('api.creditCardsUpdate need a id.'); + }, + delete: (id?: string) => { + if (id) { + return joanieApi.creditCards.creditCardsDelete(id); + } + throw new Error('api.creditCardsDelete need a id.'); + }, + }), omniscient: true, session: true, messages, From 25a051be7863d7594c645093f2c0622f334e5b3d Mon Sep 17 00:00:00 2001 From: Romain Le Cellier Date: Fri, 24 Feb 2023 16:41:10 +0100 Subject: [PATCH 08/12] =?UTF-8?q?=E2=9C=A8(frontend)=20useEnrollments=20wi?= =?UTF-8?q?th=20generated=20api=20client?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/frontend/js/hooks/useEnrollments.ts | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/frontend/js/hooks/useEnrollments.ts b/src/frontend/js/hooks/useEnrollments.ts index 2896350173..4fe49fb559 100644 --- a/src/frontend/js/hooks/useEnrollments.ts +++ b/src/frontend/js/hooks/useEnrollments.ts @@ -1,7 +1,8 @@ import { defineMessages } from 'react-intl'; -import { useJoanieApi } from 'data/JoanieApiProvider'; +import { Enrollment } from 'api/joanie/gen'; import { ResourcesQuery, useResources, UseResourcesProps } from 'hooks/useResources'; -import { API, Enrollment } from 'types/Joanie'; +import { ApiResourceInterface } from 'types/Joanie'; +import { joanieApi } from 'api/joanie'; const messages = defineMessages({ errorUpdate: { @@ -31,9 +32,24 @@ const messages = defineMessages({ }, }); -const props: UseResourcesProps = { +const props: UseResourcesProps> = { queryKey: ['enrollments'], - apiInterface: () => useJoanieApi().user.enrollments, + apiInterface: () => ({ + get: async (filters?: ResourcesQuery) => { + if (filters?.id) { + return joanieApi.enrollments.enrollmentsRead(filters?.id); + } + return joanieApi.enrollments.enrollmentsList(); + }, + create: (data: Enrollment) => joanieApi.enrollments.enrollmentsCreate(data), + update: (data: Enrollment) => { + const { id, ...updatedData } = data; + if (id) { + return joanieApi.enrollments.enrollmentsUpdate(id, updatedData); + } + throw new Error('api.enrollmentsUpdate need a id.'); + }, + }), session: true, messages, onMutationSuccess: async (queryClient) => { From 11d282210c1ecce94bd3a59cd68f86ddb38e6c2e Mon Sep 17 00:00:00 2001 From: Romain Le Cellier Date: Fri, 24 Feb 2023 16:52:48 +0100 Subject: [PATCH 09/12] =?UTF-8?q?=F0=9F=9A=A7(frontend)=20useProducts=20wi?= =?UTF-8?q?th=20generated=20api=20client?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/frontend/js/hooks/useProduct.ts | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/frontend/js/hooks/useProduct.ts b/src/frontend/js/hooks/useProduct.ts index fb8972800a..049a1a0c0b 100644 --- a/src/frontend/js/hooks/useProduct.ts +++ b/src/frontend/js/hooks/useProduct.ts @@ -1,7 +1,8 @@ import { defineMessages } from 'react-intl'; -import { Product } from 'types/Joanie'; -import { useResource, UseResourcesProps } from 'hooks/useResources'; +import { Product } from 'api/joanie/gen'; +import { joanieApi } from 'api/joanie'; import { useJoanieApi } from 'data/JoanieApiProvider'; +import { ResourcesQuery, useResource, UseResourcesProps } from 'hooks/useResources'; const messages = defineMessages({ errorGet: { @@ -16,12 +17,24 @@ const messages = defineMessages({ }, }); +interface UseProductReadQuery extends ResourcesQuery { + course?: string; +} + /** * Joanie Api hook to retrieve a product through its id. */ const props: UseResourcesProps = { queryKey: ['products'], - apiInterface: () => useJoanieApi().products, + apiInterface: () => ({ + get: async (filters?: UseProductReadQuery) => { + const { id, course } = filters || {}; + if (id) { + return joanieApi.products.productsRead(id, course); + } + useJoanieApi().products.get(filters); + }, + }), messages, }; From 029281a7d43276bfd8ff2d33cb76c35b6ada57dd Mon Sep 17 00:00:00 2001 From: Romain Le Cellier Date: Tue, 28 Feb 2023 16:55:38 +0100 Subject: [PATCH 10/12] =?UTF-8?q?fixup!=20=F0=9F=91=BD=EF=B8=8F(chore):=20?= =?UTF-8?q?generate=20joanie=20api=20client?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/frontend/js/api/joanie/gen/index.ts | 3 ++ .../api/joanie/gen/models/OrderCreateBody.ts | 38 +++++++++++++++++++ .../joanie/gen/models/OrderCreateResponse.ts | 38 +++++++++++++++++++ .../js/api/joanie/gen/models/Payment.ts | 11 ++++++ .../joanie/gen/services/AddressesService.ts | 15 +++++++- .../gen/services/CertificatesService.ts | 15 +++++++- .../joanie/gen/services/CreditCardsService.ts | 15 +++++++- .../joanie/gen/services/EnrollmentsService.ts | 3 ++ .../api/joanie/gen/services/OrdersService.ts | 11 ++++-- 9 files changed, 140 insertions(+), 9 deletions(-) create mode 100644 src/frontend/js/api/joanie/gen/models/OrderCreateBody.ts create mode 100644 src/frontend/js/api/joanie/gen/models/OrderCreateResponse.ts create mode 100644 src/frontend/js/api/joanie/gen/models/Payment.ts diff --git a/src/frontend/js/api/joanie/gen/index.ts b/src/frontend/js/api/joanie/gen/index.ts index 3c1be5461a..dc13002391 100644 --- a/src/frontend/js/api/joanie/gen/index.ts +++ b/src/frontend/js/api/joanie/gen/index.ts @@ -17,6 +17,9 @@ export type { CourseRun } from './models/CourseRun'; export type { CreditCard } from './models/CreditCard'; export { Enrollment } from './models/Enrollment'; export { Order } from './models/Order'; +export { OrderCreateBody } from './models/OrderCreateBody'; +export { OrderCreateResponse } from './models/OrderCreateResponse'; +export type { Payment } from './models/Payment'; export { Product } from './models/Product'; export { AddressesService } from './services/AddressesService'; diff --git a/src/frontend/js/api/joanie/gen/models/OrderCreateBody.ts b/src/frontend/js/api/joanie/gen/models/OrderCreateBody.ts new file mode 100644 index 0000000000..c2d4c622bd --- /dev/null +++ b/src/frontend/js/api/joanie/gen/models/OrderCreateBody.ts @@ -0,0 +1,38 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { Address } from './Address'; + +export type OrderCreateBody = { + course: string; + /** + * date and time at which a record was created + */ + readonly created_on?: string; + readonly certificate?: string; + readonly enrollments?: string; + readonly id?: string; + readonly main_invoice?: string; + organization?: string; + readonly owner?: string; + readonly total?: number; + readonly total_currency?: string; + product: string; + readonly state?: OrderCreateBody.state; + readonly target_courses?: string; + billing_address?: Address; +}; + +export namespace OrderCreateBody { + + export enum state { + PENDING = 'pending', + CANCELED = 'canceled', + FAILED = 'failed', + VALIDATED = 'validated', + } + + +} + diff --git a/src/frontend/js/api/joanie/gen/models/OrderCreateResponse.ts b/src/frontend/js/api/joanie/gen/models/OrderCreateResponse.ts new file mode 100644 index 0000000000..313fef4ab8 --- /dev/null +++ b/src/frontend/js/api/joanie/gen/models/OrderCreateResponse.ts @@ -0,0 +1,38 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { Payment } from './Payment'; + +export type OrderCreateResponse = { + course: string; + /** + * date and time at which a record was created + */ + readonly created_on?: string; + readonly certificate?: string; + readonly enrollments?: string; + id: string; + readonly main_invoice?: string; + organization?: string; + readonly owner?: string; + readonly total?: number; + readonly total_currency?: string; + product: string; + readonly state?: OrderCreateResponse.state; + readonly target_courses?: string; + payment_info?: Payment; +}; + +export namespace OrderCreateResponse { + + export enum state { + PENDING = 'pending', + CANCELED = 'canceled', + FAILED = 'failed', + VALIDATED = 'validated', + } + + +} + diff --git a/src/frontend/js/api/joanie/gen/models/Payment.ts b/src/frontend/js/api/joanie/gen/models/Payment.ts new file mode 100644 index 0000000000..29da594251 --- /dev/null +++ b/src/frontend/js/api/joanie/gen/models/Payment.ts @@ -0,0 +1,11 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +export type Payment = { + payment_id: string; + provider: string; + url: string; + is_paid?: boolean; +}; + diff --git a/src/frontend/js/api/joanie/gen/services/AddressesService.ts b/src/frontend/js/api/joanie/gen/services/AddressesService.ts index c7c3c68046..4c6f594b24 100644 --- a/src/frontend/js/api/joanie/gen/services/AddressesService.ts +++ b/src/frontend/js/api/joanie/gen/services/AddressesService.ts @@ -39,13 +39,24 @@ export class AddressesService { * * DELETE /api/addresses// * Delete selected address - * @returns Address + * @param page A page number within the paginated result set. + * @returns any * @throws ApiError */ - public addressesList(): CancelablePromise> { + public addressesList( + page?: number, + ): CancelablePromise<{ + count: number; + next?: string | null; + previous?: string | null; + results: Array
; + }> { return this.httpRequest.request({ method: 'GET', url: '/addresses/', + query: { + 'page': page, + }, }); } diff --git a/src/frontend/js/api/joanie/gen/services/CertificatesService.ts b/src/frontend/js/api/joanie/gen/services/CertificatesService.ts index a4e44edc38..b75c1f9f01 100644 --- a/src/frontend/js/api/joanie/gen/services/CertificatesService.ts +++ b/src/frontend/js/api/joanie/gen/services/CertificatesService.ts @@ -18,13 +18,24 @@ export class CertificatesService { * * GET /api/certificates/:certificate_id/download * Return the certificate document in PDF format. - * @returns Certificate + * @param page A page number within the paginated result set. + * @returns any * @throws ApiError */ - public certificatesList(): CancelablePromise> { + public certificatesList( + page?: number, + ): CancelablePromise<{ + count: number; + next?: string | null; + previous?: string | null; + results: Array; + }> { return this.httpRequest.request({ method: 'GET', url: '/certificates/', + query: { + 'page': page, + }, }); } diff --git a/src/frontend/js/api/joanie/gen/services/CreditCardsService.ts b/src/frontend/js/api/joanie/gen/services/CreditCardsService.ts index af5816297b..41fd941306 100644 --- a/src/frontend/js/api/joanie/gen/services/CreditCardsService.ts +++ b/src/frontend/js/api/joanie/gen/services/CreditCardsService.ts @@ -22,13 +22,24 @@ export class CreditCardsService { * * DELETE /api/credit-cards/ * Delete the selected credit card - * @returns CreditCard + * @param page A page number within the paginated result set. + * @returns any * @throws ApiError */ - public creditCardsList(): CancelablePromise> { + public creditCardsList( + page?: number, + ): CancelablePromise<{ + count: number; + next?: string | null; + previous?: string | null; + results: Array; + }> { return this.httpRequest.request({ method: 'GET', url: '/credit-cards/', + query: { + 'page': page, + }, }); } diff --git a/src/frontend/js/api/joanie/gen/services/EnrollmentsService.ts b/src/frontend/js/api/joanie/gen/services/EnrollmentsService.ts index f5ad9bd6f9..88f519e80b 100644 --- a/src/frontend/js/api/joanie/gen/services/EnrollmentsService.ts +++ b/src/frontend/js/api/joanie/gen/services/EnrollmentsService.ts @@ -15,6 +15,7 @@ export class EnrollmentsService { * @param courseRun * @param wasCreatedByOrder * @param page A page number within the paginated result set. + * @param pageSize Number of results to return per page. * @returns any * @throws ApiError */ @@ -22,6 +23,7 @@ export class EnrollmentsService { courseRun?: string, wasCreatedByOrder?: string, page?: number, + pageSize?: number, ): CancelablePromise<{ count: number; next?: string | null; @@ -35,6 +37,7 @@ export class EnrollmentsService { 'course_run': courseRun, 'was_created_by_order': wasCreatedByOrder, 'page': page, + 'page_size': pageSize, }, }); } diff --git a/src/frontend/js/api/joanie/gen/services/OrdersService.ts b/src/frontend/js/api/joanie/gen/services/OrdersService.ts index 196c99157c..e27c546fcf 100644 --- a/src/frontend/js/api/joanie/gen/services/OrdersService.ts +++ b/src/frontend/js/api/joanie/gen/services/OrdersService.ts @@ -2,6 +2,8 @@ /* tslint:disable */ /* eslint-disable */ import type { Order } from '../models/Order'; +import type { OrderCreateBody } from '../models/OrderCreateBody'; +import type { OrderCreateResponse } from '../models/OrderCreateResponse'; import type { CancelablePromise } from '../core/CancelablePromise'; import type { BaseHttpRequest } from '../core/BaseHttpRequest'; @@ -24,6 +26,7 @@ export class OrdersService { * @param course * @param state * @param page A page number within the paginated result set. + * @param pageSize Number of results to return per page. * @returns any * @throws ApiError */ @@ -32,6 +35,7 @@ export class OrdersService { course?: string, state?: string, page?: number, + pageSize?: number, ): CancelablePromise<{ count: number; next?: string | null; @@ -46,6 +50,7 @@ export class OrdersService { 'course': course, 'state': state, 'page': page, + 'page_size': pageSize, }, }); } @@ -53,12 +58,12 @@ export class OrdersService { /** * Try to create an order and a related payment if the payment is fee. * @param data - * @returns Order + * @returns OrderCreateResponse * @throws ApiError */ public ordersCreate( - data: Order, - ): CancelablePromise { + data: OrderCreateBody, + ): CancelablePromise { return this.httpRequest.request({ method: 'POST', url: '/orders/', From 12614aae347292ca69d1dbbb79e0f29cea7a3477 Mon Sep 17 00:00:00 2001 From: Romain Le Cellier Date: Tue, 28 Feb 2023 16:56:18 +0100 Subject: [PATCH 11/12] =?UTF-8?q?=F0=9F=9A=A7(fronend)=20Try=20to=20deal?= =?UTF-8?q?=20with=20PaymentButton=20and=20OrderServices=20api=20calls?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../js/components/PaymentButton/index.tsx | 25 ++++++++----------- src/frontend/js/hooks/useOrders.ts | 11 +++++--- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/src/frontend/js/components/PaymentButton/index.tsx b/src/frontend/js/components/PaymentButton/index.tsx index b5d7c279f0..94281bc0e0 100644 --- a/src/frontend/js/components/PaymentButton/index.tsx +++ b/src/frontend/js/components/PaymentButton/index.tsx @@ -1,13 +1,12 @@ import { useEffect, useMemo, useRef, useState } from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; +import { joanieApi } from 'api/joanie'; +import { Address, CreditCard, Order, Payment, Product } from 'api/joanie/gen'; import PaymentInterface from 'components/PaymentInterfaces'; import { Spinner } from 'components/Spinner'; import { useCourseProduct } from 'data/CourseProductProvider'; -import { useJoanieApi } from 'data/JoanieApiProvider'; import { useOrders } from 'hooks/useOrders'; import { PAYMENT_SETTINGS } from 'settings'; -import type * as Joanie from 'types/Joanie'; -import { OrderState } from 'types/Joanie'; import type { Nullable } from 'types/utils'; const messages = defineMessages({ @@ -56,14 +55,13 @@ export enum PaymentErrorMessageId { } interface PaymentButtonProps { - product: Joanie.Product; - billingAddress?: Joanie.Address; - creditCard?: Nullable; + product: Product; + billingAddress?: Address; + creditCard?: Nullable; onSuccess: () => void; } -type PaymentInfo = Joanie.Payment & { order_id: string }; -type OneClickPaymentInfo = Joanie.PaymentOneClick & { order_id: string }; +type PaymentInfo = Payment & { order_id: string }; enum ComponentStates { IDLE = 'idle', @@ -78,7 +76,6 @@ enum ComponentStates { */ const PaymentButton = ({ product, billingAddress, creditCard, onSuccess }: PaymentButtonProps) => { const intl = useIntl(); - const API = useJoanieApi(); const timeoutRef = useRef(); const { courseCode } = useCourseProduct(); const orderManager = useOrders(); @@ -87,7 +84,7 @@ const PaymentButton = ({ product, billingAddress, creditCard, onSuccess }: Payme return courseCode && product.id && billingAddress; }, [product, courseCode, billingAddress]); - const [payment, setPayment] = useState(); + const [payment, setPayment] = useState(); const [state, setState] = useState(ComponentStates.IDLE); const [error, setError] = useState(PaymentErrorMessageId.ERROR_DEFAULT); @@ -98,8 +95,8 @@ const PaymentButton = ({ product, billingAddress, creditCard, onSuccess }: Payme * @returns {Promise} - Promise resolving to true if order is validated */ const isOrderValidated = async (id: string): Promise => { - const order = await API.user.orders.get({ id }); - return order?.state === OrderState.VALIDATED; + const order = await joanieApi.orders.ordersRead(id); + return order?.state === Order.state.VALIDATED; }; /** type guard to check if the payment is a payment one click */ @@ -122,7 +119,7 @@ const PaymentButton = ({ product, billingAddress, creditCard, onSuccess }: Payme billing_address: billingAddress!, ...(creditCard && { credit_card_id: creditCard }), course: courseCode, - product: product.id, + product: product.id!, }, { onSuccess: (order) => { @@ -171,7 +168,7 @@ const PaymentButton = ({ product, billingAddress, creditCard, onSuccess }: Payme }; useEffect(() => { - if (isOneClickPayment(payment) && state === ComponentStates.LOADING) { + if (payment?.is_paid && state === ComponentStates.LOADING) { handleSuccess(); } diff --git a/src/frontend/js/hooks/useOrders.ts b/src/frontend/js/hooks/useOrders.ts index 4711e6f249..5cde9de020 100644 --- a/src/frontend/js/hooks/useOrders.ts +++ b/src/frontend/js/hooks/useOrders.ts @@ -1,10 +1,11 @@ import { defineMessages } from 'react-intl'; import { ApiResourceInterface } from 'types/Joanie'; -import { Order, Product, Course } from 'api/joanie/gen'; +import { Order, Product, Course, OrderCreateResponse, OrderCreateBody } from 'api/joanie/gen'; import { useSessionMutation } from 'utils/react-query/useSessionMutation'; import { joanieApi, isCourse } from 'api/joanie'; import { QueryOptions, + Resource, ResourcesQuery, useResource, useResourcesCustom, @@ -50,7 +51,11 @@ function omniscientFiltering(data: Order[], filter: OrderResourcesQuery): Order[ ); } -const props: UseResourcesProps> = { +interface OrderApiResourceInterface extends ApiResourceInterface { + create: (payload: OrderCreateBody) => Promise; +} + +const props: UseResourcesProps = { queryKey: ['orders'], apiInterface: () => ({ get: async (filters?: ResourcesQuery) => { @@ -59,7 +64,7 @@ const props: UseResourcesProps joanieApi.orders.ordersCreate(data), + create: (data: OrderCreateBody) => joanieApi.orders.ordersCreate(data), }), messages, omniscient: true, From 254e4720c322e4b62b88bc33ce1e4219d40b084f Mon Sep 17 00:00:00 2001 From: Romain Le Cellier Date: Mon, 6 Mar 2023 15:08:13 +0100 Subject: [PATCH 12/12] [to remove] wip with create serializer --- src/frontend/js/api/joanie/gen/index.ts | 5 +++- .../js/api/joanie/gen/models/Order.ts | 2 +- .../api/joanie/gen/models/OrderCreateBody.ts | 2 +- .../joanie/gen/models/OrderCreateResponse.ts | 30 ++----------------- .../gen/services/CertificatesService.ts | 4 +-- .../api/joanie/gen/services/OrdersService.ts | 16 ++++++---- .../joanie/gen/services/ProductsService.ts | 2 +- .../components/PaymentButton/index.spec.tsx | 2 +- .../js/components/PaymentButton/index.tsx | 4 +-- src/frontend/js/hooks/useOrders.ts | 16 ++++++++-- src/frontend/js/types/Joanie.ts | 4 +-- 11 files changed, 40 insertions(+), 47 deletions(-) diff --git a/src/frontend/js/api/joanie/gen/index.ts b/src/frontend/js/api/joanie/gen/index.ts index dc13002391..3de95046e2 100644 --- a/src/frontend/js/api/joanie/gen/index.ts +++ b/src/frontend/js/api/joanie/gen/index.ts @@ -15,10 +15,13 @@ export type { CertificationDefinition } from './models/CertificationDefinition'; export type { Course } from './models/Course'; export type { CourseRun } from './models/CourseRun'; export type { CreditCard } from './models/CreditCard'; +export type { EmptyResponse } from './models/EmptyResponse'; export { Enrollment } from './models/Enrollment'; +export type { ErrorResponse } from './models/ErrorResponse'; export { Order } from './models/Order'; +export type { OrderAbortBody } from './models/OrderAbortBody'; export { OrderCreateBody } from './models/OrderCreateBody'; -export { OrderCreateResponse } from './models/OrderCreateResponse'; +export type { OrderCreateResponse } from './models/OrderCreateResponse'; export type { Payment } from './models/Payment'; export { Product } from './models/Product'; diff --git a/src/frontend/js/api/joanie/gen/models/Order.ts b/src/frontend/js/api/joanie/gen/models/Order.ts index 972500d6d7..9432b91f4e 100644 --- a/src/frontend/js/api/joanie/gen/models/Order.ts +++ b/src/frontend/js/api/joanie/gen/models/Order.ts @@ -10,7 +10,7 @@ export type Order = { readonly created_on?: string; readonly certificate?: string; readonly enrollments?: string; - readonly id?: string; + id: string; readonly main_invoice?: string; organization?: string; readonly owner?: string; diff --git a/src/frontend/js/api/joanie/gen/models/OrderCreateBody.ts b/src/frontend/js/api/joanie/gen/models/OrderCreateBody.ts index c2d4c622bd..0c45302b70 100644 --- a/src/frontend/js/api/joanie/gen/models/OrderCreateBody.ts +++ b/src/frontend/js/api/joanie/gen/models/OrderCreateBody.ts @@ -12,7 +12,7 @@ export type OrderCreateBody = { readonly created_on?: string; readonly certificate?: string; readonly enrollments?: string; - readonly id?: string; + id: string; readonly main_invoice?: string; organization?: string; readonly owner?: string; diff --git a/src/frontend/js/api/joanie/gen/models/OrderCreateResponse.ts b/src/frontend/js/api/joanie/gen/models/OrderCreateResponse.ts index 313fef4ab8..27bc0fe147 100644 --- a/src/frontend/js/api/joanie/gen/models/OrderCreateResponse.ts +++ b/src/frontend/js/api/joanie/gen/models/OrderCreateResponse.ts @@ -2,37 +2,11 @@ /* tslint:disable */ /* eslint-disable */ +import type { Order } from './Order'; import type { Payment } from './Payment'; export type OrderCreateResponse = { - course: string; - /** - * date and time at which a record was created - */ - readonly created_on?: string; - readonly certificate?: string; - readonly enrollments?: string; - id: string; - readonly main_invoice?: string; - organization?: string; - readonly owner?: string; - readonly total?: number; - readonly total_currency?: string; - product: string; - readonly state?: OrderCreateResponse.state; - readonly target_courses?: string; + order: Order; payment_info?: Payment; }; -export namespace OrderCreateResponse { - - export enum state { - PENDING = 'pending', - CANCELED = 'canceled', - FAILED = 'failed', - VALIDATED = 'validated', - } - - -} - diff --git a/src/frontend/js/api/joanie/gen/services/CertificatesService.ts b/src/frontend/js/api/joanie/gen/services/CertificatesService.ts index b75c1f9f01..098976ae31 100644 --- a/src/frontend/js/api/joanie/gen/services/CertificatesService.ts +++ b/src/frontend/js/api/joanie/gen/services/CertificatesService.ts @@ -66,12 +66,12 @@ export class CertificatesService { /** * Retrieve a certificate through its id if it is owned by the authenticated user. * @param id - * @returns Certificate + * @returns binary File Attachment * @throws ApiError */ public certificatesDownload( id: string, - ): CancelablePromise { + ): CancelablePromise { return this.httpRequest.request({ method: 'GET', url: '/certificates/{id}/download/', diff --git a/src/frontend/js/api/joanie/gen/services/OrdersService.ts b/src/frontend/js/api/joanie/gen/services/OrdersService.ts index e27c546fcf..5449b7c282 100644 --- a/src/frontend/js/api/joanie/gen/services/OrdersService.ts +++ b/src/frontend/js/api/joanie/gen/services/OrdersService.ts @@ -2,6 +2,7 @@ /* tslint:disable */ /* eslint-disable */ import type { Order } from '../models/Order'; +import type { OrderAbortBody } from '../models/OrderAbortBody'; import type { OrderCreateBody } from '../models/OrderCreateBody'; import type { OrderCreateResponse } from '../models/OrderCreateResponse'; @@ -101,13 +102,13 @@ export class OrdersService { * Abort a pending order and the related payment if there is one. * @param id * @param data - * @returns Order + * @returns void * @throws ApiError */ public ordersAbort( id: string, - data: Order, - ): CancelablePromise { + data: OrderAbortBody, + ): CancelablePromise { return this.httpRequest.request({ method: 'POST', url: '/orders/{id}/abort/', @@ -122,18 +123,23 @@ export class OrdersService { * Retrieve an invoice through its reference if it is related to * the order instance and owned by the authenticated user. * @param id - * @returns Order + * @param reference + * @returns binary File Attachment * @throws ApiError */ public ordersInvoice( id: string, - ): CancelablePromise { + reference: string, + ): CancelablePromise { return this.httpRequest.request({ method: 'GET', url: '/orders/{id}/invoice/', path: { 'id': id, }, + query: { + 'reference': reference, + }, }); } diff --git a/src/frontend/js/api/joanie/gen/services/ProductsService.ts b/src/frontend/js/api/joanie/gen/services/ProductsService.ts index 4d85fd8395..baf269398f 100644 --- a/src/frontend/js/api/joanie/gen/services/ProductsService.ts +++ b/src/frontend/js/api/joanie/gen/services/ProductsService.ts @@ -19,7 +19,7 @@ export class ProductsService { */ public productsRead( id: string, - course?: string, + course: string, ): CancelablePromise { return this.httpRequest.request({ method: 'GET', diff --git a/src/frontend/js/components/PaymentButton/index.spec.tsx b/src/frontend/js/components/PaymentButton/index.spec.tsx index f3a75d04a7..f8c4724219 100644 --- a/src/frontend/js/components/PaymentButton/index.spec.tsx +++ b/src/frontend/js/components/PaymentButton/index.spec.tsx @@ -1,4 +1,4 @@ -import { act, fireEvent, render, screen, waitFor, cleanup } from '@testing-library/react'; +simport { act, fireEvent, render, screen, waitFor, cleanup } from '@testing-library/react'; import fetchMock from 'fetch-mock'; import { PropsWithChildren } from 'react'; import { IntlProvider } from 'react-intl'; diff --git a/src/frontend/js/components/PaymentButton/index.tsx b/src/frontend/js/components/PaymentButton/index.tsx index 94281bc0e0..ffbc3abcd9 100644 --- a/src/frontend/js/components/PaymentButton/index.tsx +++ b/src/frontend/js/components/PaymentButton/index.tsx @@ -122,9 +122,9 @@ const PaymentButton = ({ product, billingAddress, creditCard, onSuccess }: Payme product: product.id!, }, { - onSuccess: (order) => { + onSuccess: ({ order, payment_info }) => { paymentInfos = { - ...order.payment_info, + ...payment_info, order_id: order.id, }; setPayment(paymentInfos); diff --git a/src/frontend/js/hooks/useOrders.ts b/src/frontend/js/hooks/useOrders.ts index 5cde9de020..3e3a67fd59 100644 --- a/src/frontend/js/hooks/useOrders.ts +++ b/src/frontend/js/hooks/useOrders.ts @@ -1,11 +1,17 @@ import { defineMessages } from 'react-intl'; import { ApiResourceInterface } from 'types/Joanie'; -import { Order, Product, Course, OrderCreateResponse, OrderCreateBody } from 'api/joanie/gen'; +import { + Order, + Product, + Course, + OrderCreateResponse, + OrderCreateBody, + OrderAbortBody, +} from 'api/joanie/gen'; import { useSessionMutation } from 'utils/react-query/useSessionMutation'; import { joanieApi, isCourse } from 'api/joanie'; import { QueryOptions, - Resource, ResourcesQuery, useResource, useResourcesCustom, @@ -72,10 +78,14 @@ const props: UseResourcesProps) => { const custom = useResourcesCustom({ ...props, filters, queryOptions }); - const abortHandler = useSessionMutation((data: Order) => { + const abortHandler = useSessionMutation((data: OrderAbordData) => { const { id, ...updatedData } = data; if (id) { return joanieApi.orders.ordersAbort(id, updatedData); diff --git a/src/frontend/js/types/Joanie.ts b/src/frontend/js/types/Joanie.ts index bde8eb948e..ef6a32f21e 100644 --- a/src/frontend/js/types/Joanie.ts +++ b/src/frontend/js/types/Joanie.ts @@ -233,8 +233,8 @@ export interface ApiResourceInterface< TResourceQuery extends ResourcesQuery = ResourcesQuery, > { get: (filters?: TResourceQuery) => any; - create?: (payload: any) => Promise; - update?: (payload: any) => Promise; + create?: (payload: any) => Promise; + update?: (payload: any) => Promise; delete?: (id: TData['id']) => Promise; }