adjust frontend to new backend
This commit is contained in:
25
frontend/openapitools.json
Normal file
25
frontend/openapitools.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"$schema": "./node_modules/@openapitools/openapi-generator-cli/config.schema.json",
|
||||||
|
"spaces": 2,
|
||||||
|
"generator-cli": {
|
||||||
|
"version": "6.6.0",
|
||||||
|
"generators": {
|
||||||
|
"typescript": {
|
||||||
|
"generatorName": "typescript",
|
||||||
|
"output": "../oas/",
|
||||||
|
"glob": "../swagger.json",
|
||||||
|
"removeOperationIdPrefix": true,
|
||||||
|
"templateDir": "../templates/",
|
||||||
|
"skipValidateSpec": true,
|
||||||
|
"additionalProperties": {
|
||||||
|
"npmName": "ai-oas",
|
||||||
|
"npmVersion": "1.0.0",
|
||||||
|
"withInterfaces": true,
|
||||||
|
"platform": "browser",
|
||||||
|
"framework": "fetch-api",
|
||||||
|
"supportsES6": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2600
frontend/package-lock.json
generated
2600
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -4,18 +4,26 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"author": "wea_ondara",
|
"author": "wea_ondara",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
|
"type": "module",
|
||||||
|
"node": ">=21.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"setup": "run-s setup:1 setup:2",
|
||||||
|
"setup:1": "npm run build:oas",
|
||||||
|
"setup:2": "npm i --save-optional ../oas",
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "run-p type-check \"build-only {@}\" --",
|
"clean": "rm -r ../oas dev/ dist/ 2>/dev/null",
|
||||||
|
"build": "run-s build:oas build:client",
|
||||||
|
"build:oas": "run-s generate:oas build-only:oas",
|
||||||
|
"build:client": "run-p type-check:client build-only:client test:unit",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"test:unit": "vitest",
|
"test:unit": "vitest --environment jsdom --root src/ --passWithNoTests",
|
||||||
"test:e2e": "start-server-and-test preview http://localhost:4173 'cypress run --e2e'",
|
"test:e2e": "start-server-and-test preview :4173 'cypress run --e2e'",
|
||||||
"test:e2e:dev": "start-server-and-test 'vite dev --port 4173' http://localhost:4173 'cypress open --e2e'",
|
"test:e2e:dev": "start-server-and-test 'vite dev --port 4173' :4173 'cypress open --e2e'",
|
||||||
"build-only": "vite build",
|
"build-only:oas": "cd ../oas && npm i && npm run build",
|
||||||
"type-check": "vue-tsc --noEmit -p tsconfig.vitest.json --composite false",
|
"build-only:client": "vite build --outDir dist",
|
||||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
|
"generate:oas": "openapi-generator-cli generate",
|
||||||
"format": "prettier --write src/",
|
"type-check:client": "vue-tsc --noEmit -p tsconfig.vitest.json",
|
||||||
"clean:src": "find src | grep -E '((\\.d\\.ts)|(\\.js(\\.map)?))$' | xargs rm"
|
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-free": "^6.5.1",
|
"@fortawesome/fontawesome-free": "^6.5.1",
|
||||||
@@ -23,13 +31,16 @@
|
|||||||
"@popperjs/core": "^2.11.8",
|
"@popperjs/core": "^2.11.8",
|
||||||
"bootstrap": "^5.3.3",
|
"bootstrap": "^5.3.3",
|
||||||
"bootstrap-vue-next": "^0.16.6",
|
"bootstrap-vue-next": "^0.16.6",
|
||||||
|
"gravatar": "^1.8.2",
|
||||||
"html-entities": "^2.5.2",
|
"html-entities": "^2.5.2",
|
||||||
"idb": "^7.1.1",
|
"idb": "^7.1.1",
|
||||||
|
"mitt": "^3.0.1",
|
||||||
"pinia": "^2.1.7",
|
"pinia": "^2.1.7",
|
||||||
"pinia-class-component": "^0.9.4",
|
"pinia-class-component": "^0.9.4",
|
||||||
"string-similarity-js": "^2.1.4",
|
"string-similarity-js": "^2.1.4",
|
||||||
"vue": "^3.3.4",
|
"vue": "^3.3.4",
|
||||||
"vue-class-component": "^8.0.0-rc.1",
|
"vue-class-component": "^8.0.0-rc.1",
|
||||||
|
"vue-facing-decorator": "^3.0.4",
|
||||||
"vue-good-table-next": "^0.2.1",
|
"vue-good-table-next": "^0.2.1",
|
||||||
"vue-i18n": "^9.10.2",
|
"vue-i18n": "^9.10.2",
|
||||||
"vue-property-decorator": "^10.0.0-rc.3",
|
"vue-property-decorator": "^10.0.0-rc.3",
|
||||||
@@ -37,28 +48,33 @@
|
|||||||
"vue3-toastify": "^0.0.4"
|
"vue3-toastify": "^0.0.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rushstack/eslint-patch": "^1.8.0",
|
"@openapitools/openapi-generator-cli": "^2.13.4",
|
||||||
"@tsconfig/node18": "^18.2.2",
|
"@rushstack/eslint-patch": "^1.10.3",
|
||||||
|
"@tsconfig/node20": "^20.1.4",
|
||||||
"@types/bootstrap": "^5.2.10",
|
"@types/bootstrap": "^5.2.10",
|
||||||
|
"@types/gravatar": "^1.8.6",
|
||||||
"@types/jsdom": "^21.1.6",
|
"@types/jsdom": "^21.1.6",
|
||||||
"@types/node": "^18.19.26",
|
"@types/vue-select": "^3.16.8",
|
||||||
"@vitejs/plugin-vue": "^4.6.2",
|
"@vitejs/plugin-vue": "^5.0.4",
|
||||||
"@vue/eslint-config-prettier": "^8.0.0",
|
"@vue/eslint-config-prettier": "^9.0.0",
|
||||||
"@vue/eslint-config-typescript": "^12.0.0",
|
"@vue/eslint-config-typescript": "^13.0.0",
|
||||||
"@vue/test-utils": "^2.4.5",
|
"@vue/test-utils": "^2.4.6",
|
||||||
"@vue/tsconfig": "^0.4.0",
|
"@vue/tsconfig": "^0.5.1",
|
||||||
"cypress": "^13.7.1",
|
"cypress": "^13.10.0",
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^8.57.0",
|
||||||
"eslint-plugin-cypress": "^2.15.1",
|
"eslint-plugin-cypress": "^3.2.0",
|
||||||
"eslint-plugin-vue": "^9.23.0",
|
"eslint-plugin-vue": "^9.26.0",
|
||||||
"jsdom": "^22.1.0",
|
"jsdom": "^24.0.0",
|
||||||
"npm-run-all2": "^6.1.2",
|
"npm-run-all2": "^6.2.0",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.2.5",
|
||||||
"sass": "^1.72.0",
|
"sass": "^1.77.2",
|
||||||
"start-server-and-test": "^2.0.3",
|
"start-server-and-test": "^2.0.3",
|
||||||
"typescript": "~5.2.0",
|
"typescript": "~5.4.0",
|
||||||
"vite": "^4.5.2",
|
"vite": "^5.2.11",
|
||||||
"vitest": "^0.34.6",
|
"vitest": "^1.6.0",
|
||||||
"vue-tsc": "^1.8.27"
|
"vue-tsc": "^2.0.19"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"ai-oas": "file:../oas"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
26
frontend/src/components/Bootstrap/Alert.vue
Normal file
26
frontend/src/components/Bootstrap/Alert.vue
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {Options, Vue} from 'vue-class-component';
|
||||||
|
import {Prop} from 'vue-property-decorator';
|
||||||
|
|
||||||
|
export type AlertType =
|
||||||
|
'alert-primary'
|
||||||
|
| 'alert-secondary'
|
||||||
|
| 'alert-success'
|
||||||
|
| 'alert-danger'
|
||||||
|
| 'alert-warning'
|
||||||
|
| 'alert-light'
|
||||||
|
| 'alert-dark';
|
||||||
|
@Options({
|
||||||
|
name: 'Alert',
|
||||||
|
})
|
||||||
|
export default class Alert extends Vue {
|
||||||
|
@Prop({default: null})
|
||||||
|
readonly type: AlertType | null = 'alert-primary';
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="alert" :class="[type]" role="alert">
|
||||||
|
<slot/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
21
frontend/src/components/CenterOnParent.vue
Normal file
21
frontend/src/components/CenterOnParent.vue
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {Options, Vue} from 'vue-class-component';
|
||||||
|
|
||||||
|
@Options({
|
||||||
|
name: 'CenterOnParent',
|
||||||
|
components: {},
|
||||||
|
})
|
||||||
|
export default class CenterOnParent extends Vue {
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="w-100 h-100 d-flex justify-content-center">
|
||||||
|
<div class="align-self-center">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
</style>
|
||||||
18
frontend/src/components/Loading.vue
Normal file
18
frontend/src/components/Loading.vue
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {Options, Vue} from 'vue-class-component';
|
||||||
|
import CenterOnParent from '@/components/CenterOnParent.vue';
|
||||||
|
import Spinner from '@/components/Spinner.vue';
|
||||||
|
|
||||||
|
@Options({
|
||||||
|
name: 'Loading',
|
||||||
|
components: {Spinner, CenterOnParent},
|
||||||
|
})
|
||||||
|
export default class Loading extends Vue {
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CenterOnParent>
|
||||||
|
<Spinner/>
|
||||||
|
</CenterOnParent>
|
||||||
|
</template>
|
||||||
38
frontend/src/components/Spinner.vue
Normal file
38
frontend/src/components/Spinner.vue
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {Options, Vue} from 'vue-class-component';
|
||||||
|
import {Prop} from 'vue-property-decorator';
|
||||||
|
|
||||||
|
export type SpinnerType =
|
||||||
|
'text-primary'
|
||||||
|
| 'text-secondary'
|
||||||
|
| 'text-success'
|
||||||
|
| 'text-danger'
|
||||||
|
| 'text-warning'
|
||||||
|
| 'text-info'
|
||||||
|
| 'text-light'
|
||||||
|
| 'text-dark';
|
||||||
|
|
||||||
|
@Options({
|
||||||
|
name: 'Spinner',
|
||||||
|
components: {},
|
||||||
|
})
|
||||||
|
export default class Spinner extends Vue {
|
||||||
|
@Prop({default: null})
|
||||||
|
private readonly style!: SpinnerType | null;
|
||||||
|
@Prop({default: null})
|
||||||
|
private readonly size!: number | null;
|
||||||
|
|
||||||
|
private get internalStyle(): any {
|
||||||
|
return {
|
||||||
|
height: this.size + 'em',
|
||||||
|
width: this.size + 'em',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="spinner-border" :class="[style]" :style="internalStyle" role="status">
|
||||||
|
<span class="visually-hidden">Loading...</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
80
frontend/src/components/auth/LoginComponent.vue
Normal file
80
frontend/src/components/auth/LoginComponent.vue
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {ApiException, LoginRequestVmV1, LoginResponseVmV1} from 'ai-oas';
|
||||||
|
import {Component, Vue} from 'vue-facing-decorator';
|
||||||
|
import {SessionStore} from '@/stores/SessionStore';
|
||||||
|
import {ApiStore} from '@/stores/ApiStore';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
name: 'LoginComponent',
|
||||||
|
components: {},
|
||||||
|
emits: ['loggedIn'],
|
||||||
|
})
|
||||||
|
export default class LoginComponent extends Vue {
|
||||||
|
username: string = '';
|
||||||
|
password: string = '';
|
||||||
|
|
||||||
|
error: string = '';
|
||||||
|
|
||||||
|
private readonly apiStore = new ApiStore();
|
||||||
|
private readonly sessionStore = new SessionStore();
|
||||||
|
|
||||||
|
async login(): Promise<void> {
|
||||||
|
this.error = '';
|
||||||
|
try {
|
||||||
|
const res = await this.apiStore.authApi.login(LoginRequestVmV1.fromJson({
|
||||||
|
username: this.username,
|
||||||
|
password: this.password,
|
||||||
|
}));
|
||||||
|
if (res.success) {
|
||||||
|
this.sessionStore.setSession(res.user!, res.session!);
|
||||||
|
this.$emit('loggedIn');
|
||||||
|
} else {
|
||||||
|
this.error = 'Something went wrong (unknown reason).';
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
this.error = (err as ApiException<LoginResponseVmV1>).body.message ?? '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="width"></div>
|
||||||
|
<div v-if="error" class="alert alert-danger" role="alert">
|
||||||
|
{{ error }}
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
Username
|
||||||
|
<input type="text" class="form-control" v-model="username"/>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
Password
|
||||||
|
<input type="password" class="form-control" v-model="password" @keydown.enter="login"/>
|
||||||
|
</div>
|
||||||
|
<div class="float-end mb-3">
|
||||||
|
<button class="btn btn-primary" @click="login">{{ $t('auth.login') }}</button>
|
||||||
|
</div>
|
||||||
|
<div class="clearfix"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import 'bootstrap/scss/functions';
|
||||||
|
@import 'bootstrap/scss/variables';
|
||||||
|
@import 'bootstrap/scss/mixins/breakpoints';
|
||||||
|
|
||||||
|
@include media-breakpoint-down(sm) {
|
||||||
|
.width {
|
||||||
|
width: calc(100vw - 3em);
|
||||||
|
transition: width .125s ease-in-out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include media-breakpoint-up(sm) {
|
||||||
|
.width {
|
||||||
|
width: 18em;
|
||||||
|
transition: width .125s ease-in-out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,31 +1,21 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {Options, Vue} from 'vue-class-component';
|
import {Options, Vue} from 'vue-class-component';
|
||||||
import type {AiInstance} from "@/data/models/AiInstance";
|
import AiInstanceComponent from '@/components/dashboard/AiInstanceComponent.vue';
|
||||||
import AiInstanceApi from "@/data/api/AiInstanceApi";
|
import Alert from '@/components/Bootstrap/Alert.vue';
|
||||||
import AiInstanceComponent from "@/components/dashboard/AiInstanceComponent.vue";
|
import {AiInstanceVmV1} from 'ai-oas';
|
||||||
|
import {AiInstanceStore} from '@/stores/AiInstanceStore';
|
||||||
|
import {storeToRefs} from 'pinia';
|
||||||
|
|
||||||
@Options({
|
@Options({
|
||||||
name: 'AiInstanceTabs',
|
name: 'AiInstanceTabs',
|
||||||
components: {AiInstanceComponent},
|
components: {Alert, AiInstanceComponent},
|
||||||
})
|
})
|
||||||
export default class AiInstanceTabs extends Vue {
|
export default class AiInstanceTabs extends Vue {
|
||||||
readonly aiInstances: AiInstance[] = [];
|
readonly aiInstanceStore = new AiInstanceStore();
|
||||||
readonly aiInstanceApi = new AiInstanceApi();
|
selectedAiInstance: AiInstanceVmV1 | null = null;
|
||||||
selectedAiInstance: AiInstance | null = null;
|
|
||||||
|
|
||||||
mounted(): void {
|
mounted(): void {
|
||||||
this.reload();
|
this.aiInstanceStore.loadIfAbsent();
|
||||||
}
|
|
||||||
|
|
||||||
async reload(): Promise<void> {
|
|
||||||
const list = await this.aiInstanceApi.fetchAiInstances();
|
|
||||||
this.aiInstances.splice(0);
|
|
||||||
this.aiInstances.push(...list);
|
|
||||||
if (list.length > 0) {
|
|
||||||
this.selectedAiInstance = list[0];
|
|
||||||
} else {
|
|
||||||
this.selectedAiInstance = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -34,19 +24,19 @@ export default class AiInstanceTabs extends Vue {
|
|||||||
<div class="d-flex flex-column">
|
<div class="d-flex flex-column">
|
||||||
<!-- tabs -->
|
<!-- tabs -->
|
||||||
<div class="d-flex flex-row">
|
<div class="d-flex flex-row">
|
||||||
<div class="flex-grow-1">
|
<div class="flex-grow-1 me-2">
|
||||||
<!-- no ais warning -->
|
<!-- no ais warning -->
|
||||||
<div v-if="aiInstances.length == 0" class="mt-2 alert alert-warning" role="alert">
|
<Alert v-if="aiInstanceStore.aiInstances.length == 0" type="alert-warning" class="my-0">
|
||||||
No AIs defined!
|
No AIs defined!
|
||||||
</div>
|
</Alert>
|
||||||
|
|
||||||
<!-- actual tabs -->
|
<!-- actual tabs -->
|
||||||
<div class="btn-group">
|
<div v-else class="btn-group">
|
||||||
<button v-for="aiInstance in aiInstances"
|
<button v-for="aiInstance in aiInstanceStore.aiInstances"
|
||||||
:key="aiInstance.configuration.name"
|
:key="aiInstance.configuration.name"
|
||||||
class="btn" :class="{
|
class="btn" :class="{
|
||||||
'btn-secondary': aiInstance === selectedAiInstance,
|
'btn-secondary': aiInstance === selectedAiInstance,
|
||||||
'btn-secondary-outline': aiInstance !== selectedAiInstance
|
'btn-outline-secondary': aiInstance !== selectedAiInstance
|
||||||
}"
|
}"
|
||||||
@click="selectedAiInstance = aiInstance">
|
@click="selectedAiInstance = aiInstance">
|
||||||
{{ aiInstance.configuration.name }}
|
{{ aiInstance.configuration.name }}
|
||||||
@@ -55,7 +45,7 @@ export default class AiInstanceTabs extends Vue {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- reload -->
|
<!-- reload -->
|
||||||
<button class="btn btn-secondary" @click="reload()">
|
<button class="btn btn-secondary" @click="aiInstanceStore.reload()">
|
||||||
<i class="fa fa-refresh"/>
|
<i class="fa fa-refresh"/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,34 +1,34 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {Options, Vue} from 'vue-class-component';
|
import {Options, Vue} from 'vue-class-component';
|
||||||
import {Prop} from "vue-property-decorator";
|
import {Prop} from 'vue-property-decorator';
|
||||||
import AiInstanceApi from "@/data/api/AiInstanceApi";
|
import {toast} from 'vue3-toastify';
|
||||||
import type {AiInstance} from "@/data/models/AiInstance";
|
import {BSpinner} from 'bootstrap-vue-next';
|
||||||
import {toast} from "vue3-toastify";
|
import {AiInstanceVmV1, ChatMessageVmV1} from 'ai-oas';
|
||||||
import type {ChatMessage} from "@/data/models/chat/ChatMessage";
|
import {ApiStore} from '@/stores/ApiStore';
|
||||||
import {BSpinner} from "bootstrap-vue-next";
|
import Spinner from '@/components/Spinner.vue';
|
||||||
|
|
||||||
@Options({
|
@Options({
|
||||||
name: 'Chat',
|
name: 'Chat',
|
||||||
components: {BSpinner},
|
components: {
|
||||||
|
Spinner,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
export default class Chat extends Vue {
|
export default class Chat extends Vue {
|
||||||
@Prop({required: true})
|
@Prop({required: true})
|
||||||
readonly aiInstance!: AiInstance;
|
readonly aiInstance!: AiInstanceVmV1;
|
||||||
readonly aiInstanceApi = new AiInstanceApi();
|
readonly apiStore = new ApiStore();
|
||||||
user: string = 'alice';
|
user: string = 'alice';
|
||||||
text: string = '';
|
text: string = '';
|
||||||
waiting: boolean = false;
|
waiting: boolean = false;
|
||||||
message: ChatMessage | null = null;
|
message: ChatMessageVmV1 | null = null;
|
||||||
|
|
||||||
async send(): Promise<void> {
|
async send(): Promise<void> {
|
||||||
this.waiting = true;
|
this.waiting = true;
|
||||||
try {
|
try {
|
||||||
this.message = {'role': 'user', 'name': this.user, 'content': this.text};
|
this.message = ChatMessageVmV1.fromJson({'role': 'user', 'name': this.user, 'content': this.text});
|
||||||
this.text = '';
|
this.text = '';
|
||||||
const response = await this.aiInstanceApi.chatText(this.aiInstance.configuration.name, this.message);
|
await this.apiStore.aiApi.chatText(this.aiInstance.configuration.id, this.message);
|
||||||
debugger;
|
|
||||||
this.aiInstance.messages.push(this.message);
|
this.aiInstance.messages.push(this.message);
|
||||||
this.aiInstance.messages.push(response);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error('Error while chatting: ' + JSON.stringify(e));
|
toast.error('Error while chatting: ' + JSON.stringify(e));
|
||||||
} finally {
|
} finally {
|
||||||
@@ -65,7 +65,7 @@ export default class Chat extends Vue {
|
|||||||
</div>
|
</div>
|
||||||
<div v-if="waiting">
|
<div v-if="waiting">
|
||||||
<b>{{ message!.name }}</b>{{ ': ' + message!.content }}
|
<b>{{ message!.name }}</b>{{ ': ' + message!.content }}
|
||||||
<Spinner/>
|
<Spinner style="text-secondary" :size="1"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {Options, Vue} from 'vue-class-component';
|
import {Options, Vue} from 'vue-class-component';
|
||||||
import {Prop} from "vue-property-decorator";
|
import {Prop} from "vue-property-decorator";
|
||||||
import type {AiInstance} from "@/data/models/AiInstance";
|
|
||||||
import {getCurrentInstance} from "vue";
|
import {getCurrentInstance} from "vue";
|
||||||
import {toast} from "vue3-toastify";
|
import {toast} from "vue3-toastify";
|
||||||
import AiInstanceApi from "@/data/api/AiInstanceApi";
|
import {AiInstanceVmV1} from 'ai-oas';
|
||||||
|
import {ApiStore} from '@/stores/ApiStore';
|
||||||
|
|
||||||
@Options({
|
@Options({
|
||||||
name: 'Discord',
|
name: 'Discord',
|
||||||
@@ -13,8 +13,8 @@ import AiInstanceApi from "@/data/api/AiInstanceApi";
|
|||||||
})
|
})
|
||||||
export default class Discord extends Vue {
|
export default class Discord extends Vue {
|
||||||
@Prop({required: true})
|
@Prop({required: true})
|
||||||
readonly aiInstance!: AiInstance;
|
readonly aiInstance!: AiInstanceVmV1;
|
||||||
readonly aiInstanceApi = new AiInstanceApi();
|
readonly apiStore = new ApiStore();
|
||||||
|
|
||||||
get online(): boolean {
|
get online(): boolean {
|
||||||
return this.aiInstance.discord.online;
|
return this.aiInstance.discord.online;
|
||||||
@@ -23,22 +23,22 @@ export default class Discord extends Vue {
|
|||||||
set online(val: boolean) {
|
set online(val: boolean) {
|
||||||
const oldVal = this.online;
|
const oldVal = this.online;
|
||||||
this.aiInstance.discord.online = val;
|
this.aiInstance.discord.online = val;
|
||||||
this.aiInstanceApi.discordOnline(this.aiInstance.configuration.name, val).catch(e => {
|
this.apiStore.aiApi.discordOnline(this.aiInstance.configuration.id, val).catch(e => {
|
||||||
toast.error('Error while set online status: ' + JSON.stringify(e));
|
toast.error('Error while set online status: ' + JSON.stringify(e));
|
||||||
this.aiInstance.discord.online = oldVal;
|
this.aiInstance.discord.online = oldVal;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
get react_to_chat(): boolean {
|
get reactToChat(): boolean {
|
||||||
return this.aiInstance.discord.react_to_chat;
|
return this.aiInstance.discord.reactToChat;
|
||||||
}
|
}
|
||||||
|
|
||||||
set react_to_chat(val: boolean) {
|
set reactToChat(val: boolean) {
|
||||||
const oldVal = this.online;
|
const oldVal = this.online;
|
||||||
this.aiInstance.discord.react_to_chat = val;
|
this.aiInstance.discord.reactToChat = val;
|
||||||
this.aiInstanceApi.discordReactToChat(this.aiInstance.configuration.name, val).catch(e => {
|
this.apiStore.aiApi.discordReactToChat(this.aiInstance.configuration.name, val).catch(e => {
|
||||||
toast.error('Error while setting react_to_chat status: ' + JSON.stringify(e));
|
toast.error('Error while setting react_to_chat status: ' + JSON.stringify(e));
|
||||||
this.aiInstance.discord.react_to_chat = oldVal;
|
this.aiInstance.discord.reactToChat = oldVal;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ export default class Discord extends Vue {
|
|||||||
<label class="form-check-label" :for="uid + '_online'">Online</label>
|
<label class="form-check-label" :for="uid + '_online'">Online</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check form-switch">
|
<div class="form-check form-switch">
|
||||||
<input class="form-check-input" type="checkbox" :id="uid + '_reactToChat'" v-model="react_to_chat">
|
<input class="form-check-input" type="checkbox" :id="uid + '_reactToChat'" v-model="reactToChat">
|
||||||
<label class="form-check-label" :for="uid + '_reactToChat'">React to chat</label>
|
<label class="form-check-label" :for="uid + '_reactToChat'">React to chat</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
95
frontend/src/components/navbar/LoggedInComponent.vue
Normal file
95
frontend/src/components/navbar/LoggedInComponent.vue
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {Component, Vue} from 'vue-facing-decorator';
|
||||||
|
import {SessionStore} from '@/stores/SessionStore';
|
||||||
|
import {ApiStore} from '@/stores/ApiStore';
|
||||||
|
import ProfilePicture from '@/components/navbar/ProfilePicture.vue';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
name: 'LoggedInComponent',
|
||||||
|
components: {ProfilePicture},
|
||||||
|
emits: ['actionClicked'],
|
||||||
|
})
|
||||||
|
export default class LoggedInComponent extends Vue {
|
||||||
|
readonly apiStore: ApiStore = new ApiStore();
|
||||||
|
readonly sessionStore: SessionStore = new SessionStore();
|
||||||
|
|
||||||
|
async mounted(): Promise<void> {
|
||||||
|
await this.sessionStore.loadIfAbsent();
|
||||||
|
}
|
||||||
|
|
||||||
|
onGotoSettings(): void {
|
||||||
|
this.$router.push('/settings');
|
||||||
|
this.$emit('actionClicked');
|
||||||
|
}
|
||||||
|
|
||||||
|
onGotoProfile(): void {
|
||||||
|
this.$router.push('/profile');
|
||||||
|
this.$emit('actionClicked');
|
||||||
|
}
|
||||||
|
|
||||||
|
async onLogout(): Promise<void> {
|
||||||
|
const res = await this.apiStore.authApi.logout();
|
||||||
|
if (res.success) {
|
||||||
|
this.sessionStore.clear();
|
||||||
|
}
|
||||||
|
this.$router.push('/');
|
||||||
|
this.$emit('actionClicked');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="width"></div>
|
||||||
|
<li>
|
||||||
|
<div class="logged-in-header d-flex flex-row">
|
||||||
|
<div>
|
||||||
|
<ProfilePicture :size="64"/>
|
||||||
|
</div>
|
||||||
|
<div class="ms-3 d-flex flex-column justify-content-center">
|
||||||
|
<div>{{ sessionStore.user?.displayName ?? sessionStore.user?.name }}</div>
|
||||||
|
<div><small>{{ sessionStore.user?.email }}</small></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<div class="dropdown-divider"></div>
|
||||||
|
<li>
|
||||||
|
<a class="dropdown-item c-pointer" @click="onGotoSettings">
|
||||||
|
<i class="fa fa-cog"></i>
|
||||||
|
{{ $t('menu.settings') }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="dropdown-item c-pointer" @click="onGotoProfile">
|
||||||
|
<i class="fa fa-user"></i>
|
||||||
|
{{ $t('menu.profile') }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<div class="dropdown-divider"></div>
|
||||||
|
<li>
|
||||||
|
<a class="dropdown-item c-pointer" @click="onLogout">
|
||||||
|
<i class="fa fa-power-off"></i>
|
||||||
|
{{ $t('auth.logout') }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import 'bootstrap/scss/functions';
|
||||||
|
@import 'bootstrap/scss/variables';
|
||||||
|
@import 'bootstrap/scss/mixins/breakpoints';
|
||||||
|
|
||||||
|
.logged-in-header {
|
||||||
|
padding: var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x);
|
||||||
|
}
|
||||||
|
|
||||||
|
@include media-breakpoint-down(sm) {
|
||||||
|
.width {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include media-breakpoint-up(sm) {
|
||||||
|
.width {
|
||||||
|
width: 18em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -3,6 +3,7 @@ import {Options, Vue} from 'vue-class-component';
|
|||||||
import BootstrapThemeSwitch from '@/components/bootstrapThemeSwitch/BootstrapThemeSwitch.vue';
|
import BootstrapThemeSwitch from '@/components/bootstrapThemeSwitch/BootstrapThemeSwitch.vue';
|
||||||
import LocaleSelector from '@/components/locale/LocaleSelector.vue';
|
import LocaleSelector from '@/components/locale/LocaleSelector.vue';
|
||||||
import Logo from '@/components/Logo.vue';
|
import Logo from '@/components/Logo.vue';
|
||||||
|
import UserAccountDropdown from '@/components/navbar/UserAccountDropdown.vue';
|
||||||
|
|
||||||
@Options({
|
@Options({
|
||||||
name: 'NavBar',
|
name: 'NavBar',
|
||||||
@@ -10,6 +11,7 @@ import Logo from '@/components/Logo.vue';
|
|||||||
BootstrapThemeSwitch,
|
BootstrapThemeSwitch,
|
||||||
LocaleSelector,
|
LocaleSelector,
|
||||||
Logo,
|
Logo,
|
||||||
|
UserAccountDropdown,
|
||||||
},
|
},
|
||||||
emits: {
|
emits: {
|
||||||
'toggleSidebar': undefined,
|
'toggleSidebar': undefined,
|
||||||
@@ -45,6 +47,7 @@ export default class NavBar extends Vue {
|
|||||||
<div class="d-flex flex-row align-items-center">
|
<div class="d-flex flex-row align-items-center">
|
||||||
<LocaleSelector class="navbar-locale-select"/>
|
<LocaleSelector class="navbar-locale-select"/>
|
||||||
<BootstrapThemeSwitch class="navbar-theme-switch ms-2"/>
|
<BootstrapThemeSwitch class="navbar-theme-switch ms-2"/>
|
||||||
|
<UserAccountDropdown/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
74
frontend/src/components/navbar/ProfilePicture.vue
Normal file
74
frontend/src/components/navbar/ProfilePicture.vue
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {Component, Prop, Vue} from 'vue-facing-decorator';
|
||||||
|
import * as gravatar from 'gravatar';
|
||||||
|
import {SessionStore} from '@/stores/SessionStore';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
name: 'ProfilePicture',
|
||||||
|
})
|
||||||
|
export default class ProfilePicture extends Vue {
|
||||||
|
@Prop({default: 64})
|
||||||
|
readonly size!: number;
|
||||||
|
|
||||||
|
private readonly sessionStore = new SessionStore();
|
||||||
|
|
||||||
|
private useErrorProfilePictureUrl: boolean = false;
|
||||||
|
|
||||||
|
get profilePictureUrl(): string | undefined {
|
||||||
|
return (this.useErrorProfilePictureUrl ? this.errorProfilePictureUrl : undefined)
|
||||||
|
?? this.gravatarProfilePictureUrl
|
||||||
|
?? this.errorProfilePictureUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
private get gravatarProfilePictureUrl(): string | undefined {
|
||||||
|
const email = this.sessionStore.user?.email;
|
||||||
|
return email && gravatar.url(email, {protocol: 'https', s: '' + this.size});
|
||||||
|
}
|
||||||
|
|
||||||
|
private get errorProfilePictureUrl(): string {
|
||||||
|
//prepare canvas
|
||||||
|
let canvas = document.createElement('canvas');
|
||||||
|
canvas.height = this.size;
|
||||||
|
canvas.width = this.size;
|
||||||
|
let context = canvas.getContext('2d')!;
|
||||||
|
|
||||||
|
//prepare params
|
||||||
|
const fontSize = this.size / 2;
|
||||||
|
const char = (this.sessionStore.user?.displayName ?? '?').substring(0, 1);
|
||||||
|
|
||||||
|
//prepare colors
|
||||||
|
const r = (char.codePointAt(0)! * 1361) % 256;
|
||||||
|
const g = (char.codePointAt(0)! * 1823) % 256;
|
||||||
|
const b = (char.codePointAt(0)! * 2347) % 256;
|
||||||
|
const brightness = r * r + g * g + b * b;
|
||||||
|
const foregroundColor = brightness < 127 * 127 * 3 ? '#fff' : '#000';
|
||||||
|
const backgroundColor = `rgb(${r},${g},${b})`;
|
||||||
|
|
||||||
|
//background
|
||||||
|
context.fillStyle = backgroundColor;
|
||||||
|
context.fillRect(0, 0, this.size, this.size);
|
||||||
|
|
||||||
|
//text
|
||||||
|
context.fillStyle = foregroundColor;
|
||||||
|
context.font = `${fontSize}px sans-serif`;
|
||||||
|
context.textAlign = 'center';
|
||||||
|
context.textBaseline = 'middle';
|
||||||
|
context.fillText(char, this.size / 2 + .5, this.size / 2 + .5);
|
||||||
|
|
||||||
|
//fin
|
||||||
|
return canvas.toDataURL('png');
|
||||||
|
}
|
||||||
|
|
||||||
|
async mounted(): Promise<void> {
|
||||||
|
await this.sessionStore.loadIfAbsent();
|
||||||
|
}
|
||||||
|
|
||||||
|
onProfilePictureError(): void {
|
||||||
|
this.useErrorProfilePictureUrl = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<img :src="profilePictureUrl" alt="Profile picture" :width="size" :height="size" class="rounded-circle"
|
||||||
|
referrerpolicy="no-referrer" @error="onProfilePictureError">
|
||||||
|
</template>
|
||||||
50
frontend/src/components/navbar/UserAccountDropdown.vue
Normal file
50
frontend/src/components/navbar/UserAccountDropdown.vue
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import LoggedInComponent from '@/components/navbar/LoggedInComponent.vue';
|
||||||
|
import LoginComponent from '@/components/auth/LoginComponent.vue';
|
||||||
|
import {SessionStore} from '@/stores/SessionStore';
|
||||||
|
import {Component, Vue} from 'vue-facing-decorator';
|
||||||
|
import {getCurrentInstance} from 'vue';
|
||||||
|
import ProfilePicture from '@/components/navbar/ProfilePicture.vue';
|
||||||
|
import {Dropdown} from 'bootstrap';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
name: 'UserAccountDropdown',
|
||||||
|
methods: {getCurrentInstance},
|
||||||
|
components: {
|
||||||
|
LoggedInComponent,
|
||||||
|
LoginComponent,
|
||||||
|
ProfilePicture,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export default class UserAccountDropdown extends Vue {
|
||||||
|
readonly sessionStore = new SessionStore();
|
||||||
|
|
||||||
|
get uid(): number {
|
||||||
|
return getCurrentInstance()!.uid;
|
||||||
|
}
|
||||||
|
|
||||||
|
async mounted(): Promise<void> {
|
||||||
|
await this.sessionStore.loadIfAbsent();
|
||||||
|
}
|
||||||
|
|
||||||
|
closeDropDown(): void {
|
||||||
|
const el = this.$refs.dropdownTrigger as HTMLElement;
|
||||||
|
Dropdown.getOrCreateInstance(el)?.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="dropdown">
|
||||||
|
<div ref="dropdownTrigger" class="m-1 d-flex align-items-center c-pointer" :id="'userdropdown' + uid"
|
||||||
|
data-bs-toggle="dropdown" data-bs-auto-close="outside" aria-expanded="false">
|
||||||
|
<ProfilePicture size="32"/>
|
||||||
|
</div>
|
||||||
|
<ul class="dropdown-menu dropdown-menu-end text-small shadow"
|
||||||
|
:aria-labelledby="'userdropdown' + uid">
|
||||||
|
<li v-if="!sessionStore.session" style="margin: -.5em 0">
|
||||||
|
<LoginComponent class="m-3"/>
|
||||||
|
</li>
|
||||||
|
<LoggedInComponent v-if="sessionStore.user" @actionClicked="closeDropDown"/>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
6
frontend/src/composables/emitter.ts
Normal file
6
frontend/src/composables/emitter.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import mitt from 'mitt';
|
||||||
|
|
||||||
|
const emitter = mitt();
|
||||||
|
export default function getEmitter() {
|
||||||
|
return emitter;
|
||||||
|
}
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
import {handleJsonResponse} from '@/data/api/ApiUtils';
|
|
||||||
import type {AiInstance} from "@/data/models/AiInstance";
|
|
||||||
import type {ChatMessage} from "@/data/models/chat/ChatMessage";
|
|
||||||
|
|
||||||
export default class AiInstanceApi {
|
|
||||||
readonly baseurl = 'http://localhost:8877/'
|
|
||||||
|
|
||||||
async fetchAiInstances(): Promise<AiInstance[]> {
|
|
||||||
const res = fetch(this.baseurl + 'api/v1/ai',
|
|
||||||
{
|
|
||||||
headers: {'Accept': 'application/json'}
|
|
||||||
});
|
|
||||||
return await handleJsonResponse(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
async chatText(aiName: string, message: ChatMessage): Promise<ChatMessage> {
|
|
||||||
const res = fetch(this.baseurl + 'api/v1/ai/' + aiName + '/chat/text',
|
|
||||||
{
|
|
||||||
method: 'POST',
|
|
||||||
headers: {'Accept': 'application/json', 'Content-Type': 'application/json'},
|
|
||||||
body: JSON.stringify(message),
|
|
||||||
});
|
|
||||||
return await handleJsonResponse(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
async discordOnline(aiName: string, online: boolean): Promise<void> {
|
|
||||||
const res = fetch(this.baseurl + 'api/v1/ai/' + aiName + '/discord',
|
|
||||||
{
|
|
||||||
method: 'PATCH',
|
|
||||||
headers: {'Accept': 'application/json', 'Content-Type': 'application/json'},
|
|
||||||
body: JSON.stringify({online: online}),
|
|
||||||
});
|
|
||||||
await handleJsonResponse(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
async discordReactToChat(aiName: string, react_to_chat: boolean): Promise<void> {
|
|
||||||
const res = fetch(this.baseurl + 'api/v1/ai/' + aiName + '/discord',
|
|
||||||
{
|
|
||||||
method: 'PATCH',
|
|
||||||
headers: {'Accept': 'application/json', 'Content-Type': 'application/json'},
|
|
||||||
body: JSON.stringify({react_to_chat: react_to_chat}),
|
|
||||||
});
|
|
||||||
await handleJsonResponse(res);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
export class ApiError extends Error {
|
|
||||||
readonly statusCode: number;
|
|
||||||
|
|
||||||
constructor(statusCode: number, message?: string) {
|
|
||||||
super(message);
|
|
||||||
this.statusCode = statusCode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function handleJsonResponse<T>(res: Promise<Response>): Promise<T> {
|
|
||||||
return res.then(async e => {
|
|
||||||
if (e.status === 200) {
|
|
||||||
const text = await e.text();
|
|
||||||
if (!text.length) {
|
|
||||||
return undefined;
|
|
||||||
} else {
|
|
||||||
return JSON.parse(text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new ApiError(e.status, 'Api error ' + e.status + ': ' + await e.text());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
export type AiConfiguration = {
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import type {AiConfiguration} from "./AiConfiguration";
|
|
||||||
import type {ChatMessage} from "@/data/models/chat/ChatMessage";
|
|
||||||
|
|
||||||
export type AiInstance = {
|
|
||||||
configuration: AiConfiguration;
|
|
||||||
discord: {
|
|
||||||
online: boolean;
|
|
||||||
react_to_chat: boolean;
|
|
||||||
}
|
|
||||||
messages: ChatMessage[];
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export type ChatMessage = { 'role': string, 'name': string, 'content': string }
|
|
||||||
@@ -9,6 +9,16 @@ const router = createRouter({
|
|||||||
name: 'home',
|
name: 'home',
|
||||||
component: HomeView,
|
component: HomeView,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/register',
|
||||||
|
name: 'register',
|
||||||
|
component: () => import('../views/auth/RegisterView.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/login',
|
||||||
|
name: 'login',
|
||||||
|
component: () => import('../views/auth/LoginView.vue'),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/about',
|
path: '/about',
|
||||||
name: 'about',
|
name: 'about',
|
||||||
|
|||||||
82
frontend/src/stores/AiIConfigurationStore.ts
Normal file
82
frontend/src/stores/AiIConfigurationStore.ts
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
import {Pinia, Store} from 'pinia-class-component';
|
||||||
|
import {ApiStore} from '@/stores/ApiStore';
|
||||||
|
import {AiConfigurationVmV1} from 'ai-oas';
|
||||||
|
|
||||||
|
@Store({
|
||||||
|
id: 'AiConfigurationStore',
|
||||||
|
name: 'AiConfigurationStore',
|
||||||
|
})
|
||||||
|
export class AiConfigurationStore extends Pinia {
|
||||||
|
private get apiStore() {
|
||||||
|
return new ApiStore();
|
||||||
|
}
|
||||||
|
|
||||||
|
//data
|
||||||
|
private _loadingPromise: Promise<AiConfigurationVmV1[]> | null = null;
|
||||||
|
private _aiConfigurations: AiConfigurationVmV1[] = [];
|
||||||
|
|
||||||
|
//getter
|
||||||
|
get loading(): boolean {
|
||||||
|
return this._loadingPromise !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
get aiConfigurations(): AiConfigurationVmV1[] {
|
||||||
|
return this._aiConfigurations;
|
||||||
|
}
|
||||||
|
|
||||||
|
//actions
|
||||||
|
clear(): void {
|
||||||
|
this._aiConfigurations.splice(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
setAis(ais: AiConfigurationVmV1[]): void {
|
||||||
|
this._aiConfigurations.splice(0);
|
||||||
|
this._aiConfigurations.push(...ais);
|
||||||
|
}
|
||||||
|
|
||||||
|
addAiConfiguration(ai: AiConfigurationVmV1): void {
|
||||||
|
this._aiConfigurations.push(ai);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateInstanceAi(ai: AiConfigurationVmV1): void {
|
||||||
|
const index = this._aiConfigurations.findIndex(e => e.id == ai.id);
|
||||||
|
if (index >= 0) {
|
||||||
|
this._aiConfigurations.splice(index, 1, ai);
|
||||||
|
} else {
|
||||||
|
this._aiConfigurations.push(ai);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
forgetInstanceAi(ai: AiConfigurationVmV1): void {
|
||||||
|
const index = this._aiConfigurations.findIndex(e => e.id == ai.id);
|
||||||
|
if (index >= 0) {
|
||||||
|
this._aiConfigurations.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async reload(force: boolean = false): Promise<void> {
|
||||||
|
if (this._loadingPromise) {
|
||||||
|
await this._loadingPromise;
|
||||||
|
if (!force) {// when force is true, wait for the current operation to complete, then reload
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this._loadingPromise = this.apiStore.aiConfigurationApi.list();
|
||||||
|
const ais = await this._loadingPromise;
|
||||||
|
this.setAis(ais);
|
||||||
|
} catch (err) {
|
||||||
|
this.clear();
|
||||||
|
} finally {
|
||||||
|
this._loadingPromise = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadIfAbsent(): Promise<void> {
|
||||||
|
if (this._aiConfigurations.length > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return await this.reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
82
frontend/src/stores/AiInstanceStore.ts
Normal file
82
frontend/src/stores/AiInstanceStore.ts
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
import {Pinia, Store} from 'pinia-class-component';
|
||||||
|
import {ApiStore} from '@/stores/ApiStore';
|
||||||
|
import {AiInstanceVmV1} from 'ai-oas';
|
||||||
|
|
||||||
|
@Store({
|
||||||
|
id: 'AiInstanceStore',
|
||||||
|
name: 'AiInstanceStore',
|
||||||
|
})
|
||||||
|
export class AiInstanceStore extends Pinia {
|
||||||
|
private get apiStore() {
|
||||||
|
return new ApiStore();
|
||||||
|
}
|
||||||
|
|
||||||
|
//data
|
||||||
|
private _loadingPromise: Promise<AiInstanceVmV1[]> | null = null;
|
||||||
|
private _aiInstances: AiInstanceVmV1[] = [];
|
||||||
|
|
||||||
|
//getter
|
||||||
|
get loading(): boolean {
|
||||||
|
return this._loadingPromise !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
get aiInstances(): AiInstanceVmV1[] {
|
||||||
|
return this._aiInstances;
|
||||||
|
}
|
||||||
|
|
||||||
|
//actions
|
||||||
|
clear(): void {
|
||||||
|
this._aiInstances.splice(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
setAis(ais: AiInstanceVmV1[]): void {
|
||||||
|
this._aiInstances.splice(0);
|
||||||
|
this._aiInstances.push(...ais);
|
||||||
|
}
|
||||||
|
|
||||||
|
addAiInstance(ai: AiInstanceVmV1): void {
|
||||||
|
this._aiInstances.push(ai);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateInstanceAi(ai: AiInstanceVmV1): void {
|
||||||
|
const index = this._aiInstances.findIndex(e => e.configuration.id == ai.configuration.id);
|
||||||
|
if (index >= 0) {
|
||||||
|
this._aiInstances.splice(index, 1, ai);
|
||||||
|
} else {
|
||||||
|
this._aiInstances.push(ai);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
forgetInstanceAi(ai: AiInstanceVmV1): void {
|
||||||
|
const index = this._aiInstances.findIndex(e => e.configuration.id == ai.configuration.id);
|
||||||
|
if (index >= 0) {
|
||||||
|
this._aiInstances.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async reload(force: boolean = false): Promise<void> {
|
||||||
|
if (this._loadingPromise) {
|
||||||
|
await this._loadingPromise;
|
||||||
|
if (!force) {// when force is true, wait for the current operation to complete, then reload
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this._loadingPromise = this.apiStore.aiApi.list();
|
||||||
|
const ais = await this._loadingPromise;
|
||||||
|
this.setAis(ais);
|
||||||
|
} catch (err) {
|
||||||
|
this.clear();
|
||||||
|
} finally {
|
||||||
|
this._loadingPromise = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadIfAbsent(): Promise<void> {
|
||||||
|
if (this._aiInstances.length > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return await this.reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
38
frontend/src/stores/ApiStore.ts
Normal file
38
frontend/src/stores/ApiStore.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import {Pinia, Store} from 'pinia-class-component';
|
||||||
|
import {AiConfigurationApi, AiApi, AuthApi, createConfiguration, HttpMethod, RequestContext, UsersApi} from 'ai-oas';
|
||||||
|
|
||||||
|
@Store({
|
||||||
|
id: 'ApiStore',
|
||||||
|
name: 'ApiStore',
|
||||||
|
})
|
||||||
|
export class ApiStore extends Pinia {
|
||||||
|
private readonly _config = createConfiguration({
|
||||||
|
baseServer: {
|
||||||
|
makeRequestContext(endpoint: string, httpMethod: HttpMethod): RequestContext {
|
||||||
|
return new RequestContext(endpoint, httpMethod);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
//api
|
||||||
|
private readonly _authApi = new AuthApi(this._config);
|
||||||
|
private readonly _aiApi = new AiApi(this._config);
|
||||||
|
private readonly _aiConfigurationApi = new AiConfigurationApi(this._config);
|
||||||
|
private readonly _userApi = new UsersApi(this._config);
|
||||||
|
|
||||||
|
get authApi(): AuthApi {
|
||||||
|
return this._authApi;
|
||||||
|
}
|
||||||
|
|
||||||
|
get aiApi(): AiApi {
|
||||||
|
return this._aiApi;
|
||||||
|
}
|
||||||
|
|
||||||
|
get aiConfigurationApi(): AiConfigurationApi {
|
||||||
|
return this._aiConfigurationApi;
|
||||||
|
}
|
||||||
|
|
||||||
|
get userApi(): UsersApi {
|
||||||
|
return this._userApi;
|
||||||
|
}
|
||||||
|
}
|
||||||
84
frontend/src/stores/SessionStore.ts
Normal file
84
frontend/src/stores/SessionStore.ts
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import {Pinia, Store} from 'pinia-class-component';
|
||||||
|
import {type UserInfoVmV1, type UserSessionInfoVmV1, VerifyResponseVmV1} from 'ai-oas';
|
||||||
|
import {ApiStore} from '@/stores/ApiStore';
|
||||||
|
import useEmitter from '@/composables/emitter';
|
||||||
|
|
||||||
|
@Store({
|
||||||
|
id: 'SessionStore',
|
||||||
|
name: 'SessionStore',
|
||||||
|
})
|
||||||
|
export class SessionStore extends Pinia {
|
||||||
|
private get apiStore() {
|
||||||
|
return new ApiStore();
|
||||||
|
}
|
||||||
|
|
||||||
|
//data
|
||||||
|
private _loadedOnce: boolean = false;
|
||||||
|
private _loadingPromise: Promise<VerifyResponseVmV1> | null = null;
|
||||||
|
private _user: UserInfoVmV1 | null = null;
|
||||||
|
private _session: UserSessionInfoVmV1 | null = null;
|
||||||
|
|
||||||
|
//getter
|
||||||
|
get loading(): boolean {
|
||||||
|
return this._loadingPromise !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isLoggedIn(): boolean {
|
||||||
|
return !!this.user;
|
||||||
|
}
|
||||||
|
|
||||||
|
get user(): UserInfoVmV1 | null {
|
||||||
|
return this._user;
|
||||||
|
}
|
||||||
|
|
||||||
|
get session(): UserSessionInfoVmV1 | null {
|
||||||
|
return this._session;
|
||||||
|
}
|
||||||
|
|
||||||
|
//actions
|
||||||
|
clear() {
|
||||||
|
this._user = null;
|
||||||
|
this._session = null;
|
||||||
|
this.triggerEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
setSession(user: UserInfoVmV1, session: UserSessionInfoVmV1) {
|
||||||
|
this._user = user;
|
||||||
|
this._session = session;
|
||||||
|
this.triggerEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
async reload(force: boolean = false) {
|
||||||
|
if (this._loadingPromise) {
|
||||||
|
await this._loadingPromise;
|
||||||
|
if (!force) {// when force is true, wait for the current operation to complete, then reload
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this._loadingPromise = this.apiStore.authApi.verify();
|
||||||
|
const res = await this._loadingPromise;
|
||||||
|
this._user = res.user ?? null;
|
||||||
|
this._session = res.session ?? null;
|
||||||
|
this.triggerEvent();
|
||||||
|
} catch (err) {
|
||||||
|
this.clear();
|
||||||
|
} finally {
|
||||||
|
this._loadingPromise = null;
|
||||||
|
this._loadedOnce = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadIfAbsent(): Promise<void> {
|
||||||
|
if (this._loadedOnce) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return await this.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
private triggerEvent(): void {
|
||||||
|
useEmitter().emit('authChanged');
|
||||||
|
}
|
||||||
|
}
|
||||||
6
frontend/src/stores/events.ts
Normal file
6
frontend/src/stores/events.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import useEmitter from '@/composables/emitter';
|
||||||
|
import {AiInstanceStore} from '@/stores/AiInstanceStore';
|
||||||
|
import {AiConfigurationStore} from '@/stores/AiIConfigurationStore';
|
||||||
|
|
||||||
|
useEmitter().on('authChanged', () => new AiInstanceStore().reload(true));
|
||||||
|
useEmitter().on('authChanged', () => new AiConfigurationStore().reload(true));
|
||||||
28
frontend/src/views/auth/LoginView.vue
Normal file
28
frontend/src/views/auth/LoginView.vue
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {Component, Vue} from 'vue-facing-decorator';
|
||||||
|
import LoginComponent from '@/components/auth/LoginComponent.vue';
|
||||||
|
import CenterOnParent from '@/components/CenterOnParent.vue';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
name: 'LoginView',
|
||||||
|
components: {
|
||||||
|
CenterOnParent,
|
||||||
|
LoginComponent
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export default class LoginView extends Vue {
|
||||||
|
async onLoggedIn(): Promise<void> {
|
||||||
|
this.$router.push('/');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<center-on-parent>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<LoginComponent @loggedIn="onLoggedIn"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</center-on-parent>
|
||||||
|
</template>
|
||||||
63
frontend/src/views/auth/RegisterView.vue
Normal file
63
frontend/src/views/auth/RegisterView.vue
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {ApiException, RegisterRequestVmV1, RegisterResponseVmV1} from 'ai-oas';
|
||||||
|
import {Component, Vue} from 'vue-facing-decorator';
|
||||||
|
import {ApiStore} from '@/stores/ApiStore';
|
||||||
|
import {SessionStore} from '@/stores/SessionStore';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
name: 'RegisterView',
|
||||||
|
components: {},
|
||||||
|
})
|
||||||
|
export default class RegisterView extends Vue {
|
||||||
|
username: string = '';
|
||||||
|
password: string = '';
|
||||||
|
|
||||||
|
error: string = '';
|
||||||
|
|
||||||
|
private readonly apiStore = new ApiStore();
|
||||||
|
private readonly sessionStore = new SessionStore();
|
||||||
|
|
||||||
|
async register(): Promise<void> {
|
||||||
|
this.error = '';
|
||||||
|
try {
|
||||||
|
const res = await this.apiStore.authApi.register(RegisterRequestVmV1.fromJson({ username: this.username, password: this.password }));
|
||||||
|
if (res.success) {
|
||||||
|
this.sessionStore.setSession(res.user!, res.session!);
|
||||||
|
this.$router.push('/');
|
||||||
|
} else {
|
||||||
|
this.error = 'Something went wrong (unknown reason).';//TODO translate
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
this.error = (err as ApiException<RegisterResponseVmV1>).body.message ?? '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="w-100 h-100 d-flex justify-content-center">
|
||||||
|
<div class="align-self-center">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<div v-if="error" class="alert alert-danger" role="alert">
|
||||||
|
{{ error }}
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
Username
|
||||||
|
<input type="text" class="form-control" v-model="username" />
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
Password
|
||||||
|
<input type="password" class="form-control" v-model="password" />
|
||||||
|
</div>
|
||||||
|
<div class="float-right">
|
||||||
|
<button class="btn btn-primary" @click="register">{{ $t('auth.register') }}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
</style>
|
||||||
@@ -4,15 +4,20 @@
|
|||||||
"exclude": ["src/**/__tests__/*"],
|
"exclude": ["src/**/__tests__/*"],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"composite": true,
|
"moduleResolution": "Node",
|
||||||
"baseUrl": ".",
|
|
||||||
"paths": {
|
|
||||||
"@/*": ["./src/*"]
|
|
||||||
},
|
|
||||||
"lib": [
|
"lib": [
|
||||||
"ES2021",
|
"ESNext",
|
||||||
"DOM",
|
"DOM",
|
||||||
"DOM.Iterable"
|
"DOM.Iterable"
|
||||||
]
|
],
|
||||||
}
|
"module": "ESNext",
|
||||||
|
|
||||||
|
"composite": true,
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
|
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"],
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,5 +10,9 @@
|
|||||||
{
|
{
|
||||||
"path": "./tsconfig.vitest.json"
|
"path": "./tsconfig.vitest.json"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"compilerOptions": {
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"module": "NodeNext"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"extends": "@tsconfig/node18/tsconfig.json",
|
"extends": "@tsconfig/node20/tsconfig.json",
|
||||||
"include": [
|
"include": [
|
||||||
"vite.config.*",
|
"vite.config.*",
|
||||||
"vitest.config.*",
|
"vitest.config.*",
|
||||||
@@ -9,7 +9,11 @@
|
|||||||
],
|
],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
|
|
||||||
"composite": true,
|
"composite": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||||
|
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"moduleResolution": "Bundler",
|
"moduleResolution": "Bundler",
|
||||||
"types": ["node"]
|
"types": ["node"]
|
||||||
|
|||||||
@@ -2,6 +2,10 @@
|
|||||||
"extends": "./tsconfig.app.json",
|
"extends": "./tsconfig.app.json",
|
||||||
"exclude": [],
|
"exclude": [],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.vitest.tsbuildinfo",
|
||||||
|
|
||||||
|
"lib": [],
|
||||||
"types": ["node", "jsdom"]
|
"types": ["node", "jsdom"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,11 @@ import {fileURLToPath, URL} from 'node:url';
|
|||||||
import {defineConfig} from 'vite';
|
import {defineConfig} from 'vite';
|
||||||
import vue from '@vitejs/plugin-vue';
|
import vue from '@vitejs/plugin-vue';
|
||||||
import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite';
|
import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite';
|
||||||
|
import {ManualChunkMeta} from 'rollup';
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [vue(), VueI18nPlugin({compositionOnly: false})],
|
||||||
vue(), VueI18nPlugin({compositionOnly: false}),
|
|
||||||
],
|
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||||
@@ -17,9 +16,25 @@ export default defineConfig({
|
|||||||
build: {
|
build: {
|
||||||
manifest: true,
|
manifest: true,
|
||||||
emptyOutDir: false,
|
emptyOutDir: false,
|
||||||
|
chunkSizeWarningLimit: 10_000_000,
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
input: ['./src/main.ts', './index.html'],
|
input: ['./src/main.ts', './index.html'],
|
||||||
|
output: {
|
||||||
|
manualChunks: (id: string, meta: ManualChunkMeta) => {
|
||||||
|
if (id.includes('node_modules')) {
|
||||||
|
return 'vendor';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
clearScreen: false,
|
clearScreen: false,
|
||||||
|
server: {
|
||||||
|
proxy: {//for dev
|
||||||
|
'^/api/.*$': {
|
||||||
|
target: 'http://localhost:5172',
|
||||||
|
changeOrigin: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
1
templates/.gitignore.mustache
Normal file
1
templates/.gitignore.mustache
Normal file
@@ -0,0 +1 @@
|
|||||||
|
dist
|
||||||
30
templates/README.mustache
Normal file
30
templates/README.mustache
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
## {{npmName}}@{{npmVersion}}
|
||||||
|
|
||||||
|
This generator creates TypeScript/JavaScript client that utilizes {{framework}}.
|
||||||
|
|
||||||
|
### Building
|
||||||
|
|
||||||
|
To build and compile the typescript sources to javascript use:
|
||||||
|
```
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Publishing
|
||||||
|
|
||||||
|
First build the package then run ```npm publish```
|
||||||
|
|
||||||
|
### Consuming
|
||||||
|
|
||||||
|
navigate to the folder of your consuming project and run one of the following commands.
|
||||||
|
|
||||||
|
_published:_
|
||||||
|
|
||||||
|
```
|
||||||
|
npm install {{npmName}}@{{npmVersion}} --save
|
||||||
|
```
|
||||||
|
|
||||||
|
_unPublished (not recommended):_
|
||||||
|
|
||||||
|
```
|
||||||
|
npm install PATH_TO_GENERATED_PACKAGE --save
|
||||||
249
templates/api/api.mustache
Normal file
249
templates/api/api.mustache
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
// TODO: better import syntax?
|
||||||
|
import {BaseAPIRequestFactory, RequiredError, COLLECTION_FORMATS} from './baseapi{{extensionForDeno}}';
|
||||||
|
import {Configuration} from '../configuration{{extensionForDeno}}';
|
||||||
|
import {RequestContext, HttpMethod, ResponseContext, HttpFile} from '../http/http{{extensionForDeno}}';
|
||||||
|
{{#platforms}}
|
||||||
|
{{#node}}
|
||||||
|
import {{^supportsES6}}* as{{/supportsES6}} FormData from "form-data";
|
||||||
|
import { URLSearchParams } from 'url';
|
||||||
|
{{/node}}
|
||||||
|
{{/platforms}}
|
||||||
|
import {ObjectSerializer} from '../models/ObjectSerializer{{extensionForDeno}}';
|
||||||
|
import {ApiException} from './exception{{extensionForDeno}}';
|
||||||
|
import {canConsumeForm, isCodeInRange} from '../util{{extensionForDeno}}';
|
||||||
|
import {SecurityAuthentication} from '../auth/auth{{extensionForDeno}}';
|
||||||
|
|
||||||
|
{{#useInversify}}
|
||||||
|
import { injectable } from "inversify";
|
||||||
|
{{/useInversify}}
|
||||||
|
|
||||||
|
{{#imports}}
|
||||||
|
import { {{classname}} } from '{{filename}}{{extensionForDeno}}';
|
||||||
|
{{/imports}}
|
||||||
|
{{#operations}}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {{{description}}}{{^description}}no description{{/description}}
|
||||||
|
*/
|
||||||
|
{{#useInversify}}
|
||||||
|
@injectable()
|
||||||
|
{{/useInversify}}
|
||||||
|
export class {{classname}}RequestFactory extends BaseAPIRequestFactory {
|
||||||
|
|
||||||
|
{{#operation}}
|
||||||
|
/**
|
||||||
|
{{#notes}}
|
||||||
|
* {{¬es}}
|
||||||
|
{{/notes}}
|
||||||
|
{{#summary}}
|
||||||
|
* {{&summary}}
|
||||||
|
{{/summary}}
|
||||||
|
{{#allParams}}
|
||||||
|
* @param {{paramName}} {{description}}
|
||||||
|
{{/allParams}}
|
||||||
|
*/
|
||||||
|
public async {{nickname}}({{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{{dataType}}}, {{/allParams}}_options?: Configuration): Promise<RequestContext> {
|
||||||
|
let _config = _options || this.configuration;
|
||||||
|
{{#allParams}}
|
||||||
|
|
||||||
|
{{#required}}
|
||||||
|
// verify required parameter '{{paramName}}' is not null or undefined
|
||||||
|
if ({{paramName}} === null || {{paramName}} === undefined) {
|
||||||
|
throw new RequiredError("{{classname}}", "{{nickname}}", "{{paramName}}");
|
||||||
|
}
|
||||||
|
|
||||||
|
{{/required}}
|
||||||
|
{{/allParams}}
|
||||||
|
|
||||||
|
// Path Params
|
||||||
|
const localVarPath = '{{{path}}}'{{#pathParams}}
|
||||||
|
.replace('{' + '{{baseName}}' + '}', encodeURIComponent(String({{paramName}}))){{/pathParams}};
|
||||||
|
|
||||||
|
// Make Request Context
|
||||||
|
const requestContext = _config.baseServer.makeRequestContext(localVarPath, HttpMethod.{{httpMethod}});
|
||||||
|
requestContext.setHeaderParam("Accept", "application/json, */*;q=0.8")
|
||||||
|
{{#queryParams}}
|
||||||
|
|
||||||
|
// Query Params
|
||||||
|
if ({{paramName}} !== undefined) {
|
||||||
|
requestContext.setQueryParam("{{baseName}}", ObjectSerializer.serialize({{paramName}}, "{{{dataType}}}", "{{dataFormat}}"));
|
||||||
|
}
|
||||||
|
{{/queryParams}}
|
||||||
|
{{#headerParams}}
|
||||||
|
|
||||||
|
// Header Params
|
||||||
|
requestContext.setHeaderParam("{{baseName}}", ObjectSerializer.serialize({{paramName}}, "{{{dataType}}}", "{{dataFormat}}"));
|
||||||
|
{{/headerParams}}
|
||||||
|
{{#hasFormParams}}
|
||||||
|
|
||||||
|
// Form Params
|
||||||
|
const useForm = canConsumeForm([
|
||||||
|
{{#consumes}}
|
||||||
|
'{{{mediaType}}}',
|
||||||
|
{{/consumes}}
|
||||||
|
]);
|
||||||
|
|
||||||
|
let localVarFormParams
|
||||||
|
if (useForm) {
|
||||||
|
localVarFormParams = new FormData();
|
||||||
|
} else {
|
||||||
|
localVarFormParams = new URLSearchParams();
|
||||||
|
}
|
||||||
|
{{/hasFormParams}}
|
||||||
|
|
||||||
|
{{#formParams}}
|
||||||
|
{{#isArray}}
|
||||||
|
if ({{paramName}}) {
|
||||||
|
{{#isCollectionFormatMulti}}
|
||||||
|
{{paramName}}.forEach((element) => {
|
||||||
|
localVarFormParams.append('{{baseName}}', element as any);
|
||||||
|
})
|
||||||
|
{{/isCollectionFormatMulti}}
|
||||||
|
{{^isCollectionFormatMulti}}
|
||||||
|
// TODO: replace .append with .set
|
||||||
|
localVarFormParams.append('{{baseName}}', {{paramName}}.join(COLLECTION_FORMATS["{{collectionFormat}}"]));
|
||||||
|
{{/isCollectionFormatMulti}}
|
||||||
|
}
|
||||||
|
{{/isArray}}
|
||||||
|
{{^isArray}}
|
||||||
|
if ({{paramName}} !== undefined) {
|
||||||
|
// TODO: replace .append with .set
|
||||||
|
{{^isFile}}
|
||||||
|
localVarFormParams.append('{{baseName}}', {{paramName}} as any);
|
||||||
|
{{/isFile}}
|
||||||
|
{{#isFile}}
|
||||||
|
if (localVarFormParams instanceof FormData) {
|
||||||
|
{{#platforms}}
|
||||||
|
{{#node}}
|
||||||
|
localVarFormParams.append('{{baseName}}', {{paramName}}.data, {{paramName}}.name);
|
||||||
|
{{/node}}
|
||||||
|
{{^node}}
|
||||||
|
localVarFormParams.append('{{baseName}}', {{paramName}}, {{paramName}}.name);
|
||||||
|
{{/node}}
|
||||||
|
{{/platforms}}
|
||||||
|
}
|
||||||
|
{{/isFile}}
|
||||||
|
}
|
||||||
|
{{/isArray}}
|
||||||
|
{{/formParams}}
|
||||||
|
{{#hasFormParams}}
|
||||||
|
|
||||||
|
requestContext.setBody(localVarFormParams);
|
||||||
|
|
||||||
|
if(!useForm) {
|
||||||
|
const contentType = ObjectSerializer.getPreferredMediaType([{{#consumes}}
|
||||||
|
"{{{mediaType}}}"{{^-last}},{{/-last}}
|
||||||
|
{{/consumes}}]);
|
||||||
|
requestContext.setHeaderParam("Content-Type", contentType);
|
||||||
|
}
|
||||||
|
{{/hasFormParams}}
|
||||||
|
{{#bodyParam}}
|
||||||
|
|
||||||
|
// Body Params
|
||||||
|
const contentType = ObjectSerializer.getPreferredMediaType([{{#consumes}}
|
||||||
|
"{{{mediaType}}}"{{^-last}},{{/-last}}
|
||||||
|
{{/consumes}}]);
|
||||||
|
requestContext.setHeaderParam("Content-Type", contentType);
|
||||||
|
const serializedBody = ObjectSerializer.stringify(
|
||||||
|
ObjectSerializer.serialize({{paramName}}, "{{{dataType}}}", "{{dataFormat}}"),
|
||||||
|
contentType
|
||||||
|
);
|
||||||
|
requestContext.setBody(serializedBody);
|
||||||
|
{{/bodyParam}}
|
||||||
|
|
||||||
|
{{#hasAuthMethods}}
|
||||||
|
let authMethod: SecurityAuthentication | undefined;
|
||||||
|
{{/hasAuthMethods}}
|
||||||
|
{{#authMethods}}
|
||||||
|
// Apply auth methods
|
||||||
|
authMethod = _config.authMethods["{{name}}"]
|
||||||
|
if (authMethod?.applySecurityAuthentication) {
|
||||||
|
await authMethod?.applySecurityAuthentication(requestContext);
|
||||||
|
}
|
||||||
|
{{/authMethods}}
|
||||||
|
|
||||||
|
{{^useInversify}}
|
||||||
|
const defaultAuth: SecurityAuthentication | undefined = _options?.authMethods?.default || this.configuration?.authMethods?.default
|
||||||
|
if (defaultAuth?.applySecurityAuthentication) {
|
||||||
|
await defaultAuth?.applySecurityAuthentication(requestContext);
|
||||||
|
}
|
||||||
|
{{/useInversify}}
|
||||||
|
|
||||||
|
return requestContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
{{/operation}}
|
||||||
|
}
|
||||||
|
{{/operations}}
|
||||||
|
{{#operations}}
|
||||||
|
|
||||||
|
{{#useInversify}}
|
||||||
|
@injectable()
|
||||||
|
{{/useInversify}}
|
||||||
|
export class {{classname}}ResponseProcessor {
|
||||||
|
|
||||||
|
{{#operation}}
|
||||||
|
/**
|
||||||
|
* Unwraps the actual response sent by the server from the response context and deserializes the response content
|
||||||
|
* to the expected objects
|
||||||
|
*
|
||||||
|
* @params response Response returned by the server for a request to {{nickname}}
|
||||||
|
* @throws ApiException if the response code was not in [200, 299]
|
||||||
|
*/
|
||||||
|
public async {{nickname}}(response: ResponseContext): Promise<{{{returnType}}} {{^returnType}}void{{/returnType}}> {
|
||||||
|
const contentType = ObjectSerializer.normalizeMediaType(response.headers["content-type"]);
|
||||||
|
{{#responses}}
|
||||||
|
if (isCodeInRange("{{code}}", response.httpStatusCode)) {
|
||||||
|
{{#dataType}}
|
||||||
|
{{#isBinary}}
|
||||||
|
const body: {{{dataType}}} = await response.getBodyAsFile() as any as {{{returnType}}};
|
||||||
|
{{/isBinary}}
|
||||||
|
{{^isBinary}}
|
||||||
|
const body: {{{dataType}}} = ObjectSerializer.deserialize(
|
||||||
|
ObjectSerializer.parse(await response.body.text(), contentType),
|
||||||
|
"{{{dataType}}}", "{{returnFormat}}"
|
||||||
|
) as {{{dataType}}};
|
||||||
|
{{/isBinary}}
|
||||||
|
{{#is2xx}}
|
||||||
|
return body;
|
||||||
|
{{/is2xx}}
|
||||||
|
{{^is2xx}}
|
||||||
|
throw new ApiException<{{{dataType}}}>(response.httpStatusCode, "{{message}}", body, response.headers);
|
||||||
|
{{/is2xx}}
|
||||||
|
{{/dataType}}
|
||||||
|
{{^dataType}}
|
||||||
|
{{#is2xx}}
|
||||||
|
return;
|
||||||
|
{{/is2xx}}
|
||||||
|
{{^is2xx}}
|
||||||
|
throw new ApiException<undefined>(response.httpStatusCode, "{{message}}", undefined, response.headers);
|
||||||
|
{{/is2xx}}
|
||||||
|
{{/dataType}}
|
||||||
|
}
|
||||||
|
{{/responses}}
|
||||||
|
|
||||||
|
// Work around for missing responses in specification, e.g. for petstore.yaml
|
||||||
|
// if (response.httpStatusCode >= 200 && response.httpStatusCode <= 299) {
|
||||||
|
// {{#returnType}}
|
||||||
|
// {{#isBinary}}
|
||||||
|
// const body: {{{returnType}}} = await response.getBodyAsFile() as any as {{{returnType}}};
|
||||||
|
// {{/isBinary}}
|
||||||
|
// {{^isBinary}}
|
||||||
|
// const body: {{{returnType}}} = ObjectSerializer.deserialize(
|
||||||
|
// ObjectSerializer.parse(await response.body.text(), contentType),
|
||||||
|
// "{{{returnType}}}", "{{returnFormat}}"
|
||||||
|
// ) as {{{returnType}}};
|
||||||
|
// {{/isBinary}}
|
||||||
|
// return body;
|
||||||
|
// {{/returnType}}
|
||||||
|
// {{^returnType}}
|
||||||
|
// return;
|
||||||
|
// {{/returnType}}
|
||||||
|
// }
|
||||||
|
|
||||||
|
throw new ApiException<string | {{{fileContentDataType}}} | undefined>(response.httpStatusCode, "Unknown API Status Code!", await response.getBodyAsAny(), response.headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
{{/operation}}
|
||||||
|
}
|
||||||
|
{{/operations}}
|
||||||
44
templates/api/baseapi.mustache
Normal file
44
templates/api/baseapi.mustache
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import { Configuration } from '../configuration{{extensionForDeno}}'
|
||||||
|
{{#useInversify}}
|
||||||
|
import { injectable, inject } from "inversify";
|
||||||
|
import { AbstractConfiguration } from "../services/configuration";
|
||||||
|
{{/useInversify}}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
*/
|
||||||
|
export const COLLECTION_FORMATS = {
|
||||||
|
csv: ",",
|
||||||
|
ssv: " ",
|
||||||
|
tsv: "\t",
|
||||||
|
pipes: "|",
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @class BaseAPI
|
||||||
|
*/
|
||||||
|
{{#useInversify}}
|
||||||
|
@injectable()
|
||||||
|
{{/useInversify}}
|
||||||
|
export class BaseAPIRequestFactory {
|
||||||
|
|
||||||
|
constructor({{#useInversify}}@inject(AbstractConfiguration) {{/useInversify}}protected configuration: Configuration) {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @class RequiredError
|
||||||
|
* @extends {Error}
|
||||||
|
*/
|
||||||
|
export class RequiredError extends Error {
|
||||||
|
name: "RequiredError" = "RequiredError";
|
||||||
|
constructor(public api: string, public method: string, public field: string) {
|
||||||
|
super("Required parameter " + field + " was null or undefined when calling " + api + "." + method + ".");
|
||||||
|
}
|
||||||
|
}
|
||||||
15
templates/api/exception.mustache
Normal file
15
templates/api/exception.mustache
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
/**
|
||||||
|
* Represents an error caused by an api call i.e. it has attributes for a HTTP status code
|
||||||
|
* and the returned body object.
|
||||||
|
*
|
||||||
|
* Example
|
||||||
|
* API returns a ErrorMessageObject whenever HTTP status code is not in [200, 299]
|
||||||
|
* => ApiException(404, someErrorMessageObject)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export class ApiException<T> extends Error {
|
||||||
|
public constructor(public code: number, message: string, public body: T, public headers: { [key: string]: string; }) {
|
||||||
|
super("HTTP-Code: " + code + "\nMessage: " + message + "\nBody: " + JSON.stringify(body) + "\nHeaders: " +
|
||||||
|
JSON.stringify(headers))
|
||||||
|
}
|
||||||
|
}
|
||||||
66
templates/api/middleware.mustache
Normal file
66
templates/api/middleware.mustache
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import {RequestContext, ResponseContext} from './http/http{{extensionForDeno}}';
|
||||||
|
import { Observable, from } from {{#useRxJS}}'rxjs'{{/useRxJS}}{{^useRxJS}}'./rxjsStub{{extensionForDeno}}'{{/useRxJS}};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines the contract for a middleware intercepting requests before
|
||||||
|
* they are sent (but after the RequestContext was created)
|
||||||
|
* and before the ResponseContext is unwrapped.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export interface Middleware {
|
||||||
|
/**
|
||||||
|
* Modifies the request before the request is sent.
|
||||||
|
*
|
||||||
|
* @param context RequestContext of a request which is about to be sent to the server
|
||||||
|
* @returns an observable of the updated request context
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
pre(context: RequestContext): Observable<RequestContext>;
|
||||||
|
/**
|
||||||
|
* Modifies the returned response before it is deserialized.
|
||||||
|
*
|
||||||
|
* @param context ResponseContext of a sent request
|
||||||
|
* @returns an observable of the modified response context
|
||||||
|
*/
|
||||||
|
post(context: ResponseContext): Observable<ResponseContext>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PromiseMiddlewareWrapper implements Middleware {
|
||||||
|
|
||||||
|
public constructor(private middleware: PromiseMiddleware) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pre(context: RequestContext): Observable<RequestContext> {
|
||||||
|
return from(this.middleware.pre(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
post(context: ResponseContext): Observable<ResponseContext> {
|
||||||
|
return from(this.middleware.post(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines the contract for a middleware intercepting requests before
|
||||||
|
* they are sent (but after the RequestContext was created)
|
||||||
|
* and before the ResponseContext is unwrapped.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export interface PromiseMiddleware {
|
||||||
|
/**
|
||||||
|
* Modifies the request before the request is sent.
|
||||||
|
*
|
||||||
|
* @param context RequestContext of a request which is about to be sent to the server
|
||||||
|
* @returns an observable of the updated request context
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
pre(context: RequestContext): Promise<RequestContext>;
|
||||||
|
/**
|
||||||
|
* Modifies the returned response before it is deserialized.
|
||||||
|
*
|
||||||
|
* @param context ResponseContext of a sent request
|
||||||
|
* @returns an observable of the modified response context
|
||||||
|
*/
|
||||||
|
post(context: ResponseContext): Promise<ResponseContext>;
|
||||||
|
}
|
||||||
84
templates/api_doc.mustache
Normal file
84
templates/api_doc.mustache
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
# {{moduleName}}.{{classname}}{{#description}}
|
||||||
|
|
||||||
|
{{description}}{{/description}}
|
||||||
|
|
||||||
|
All URIs are relative to *{{basePath}}*
|
||||||
|
|
||||||
|
Method | HTTP request | Description
|
||||||
|
------------- | ------------- | -------------
|
||||||
|
{{#operations}}{{#operation}}[**{{operationId}}**]({{classname}}.md#{{operationId}}) | **{{httpMethod}}** {{path}} | {{#summary}}{{summary}}{{/summary}}
|
||||||
|
{{/operation}}{{/operations}}
|
||||||
|
|
||||||
|
{{#operations}}
|
||||||
|
{{#operation}}
|
||||||
|
# **{{{operationId}}}**
|
||||||
|
> {{#returnType}}{{{returnType}}} {{/returnType}}{{{operationId}}}({{#requiredParams}}{{^defaultValue}}{{paramName}}{{^-last}}, {{/-last}}{{/defaultValue}}{{/requiredParams}})
|
||||||
|
|
||||||
|
{{#notes}}
|
||||||
|
{{{notes}}}
|
||||||
|
{{/notes}}
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { {{{moduleName}}} } from '{{{projectName}}}';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
|
||||||
|
const configuration = {{{moduleName}}}.createConfiguration();
|
||||||
|
const apiInstance = new {{{moduleName}}}.{{classname}}(configuration);
|
||||||
|
|
||||||
|
{{#hasParams}}
|
||||||
|
let body:{{{moduleName}}}.{{classname}}{{operationIdCamelCase}}Request = {
|
||||||
|
{{#allParams}}
|
||||||
|
// {{{dataType}}}{{#description}} | {{{description}}}{{/description}}{{^required}} (optional){{/required}}
|
||||||
|
{{paramName}}: {{{example}}},
|
||||||
|
{{/allParams}}
|
||||||
|
};
|
||||||
|
{{/hasParams}}
|
||||||
|
{{^hasParams}}
|
||||||
|
let body:any = {};
|
||||||
|
{{/hasParams}}
|
||||||
|
|
||||||
|
apiInstance.{{{operationId}}}(body).then((data:any) => {
|
||||||
|
console.log('API called successfully. Returned data: ' + data);
|
||||||
|
}).catch((error:any) => console.error(error));
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Parameters
|
||||||
|
{{^hasParams}}This endpoint does not need any parameter.{{/hasParams}}{{#allParams}}{{#-last}}
|
||||||
|
Name | Type | Description | Notes
|
||||||
|
------------- | ------------- | ------------- | -------------{{/-last}}{{/allParams}}
|
||||||
|
{{#allParams}}{{^defaultValue}} **{{paramName}}** | {{^isPrimitiveType}}**{{{dataType}}}**{{/isPrimitiveType}}{{#isPrimitiveType}}**{{dataType}}**{{/isPrimitiveType}}| {{description}} |
|
||||||
|
{{/defaultValue}}{{/allParams}}{{#allParams}}{{#defaultValue}} **{{paramName}}** | {{^isPrimitiveType}}{{^isEnum}}**{{dataType}}**{{/isEnum}}{{/isPrimitiveType}}{{#isPrimitiveType}}[**{{dataType}}**]{{/isPrimitiveType}}{{#isEnum}}{{#allowableValues}}{{#enumVars}}{{#-first}}**Array<{{/-first}}{{value}}{{^-last}} | {{/-last}}{{#-last}}>**{{/-last}}{{/enumVars}}{{/allowableValues}}{{/isEnum}} | {{description}} |{{^required}} (optional){{/required}} defaults to {{{.}}}
|
||||||
|
{{/defaultValue}}{{/allParams}}
|
||||||
|
|
||||||
|
### Return type
|
||||||
|
|
||||||
|
{{#returnType}}{{#returnTypeIsPrimitive}}**{{{returnType}}}**{{/returnTypeIsPrimitive}}{{^returnTypeIsPrimitive}}**{{{returnType}}}**{{/returnTypeIsPrimitive}}{{/returnType}}{{^returnType}}void (empty response body){{/returnType}}
|
||||||
|
|
||||||
|
### Authorization
|
||||||
|
|
||||||
|
{{^authMethods}}No authorization required{{/authMethods}}{{#authMethods}}[{{{name}}}](README.md#{{{name}}}){{^-last}}, {{/-last}}{{/authMethods}}
|
||||||
|
|
||||||
|
### HTTP request headers
|
||||||
|
|
||||||
|
- **Content-Type**: {{#consumes}}{{{mediaType}}}{{^-last}}, {{/-last}}{{/consumes}}{{^consumes}}Not defined{{/consumes}}
|
||||||
|
- **Accept**: {{#produces}}{{{mediaType}}}{{^-last}}, {{/-last}}{{/produces}}{{^produces}}Not defined{{/produces}}
|
||||||
|
|
||||||
|
{{#responses.0}}
|
||||||
|
|
||||||
|
### HTTP response details
|
||||||
|
| Status code | Description | Response headers |
|
||||||
|
|-------------|-------------|------------------|
|
||||||
|
{{#responses}}
|
||||||
|
**{{code}}** | {{message}} | {{#headers}} * {{baseName}} - {{description}} <br> {{/headers}}{{^headers.0}} - {{/headers.0}} |
|
||||||
|
{{/responses}}
|
||||||
|
{{/responses.0}}
|
||||||
|
|
||||||
|
[[Back to top]](#) [[Back to API list]](README.md#documentation-for-api-endpoints) [[Back to Model list]](README.md#documentation-for-models) [[Back to README]](README.md)
|
||||||
|
|
||||||
|
{{/operation}}
|
||||||
|
{{/operations}}
|
||||||
|
|
||||||
178
templates/auth/auth.mustache
Normal file
178
templates/auth/auth.mustache
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
{{#platforms}}
|
||||||
|
{{#node}}
|
||||||
|
// typings for btoa are incorrect
|
||||||
|
//@ts-ignore
|
||||||
|
import {{^supportsES6}}* as{{/supportsES6}} btoa from "btoa";
|
||||||
|
{{/node}}
|
||||||
|
{{/platforms}}
|
||||||
|
import { RequestContext } from "../http/http{{extensionForDeno}}";
|
||||||
|
{{#useInversify}}
|
||||||
|
import { injectable, inject, named } from "inversify";
|
||||||
|
import { AbstractTokenProvider } from "../services/configuration";
|
||||||
|
{{/useInversify}}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface authentication schemes.
|
||||||
|
*/
|
||||||
|
export interface SecurityAuthentication {
|
||||||
|
/*
|
||||||
|
* @return returns the name of the security authentication as specified in OAI
|
||||||
|
*/
|
||||||
|
getName(): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies the authentication scheme to the request context
|
||||||
|
*
|
||||||
|
* @params context the request context which should use this authentication scheme
|
||||||
|
*/
|
||||||
|
applySecurityAuthentication(context: RequestContext): void | Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
{{#useInversify}}
|
||||||
|
export const AuthApiKey = Symbol("auth.api_key");
|
||||||
|
export const AuthUsername = Symbol("auth.username");
|
||||||
|
export const AuthPassword = Symbol("auth.password");
|
||||||
|
|
||||||
|
{{/useInversify}}
|
||||||
|
export interface TokenProvider {
|
||||||
|
getToken(): Promise<string> | string;
|
||||||
|
}
|
||||||
|
|
||||||
|
{{#authMethods}}
|
||||||
|
/**
|
||||||
|
* Applies {{type}} authentication to the request context.
|
||||||
|
*/
|
||||||
|
{{#useInversify}}
|
||||||
|
@injectable()
|
||||||
|
{{/useInversify}}
|
||||||
|
export class {{#lambda.pascalcase}}{{name}}{{/lambda.pascalcase}}Authentication implements SecurityAuthentication {
|
||||||
|
{{#isApiKey}}
|
||||||
|
/**
|
||||||
|
* Configures this api key authentication with the necessary properties
|
||||||
|
*
|
||||||
|
* @param apiKey: The api key to be used for every request
|
||||||
|
*/
|
||||||
|
public constructor({{#useInversify}}@inject(AuthApiKey) @named("{{name}}") {{/useInversify}}private apiKey: string) {}
|
||||||
|
{{/isApiKey}}
|
||||||
|
{{#isBasicBasic}}
|
||||||
|
/**
|
||||||
|
* Configures the http authentication with the required details.
|
||||||
|
*
|
||||||
|
* @param username username for http basic authentication
|
||||||
|
* @param password password for http basic authentication
|
||||||
|
*/
|
||||||
|
public constructor(
|
||||||
|
{{#useInversify}}@inject(AuthUsername) @named("{{name}}") {{/useInversify}}private username: string,
|
||||||
|
{{#useInversify}}@inject(AuthPassword) @named("{{name}}") {{/useInversify}}private password: string
|
||||||
|
) {}
|
||||||
|
{{/isBasicBasic}}
|
||||||
|
{{#isBasicBearer}}
|
||||||
|
/**
|
||||||
|
* Configures the http authentication with the required details.
|
||||||
|
*
|
||||||
|
* @param tokenProvider service that can provide the up-to-date token when needed
|
||||||
|
*/
|
||||||
|
public constructor({{#useInversify}}@inject(AbstractTokenProvider) @named("{{name}}") {{/useInversify}}private tokenProvider: TokenProvider) {}
|
||||||
|
{{/isBasicBearer}}
|
||||||
|
{{#isOAuth}}
|
||||||
|
/**
|
||||||
|
* Configures OAuth2 with the necessary properties
|
||||||
|
*
|
||||||
|
* @param accessToken: The access token to be used for every request
|
||||||
|
*/
|
||||||
|
public constructor(private accessToken: string) {}
|
||||||
|
{{/isOAuth}}
|
||||||
|
|
||||||
|
public getName(): string {
|
||||||
|
return "{{name}}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public {{#isBasicBearer}}async {{/isBasicBearer}}applySecurityAuthentication(context: RequestContext) {
|
||||||
|
{{#isApiKey}}
|
||||||
|
context.{{#isKeyInHeader}}setHeaderParam{{/isKeyInHeader}}{{#isKeyInQuery}}setQueryParam{{/isKeyInQuery}}{{#isKeyInCookie}}addCookie{{/isKeyInCookie}}("{{keyParamName}}", this.apiKey);
|
||||||
|
{{/isApiKey}}
|
||||||
|
{{#isBasicBasic}}
|
||||||
|
let comb = this.username + ":" + this.password;
|
||||||
|
context.setHeaderParam("Authorization", "Basic " + btoa(comb));
|
||||||
|
{{/isBasicBasic}}
|
||||||
|
{{#isBasicBearer}}
|
||||||
|
context.setHeaderParam("Authorization", "Bearer " + await this.tokenProvider.getToken());
|
||||||
|
{{/isBasicBearer}}
|
||||||
|
{{#isOAuth}}
|
||||||
|
context.setHeaderParam("Authorization", "Bearer " + this.accessToken);
|
||||||
|
{{/isOAuth}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{{/authMethods}}
|
||||||
|
|
||||||
|
export type AuthMethods = {
|
||||||
|
{{^useInversify}}
|
||||||
|
"default"?: SecurityAuthentication,
|
||||||
|
{{/useInversify}}
|
||||||
|
{{#authMethods}}
|
||||||
|
"{{name}}"?: SecurityAuthentication{{^-last}},{{/-last}}
|
||||||
|
{{/authMethods}}
|
||||||
|
}
|
||||||
|
{{#useInversify}}
|
||||||
|
|
||||||
|
export const authMethodServices = {
|
||||||
|
{{^useInversify}}
|
||||||
|
"default"?: SecurityAuthentication,
|
||||||
|
{{/useInversify}}
|
||||||
|
{{#authMethods}}
|
||||||
|
"{{name}}": {{#lambda.pascalcase}}{{name}}{{/lambda.pascalcase}}Authentication{{^-last}},{{/-last}}
|
||||||
|
{{/authMethods}}
|
||||||
|
}
|
||||||
|
{{/useInversify}}
|
||||||
|
|
||||||
|
export type ApiKeyConfiguration = string;
|
||||||
|
export type HttpBasicConfiguration = { "username": string, "password": string };
|
||||||
|
export type HttpBearerConfiguration = { tokenProvider: TokenProvider };
|
||||||
|
export type OAuth2Configuration = { accessToken: string };
|
||||||
|
|
||||||
|
export type AuthMethodsConfiguration = {
|
||||||
|
{{^useInversify}}
|
||||||
|
"default"?: SecurityAuthentication,
|
||||||
|
{{/useInversify}}
|
||||||
|
{{#authMethods}}
|
||||||
|
"{{name}}"?: {{#isApiKey}}ApiKeyConfiguration{{/isApiKey}}{{#isBasicBasic}}HttpBasicConfiguration{{/isBasicBasic}}{{#isBasicBearer}}HttpBearerConfiguration{{/isBasicBearer}}{{#isOAuth}}OAuth2Configuration{{/isOAuth}}{{^-last}},{{/-last}}
|
||||||
|
{{/authMethods}}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the authentication methods from a swagger description.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export function configureAuthMethods(config: AuthMethodsConfiguration | undefined): AuthMethods {
|
||||||
|
let authMethods: AuthMethods = {}
|
||||||
|
|
||||||
|
if (!config) {
|
||||||
|
return authMethods;
|
||||||
|
}
|
||||||
|
{{^useInversify}}
|
||||||
|
authMethods["default"] = config["default"]
|
||||||
|
{{/useInversify}}
|
||||||
|
|
||||||
|
{{#authMethods}}
|
||||||
|
if (config["{{name}}"]) {
|
||||||
|
authMethods["{{name}}"] = new {{#lambda.pascalcase}}{{name}}{{/lambda.pascalcase}}Authentication(
|
||||||
|
{{#isApiKey}}
|
||||||
|
config["{{name}}"]
|
||||||
|
{{/isApiKey}}
|
||||||
|
{{#isBasicBasic}}
|
||||||
|
config["{{name}}"]["username"],
|
||||||
|
config["{{name}}"]["password"]
|
||||||
|
{{/isBasicBasic}}
|
||||||
|
{{#isBasicBearer}}
|
||||||
|
config["{{name}}"]["tokenProvider"]
|
||||||
|
{{/isBasicBearer}}
|
||||||
|
{{#isOAuth}}
|
||||||
|
config["{{name}}"]["accessToken"]
|
||||||
|
{{/isOAuth}}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
{{/authMethods}}
|
||||||
|
return authMethods;
|
||||||
|
}
|
||||||
73
templates/configuration.mustache
Normal file
73
templates/configuration.mustache
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import { HttpLibrary } from "./http/http{{extensionForDeno}}";
|
||||||
|
import { Middleware, PromiseMiddleware, PromiseMiddlewareWrapper } from "./middleware{{extensionForDeno}}";
|
||||||
|
{{#frameworks}}
|
||||||
|
{{#fetch-api}}
|
||||||
|
import { IsomorphicFetchHttpLibrary as DefaultHttpLibrary } from "./http/isomorphic-fetch{{extensionForDeno}}";
|
||||||
|
{{/fetch-api}}
|
||||||
|
{{#jquery}}
|
||||||
|
import { JQueryHttpLibrary as DefaultHttpLibrary } from "./http/jquery";
|
||||||
|
{{/jquery}}
|
||||||
|
{{/frameworks}}
|
||||||
|
import { BaseServerConfiguration, server1 } from "./servers{{extensionForDeno}}";
|
||||||
|
import { configureAuthMethods, AuthMethods, AuthMethodsConfiguration } from "./auth/auth{{extensionForDeno}}";
|
||||||
|
|
||||||
|
export interface Configuration {
|
||||||
|
readonly baseServer: BaseServerConfiguration;
|
||||||
|
readonly httpApi: HttpLibrary;
|
||||||
|
readonly middleware: Middleware[];
|
||||||
|
readonly authMethods: AuthMethods;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface with which a configuration object can be configured.
|
||||||
|
*/
|
||||||
|
export interface ConfigurationParameters {
|
||||||
|
/**
|
||||||
|
* Default server to use
|
||||||
|
*/
|
||||||
|
baseServer?: BaseServerConfiguration;
|
||||||
|
/**
|
||||||
|
* HTTP library to use e.g. IsomorphicFetch
|
||||||
|
*/
|
||||||
|
httpApi?: HttpLibrary;
|
||||||
|
/**
|
||||||
|
* The middlewares which will be applied to requests and responses
|
||||||
|
*/
|
||||||
|
middleware?: Middleware[];
|
||||||
|
/**
|
||||||
|
* Configures all middlewares using the promise api instead of observables (which Middleware uses)
|
||||||
|
*/
|
||||||
|
promiseMiddleware?: PromiseMiddleware[];
|
||||||
|
/**
|
||||||
|
* Configuration for the available authentication methods
|
||||||
|
*/
|
||||||
|
authMethods?: AuthMethodsConfiguration
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration factory function
|
||||||
|
*
|
||||||
|
* If a property is not included in conf, a default is used:
|
||||||
|
* - baseServer: server1
|
||||||
|
* - httpApi: IsomorphicFetchHttpLibrary
|
||||||
|
* - middleware: []
|
||||||
|
* - promiseMiddleware: []
|
||||||
|
* - authMethods: {}
|
||||||
|
*
|
||||||
|
* @param conf partial configuration
|
||||||
|
*/
|
||||||
|
export function createConfiguration(conf: ConfigurationParameters = {}): Configuration {
|
||||||
|
const configuration: Configuration = {
|
||||||
|
baseServer: conf.baseServer !== undefined ? conf.baseServer : server1,
|
||||||
|
httpApi: conf.httpApi || new DefaultHttpLibrary(),
|
||||||
|
middleware: conf.middleware || [],
|
||||||
|
authMethods: configureAuthMethods(conf.authMethods)
|
||||||
|
};
|
||||||
|
if (conf.promiseMiddleware) {
|
||||||
|
conf.promiseMiddleware.forEach(
|
||||||
|
m => configuration.middleware.push(new PromiseMiddlewareWrapper(m))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return configuration;
|
||||||
|
}
|
||||||
51
templates/git_push.sh.mustache
Normal file
51
templates/git_push.sh.mustache
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# ref: https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/
|
||||||
|
#
|
||||||
|
# Usage example: /bin/sh ./git_push.sh wing328 openapi-petstore-perl "minor update"
|
||||||
|
|
||||||
|
git_user_id=$1
|
||||||
|
git_repo_id=$2
|
||||||
|
release_note=$3
|
||||||
|
|
||||||
|
if [ "$git_user_id" = "" ]; then
|
||||||
|
git_user_id="{{{gitUserId}}}"
|
||||||
|
echo "[INFO] No command line input provided. Set \$git_user_id to $git_user_id"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$git_repo_id" = "" ]; then
|
||||||
|
git_repo_id="{{{gitRepoId}}}"
|
||||||
|
echo "[INFO] No command line input provided. Set \$git_repo_id to $git_repo_id"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$release_note" = "" ]; then
|
||||||
|
release_note="{{{releaseNote}}}"
|
||||||
|
echo "[INFO] No command line input provided. Set \$release_note to $release_note"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Initialize the local directory as a Git repository
|
||||||
|
git init
|
||||||
|
|
||||||
|
# Adds the files in the local repository and stages them for commit.
|
||||||
|
git add .
|
||||||
|
|
||||||
|
# Commits the tracked changes and prepares them to be pushed to a remote repository.
|
||||||
|
git commit -m "$release_note"
|
||||||
|
|
||||||
|
# Sets the new remote
|
||||||
|
git_remote=$(git remote)
|
||||||
|
if [ "$git_remote" = "" ]; then # git remote not defined
|
||||||
|
|
||||||
|
if [ "$GIT_TOKEN" = "" ]; then
|
||||||
|
echo "[INFO] \$GIT_TOKEN (environment variable) is not set. Using the git credential in your environment."
|
||||||
|
git remote add origin https://github.com/${git_user_id}/${git_repo_id}.git
|
||||||
|
else
|
||||||
|
git remote add origin https://${git_user_id}:"${GIT_TOKEN}"@github.com/${git_user_id}/${git_repo_id}.git
|
||||||
|
fi
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
git pull origin master
|
||||||
|
|
||||||
|
# Pushes (Forces) the changes in the local repository up to the remote repository
|
||||||
|
echo "Git pushing to https://github.com/${git_user_id}/${git_repo_id}.git"
|
||||||
|
git push origin master 2>&1 | grep -v 'To https'
|
||||||
339
templates/http/http.mustache
Normal file
339
templates/http/http.mustache
Normal file
@@ -0,0 +1,339 @@
|
|||||||
|
{{#platforms}}
|
||||||
|
{{#node}}
|
||||||
|
// TODO: evaluate if we can easily get rid of this library
|
||||||
|
import {{^supportsES6}}* as{{/supportsES6}} FormData from "form-data";
|
||||||
|
import { URLSearchParams } from 'url';
|
||||||
|
import * as http from 'http';
|
||||||
|
import * as https from 'https';
|
||||||
|
{{/node}}
|
||||||
|
{{/platforms}}
|
||||||
|
{{#platforms}}
|
||||||
|
{{^deno}}
|
||||||
|
import {{^supportsES6}}* as{{/supportsES6}} URLParse from "url-parse";
|
||||||
|
{{/deno}}
|
||||||
|
{{/platforms}}
|
||||||
|
import { Observable, from } from {{#useRxJS}}'rxjs'{{/useRxJS}}{{^useRxJS}}'../rxjsStub{{extensionForDeno}}'{{/useRxJS}};
|
||||||
|
|
||||||
|
{{#platforms}}
|
||||||
|
{{^deno}}
|
||||||
|
{{#frameworks}}
|
||||||
|
{{#fetch-api}}
|
||||||
|
export * from './isomorphic-fetch';
|
||||||
|
{{/fetch-api}}
|
||||||
|
{{#jquery}}
|
||||||
|
export * from './jquery';
|
||||||
|
{{/jquery}}
|
||||||
|
{{/frameworks}}
|
||||||
|
{{/deno}}
|
||||||
|
{{/platforms}}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents an HTTP method.
|
||||||
|
*/
|
||||||
|
export enum HttpMethod {
|
||||||
|
GET = "GET",
|
||||||
|
HEAD = "HEAD",
|
||||||
|
POST = "POST",
|
||||||
|
PUT = "PUT",
|
||||||
|
DELETE = "DELETE",
|
||||||
|
CONNECT = "CONNECT",
|
||||||
|
OPTIONS = "OPTIONS",
|
||||||
|
TRACE = "TRACE",
|
||||||
|
PATCH = "PATCH"
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents an HTTP file which will be transferred from or to a server.
|
||||||
|
*/
|
||||||
|
{{#platforms}}
|
||||||
|
{{#node}}
|
||||||
|
export type HttpFile = {
|
||||||
|
data: {{{fileContentDataType}}},
|
||||||
|
name: string
|
||||||
|
};
|
||||||
|
{{/node}}
|
||||||
|
{{^node}}
|
||||||
|
export type HttpFile = {{{fileContentDataType}}} & { readonly name: string };
|
||||||
|
{{/node}}
|
||||||
|
{{/platforms}}
|
||||||
|
|
||||||
|
{{#platforms}}
|
||||||
|
{{#deno}}
|
||||||
|
/**
|
||||||
|
* URLParse Wrapper for Deno
|
||||||
|
*/
|
||||||
|
class URLParse {
|
||||||
|
private url: URL;
|
||||||
|
|
||||||
|
constructor(address: string, _parser: boolean) {
|
||||||
|
this.url = new URL(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
public set(_part: 'query', obj: {[key: string]: string | undefined}) {
|
||||||
|
for (const key in obj) {
|
||||||
|
const value = obj[key];
|
||||||
|
if (value) {
|
||||||
|
this.url.searchParams.set(key, value);
|
||||||
|
} else {
|
||||||
|
this.url.searchParams.set(key, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public get query() {
|
||||||
|
const obj: {[key: string]: string} = {};
|
||||||
|
for (const [key, value] of this.url.searchParams.entries()) {
|
||||||
|
obj[key] = value;
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
public toString() {
|
||||||
|
return this.url.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{{/deno}}
|
||||||
|
{{/platforms}}
|
||||||
|
|
||||||
|
export class HttpException extends Error {
|
||||||
|
public constructor(msg: string) {
|
||||||
|
super(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the body of an outgoing HTTP request.
|
||||||
|
*/
|
||||||
|
export type RequestBody = undefined | string | FormData | URLSearchParams;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents an HTTP request context
|
||||||
|
*/
|
||||||
|
export class RequestContext {
|
||||||
|
private headers: { [key: string]: string } = {};
|
||||||
|
private body: RequestBody = undefined;
|
||||||
|
private url: URLParse;
|
||||||
|
{{#platforms}}
|
||||||
|
{{#node}}
|
||||||
|
private agent: http.Agent | https.Agent | undefined = undefined;
|
||||||
|
{{/node}}
|
||||||
|
{{/platforms}}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the request context using a http method and request resource url
|
||||||
|
*
|
||||||
|
* @param url url of the requested resource
|
||||||
|
* @param httpMethod http method
|
||||||
|
*/
|
||||||
|
public constructor(url: string, private httpMethod: HttpMethod) {
|
||||||
|
this.url = new URLParse(url, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns the url set in the constructor including the query string
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public getUrl(): string {
|
||||||
|
return this.url.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replaces the url set in the constructor with this url.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public setUrl(url: string) {
|
||||||
|
this.url = new URLParse(url, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the body of the http request either as a string or FormData
|
||||||
|
*
|
||||||
|
* Note that setting a body on a HTTP GET, HEAD, DELETE, CONNECT or TRACE
|
||||||
|
* request is discouraged.
|
||||||
|
* https://httpwg.org/http-core/draft-ietf-httpbis-semantics-latest.html#rfc.section.7.3.1
|
||||||
|
*
|
||||||
|
* @param body the body of the request
|
||||||
|
*/
|
||||||
|
public setBody(body: RequestBody) {
|
||||||
|
this.body = body;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getHttpMethod(): HttpMethod {
|
||||||
|
return this.httpMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getHeaders(): { [key: string]: string } {
|
||||||
|
return this.headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getBody(): RequestBody {
|
||||||
|
return this.body;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setQueryParam(name: string, value: string) {
|
||||||
|
let queryObj = this.url.query;
|
||||||
|
queryObj[name] = value;
|
||||||
|
this.url.set("query", queryObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a cookie with the name and value. NO check for duplicate cookies is performed
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public addCookie(name: string, value: string): void {
|
||||||
|
if (!this.headers["Cookie"]) {
|
||||||
|
this.headers["Cookie"] = "";
|
||||||
|
}
|
||||||
|
this.headers["Cookie"] += name + "=" + value + "; ";
|
||||||
|
}
|
||||||
|
|
||||||
|
public setHeaderParam(key: string, value: string): void {
|
||||||
|
this.headers[key] = value;
|
||||||
|
}
|
||||||
|
{{#platforms}}
|
||||||
|
{{#node}}
|
||||||
|
|
||||||
|
public setAgent(agent: http.Agent | https.Agent) {
|
||||||
|
this.agent = agent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getAgent(): http.Agent | https.Agent | undefined {
|
||||||
|
return this.agent;
|
||||||
|
}
|
||||||
|
{{/node}}
|
||||||
|
{{/platforms}}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ResponseBody {
|
||||||
|
text(): Promise<string>;
|
||||||
|
binary(): Promise<{{{fileContentDataType}}}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class to generate a `ResponseBody` from binary data
|
||||||
|
*/
|
||||||
|
export class SelfDecodingBody implements ResponseBody {
|
||||||
|
constructor(private dataSource: Promise<{{{fileContentDataType}}}>) {}
|
||||||
|
|
||||||
|
binary(): Promise<{{{fileContentDataType}}}> {
|
||||||
|
return this.dataSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
async text(): Promise<string> {
|
||||||
|
const data: {{{fileContentDataType}}} = await this.dataSource;
|
||||||
|
{{#platforms}}
|
||||||
|
{{#node}}
|
||||||
|
return data.toString();
|
||||||
|
{{/node}}
|
||||||
|
{{#browser}}
|
||||||
|
// @ts-ignore
|
||||||
|
if (data.text) {
|
||||||
|
// @ts-ignore
|
||||||
|
return data.text();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise<string>((resolve, reject) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.addEventListener("load", () => resolve(reader.result as string));
|
||||||
|
reader.addEventListener("error", () => reject(reader.error));
|
||||||
|
reader.readAsText(data);
|
||||||
|
});
|
||||||
|
{{/browser}}
|
||||||
|
{{#deno}}
|
||||||
|
return data.text();
|
||||||
|
{{/deno}}
|
||||||
|
{{/platforms}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ResponseContext {
|
||||||
|
public constructor(
|
||||||
|
public httpStatusCode: number,
|
||||||
|
public headers: { [key: string]: string },
|
||||||
|
public body: ResponseBody
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse header value in the form `value; param1="value1"`
|
||||||
|
*
|
||||||
|
* E.g. for Content-Type or Content-Disposition
|
||||||
|
* Parameter names are converted to lower case
|
||||||
|
* The first parameter is returned with the key `""`
|
||||||
|
*/
|
||||||
|
public getParsedHeader(headerName: string): { [parameter: string]: string } {
|
||||||
|
const result: { [parameter: string]: string } = {};
|
||||||
|
if (!this.headers[headerName]) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parameters = this.headers[headerName].split(";");
|
||||||
|
for (const parameter of parameters) {
|
||||||
|
let [key, value] = parameter.split("=", 2);
|
||||||
|
key = key.toLowerCase().trim();
|
||||||
|
if (value === undefined) {
|
||||||
|
result[""] = key;
|
||||||
|
} else {
|
||||||
|
value = value.trim();
|
||||||
|
if (value.startsWith('"') && value.endsWith('"')) {
|
||||||
|
value = value.substring(1, value.length - 1);
|
||||||
|
}
|
||||||
|
result[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getBodyAsFile(): Promise<HttpFile> {
|
||||||
|
const data = await this.body.binary();
|
||||||
|
const fileName = this.getParsedHeader("content-disposition")["filename"] || "";
|
||||||
|
{{#platforms}}
|
||||||
|
{{#node}}
|
||||||
|
return { data, name: fileName };
|
||||||
|
{{/node}}
|
||||||
|
{{^node}}
|
||||||
|
const contentType = this.headers["content-type"] || "";
|
||||||
|
try {
|
||||||
|
return new File([data], fileName, { type: contentType });
|
||||||
|
} catch (error) {
|
||||||
|
/** Fallback for when the File constructor is not available */
|
||||||
|
return Object.assign(data, {
|
||||||
|
name: fileName,
|
||||||
|
type: contentType
|
||||||
|
});
|
||||||
|
}
|
||||||
|
{{/node}}
|
||||||
|
{{/platforms}}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use a heuristic to get a body of unknown data structure.
|
||||||
|
* Return as string if possible, otherwise as binary.
|
||||||
|
*/
|
||||||
|
public getBodyAsAny(): Promise<string | {{{fileContentDataType}}} | undefined> {
|
||||||
|
try {
|
||||||
|
return this.body.text();
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return this.body.binary();
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
return Promise.resolve(undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HttpLibrary {
|
||||||
|
send(request: RequestContext): Observable<ResponseContext>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PromiseHttpLibrary {
|
||||||
|
send(request: RequestContext): Promise<ResponseContext>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function wrapHttpLibrary(promiseHttpLibrary: PromiseHttpLibrary): HttpLibrary {
|
||||||
|
return {
|
||||||
|
send(request: RequestContext): Observable<ResponseContext> {
|
||||||
|
return from(promiseHttpLibrary.send(request));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
56
templates/http/isomorphic-fetch.mustache
Normal file
56
templates/http/isomorphic-fetch.mustache
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import {HttpLibrary, RequestContext, ResponseContext} from './http{{extensionForDeno}}';
|
||||||
|
import { from, Observable } from {{#useRxJS}}'rxjs'{{/useRxJS}}{{^useRxJS}}'../rxjsStub{{extensionForDeno}}'{{/useRxJS}};
|
||||||
|
{{#platforms}}
|
||||||
|
{{#node}}
|
||||||
|
import fetch from "node-fetch";
|
||||||
|
{{/node}}
|
||||||
|
{{#browser}}
|
||||||
|
import "whatwg-fetch";
|
||||||
|
{{/browser}}
|
||||||
|
{{/platforms}}
|
||||||
|
|
||||||
|
export class IsomorphicFetchHttpLibrary implements HttpLibrary {
|
||||||
|
|
||||||
|
public send(request: RequestContext): Observable<ResponseContext> {
|
||||||
|
let method = request.getHttpMethod().toString();
|
||||||
|
let body = request.getBody();
|
||||||
|
|
||||||
|
const resultPromise = fetch(request.getUrl(), {
|
||||||
|
method: method,
|
||||||
|
body: body as any,
|
||||||
|
headers: request.getHeaders(),
|
||||||
|
{{#platforms}}
|
||||||
|
{{#node}}
|
||||||
|
agent: request.getAgent(),
|
||||||
|
{{/node}}
|
||||||
|
{{#browser}}
|
||||||
|
credentials: "same-origin"
|
||||||
|
{{/browser}}
|
||||||
|
{{/platforms}}
|
||||||
|
}).then((resp: any) => {
|
||||||
|
const headers: { [name: string]: string } = {};
|
||||||
|
resp.headers.forEach((value: string, name: string) => {
|
||||||
|
headers[name] = value;
|
||||||
|
});
|
||||||
|
|
||||||
|
{{#platforms}}
|
||||||
|
{{#node}}
|
||||||
|
const body = {
|
||||||
|
text: () => resp.text(),
|
||||||
|
binary: () => resp.buffer()
|
||||||
|
};
|
||||||
|
{{/node}}
|
||||||
|
{{^node}}
|
||||||
|
const body = {
|
||||||
|
text: () => resp.text(),
|
||||||
|
binary: () => resp.blob()
|
||||||
|
};
|
||||||
|
{{/node}}
|
||||||
|
{{/platforms}}
|
||||||
|
return new ResponseContext(resp.status, headers, body);
|
||||||
|
});
|
||||||
|
|
||||||
|
return from<Promise<ResponseContext>>(resultPromise);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
86
templates/http/jquery.mustache
Normal file
86
templates/http/jquery.mustache
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import { HttpLibrary, RequestContext, ResponseContext, HttpException, SelfDecodingBody } from './http';
|
||||||
|
import * as e6p from 'es6-promise'
|
||||||
|
import { from, Observable } from {{#useRxJS}}'rxjs'{{/useRxJS}}{{^useRxJS}}'../rxjsStub'{{/useRxJS}};
|
||||||
|
e6p.polyfill();
|
||||||
|
import * as $ from 'jquery';
|
||||||
|
|
||||||
|
|
||||||
|
export class JQueryHttpLibrary implements HttpLibrary {
|
||||||
|
|
||||||
|
public send(request: RequestContext): Observable<ResponseContext> {
|
||||||
|
let method = request.getHttpMethod().toString();
|
||||||
|
let body = request.getBody();
|
||||||
|
let headerParams = request.getHeaders()
|
||||||
|
|
||||||
|
let requestOptions: any = {
|
||||||
|
url: request.getUrl(),
|
||||||
|
type: method,
|
||||||
|
headers: request.getHeaders(),
|
||||||
|
processData: false,
|
||||||
|
xhrFields: { withCredentials: true },
|
||||||
|
data: body
|
||||||
|
};
|
||||||
|
|
||||||
|
// If we want a blob, we have to set the xhrFields' responseType AND add a
|
||||||
|
// custom converter to overwrite the default deserialization of JQuery...
|
||||||
|
requestOptions["xhrFields"] = { responseType: 'blob' };
|
||||||
|
requestOptions["converters"] = {}
|
||||||
|
requestOptions["converters"]["* blob"] = (result:any) => result;
|
||||||
|
requestOptions["dataType"] = "blob";
|
||||||
|
|
||||||
|
if (request.getHeaders()['Content-Type']) {
|
||||||
|
requestOptions.contentType = headerParams['Content-Type'];
|
||||||
|
}
|
||||||
|
requestOptions.dataFilter = ((headerParams: { [key:string]: string}) => {
|
||||||
|
return (data: string, type: string) => {
|
||||||
|
if (headerParams["Accept"] == "application/json" && data == "") {
|
||||||
|
return "{}"
|
||||||
|
} else {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})(headerParams);
|
||||||
|
|
||||||
|
if (request.getHeaders()["Cookie"]) {
|
||||||
|
throw new HttpException("Setting the \"Cookie\"-Header field is blocked by every major browser when using jquery.ajax requests. Please switch to another library like fetch to enable this option");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (body && body.constructor.name == "FormData") {
|
||||||
|
requestOptions.contentType = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sentRequest = $.ajax(requestOptions);
|
||||||
|
|
||||||
|
const resultPromise = new Promise<ResponseContext>((resolve, reject) => {
|
||||||
|
sentRequest.done((data, _, jqXHR) => {
|
||||||
|
const result = new ResponseContext(
|
||||||
|
jqXHR.status,
|
||||||
|
this.getResponseHeaders(jqXHR),
|
||||||
|
new SelfDecodingBody(Promise.resolve(data))
|
||||||
|
);
|
||||||
|
resolve(result);
|
||||||
|
})
|
||||||
|
sentRequest.fail((jqXHR: any) => {
|
||||||
|
const headers = this.getResponseHeaders(jqXHR)
|
||||||
|
const result = new ResponseContext(jqXHR.status, headers, jqXHR.responseText);
|
||||||
|
resolve(result);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return from(resultPromise);
|
||||||
|
}
|
||||||
|
|
||||||
|
private getResponseHeaders(jqXHR: any): { [key: string]: string } {
|
||||||
|
const responseHeaders: { [key: string]: string } = {};
|
||||||
|
var headers = jqXHR.getAllResponseHeaders();
|
||||||
|
headers = headers.split("\n");
|
||||||
|
headers.forEach(function (header: any) {
|
||||||
|
header = header.split(": ");
|
||||||
|
var key = header.shift();
|
||||||
|
if (key.length == 0) return
|
||||||
|
// chrome60+ force lowercase, other browsers can be different
|
||||||
|
key = key.toLowerCase();
|
||||||
|
responseHeaders[key] = header.join(": ");
|
||||||
|
});
|
||||||
|
return responseHeaders
|
||||||
|
}
|
||||||
|
}
|
||||||
55
templates/http/servers.mustache
Normal file
55
templates/http/servers.mustache
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import { RequestContext, HttpMethod } from "./http/http{{extensionForDeno}}";
|
||||||
|
|
||||||
|
export interface BaseServerConfiguration {
|
||||||
|
makeRequestContext(endpoint: string, httpMethod: HttpMethod): RequestContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Represents the configuration of a server including its
|
||||||
|
* url template and variable configuration based on the url.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export class ServerConfiguration<T extends { [key: string]: string }> implements BaseServerConfiguration {
|
||||||
|
public constructor(private url: string, private variableConfiguration: T) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the value of the variables of this server.
|
||||||
|
*
|
||||||
|
* @param variableConfiguration a partial variable configuration for the variables contained in the url
|
||||||
|
*/
|
||||||
|
public setVariables(variableConfiguration: Partial<T>) {
|
||||||
|
Object.assign(this.variableConfiguration, variableConfiguration);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getConfiguration(): T {
|
||||||
|
return this.variableConfiguration
|
||||||
|
}
|
||||||
|
|
||||||
|
private getUrl() {
|
||||||
|
let replacedUrl = this.url;
|
||||||
|
for (const key in this.variableConfiguration) {
|
||||||
|
var re = new RegExp("{" + key + "}","g");
|
||||||
|
replacedUrl = replacedUrl.replace(re, this.variableConfiguration[key]);
|
||||||
|
}
|
||||||
|
return replacedUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new request context for this server using the url with variables
|
||||||
|
* replaced with their respective values and the endpoint of the request appended.
|
||||||
|
*
|
||||||
|
* @param endpoint the endpoint to be queried on the server
|
||||||
|
* @param httpMethod httpMethod to be used
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public makeRequestContext(endpoint: string, httpMethod: HttpMethod): RequestContext {
|
||||||
|
return new RequestContext(this.getUrl() + endpoint, httpMethod);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{{#servers}}
|
||||||
|
export const server{{-index}} = new ServerConfiguration<{ {{#variables}} "{{name}}": {{#enumValues}}"{{.}}"{{^-last}} | {{/-last}}{{/enumValues}}{{^enumValues}}string{{/enumValues}}{{^-last}},{{/-last}} {{/variables}} }>("{{url}}", { {{#variables}} "{{name}}": "{{defaultValue}}" {{^-last}},{{/-last}}{{/variables}} })
|
||||||
|
{{/servers}}
|
||||||
|
|
||||||
|
export const servers = [{{#servers}}server{{-index}}{{^-last}}, {{/-last}}{{/servers}}];
|
||||||
41
templates/index.mustache
Normal file
41
templates/index.mustache
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
export * from "./http/http{{extensionForDeno}}";
|
||||||
|
export * from "./auth/auth{{extensionForDeno}}";
|
||||||
|
export * from "./models/all{{extensionForDeno}}";
|
||||||
|
export { createConfiguration } from "./configuration{{extensionForDeno}}"
|
||||||
|
export{{#platforms}}{{#deno}} type{{/deno}}{{/platforms}} { Configuration } from "./configuration{{extensionForDeno}}"
|
||||||
|
export * from "./apis/exception{{extensionForDeno}}";
|
||||||
|
export * from "./servers{{extensionForDeno}}";
|
||||||
|
export { RequiredError } from "./apis/baseapi{{extensionForDeno}}";
|
||||||
|
|
||||||
|
{{#useRxJS}}
|
||||||
|
export { Middleware } from './middleware{{extensionForDeno}}';
|
||||||
|
{{/useRxJS}}
|
||||||
|
{{^useRxJS}}
|
||||||
|
export{{#platforms}}{{#deno}} type{{/deno}}{{/platforms}} { PromiseMiddleware as Middleware } from './middleware{{extensionForDeno}}';
|
||||||
|
{{/useRxJS}}
|
||||||
|
{{#useObjectParameters}}
|
||||||
|
export { {{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}{{classname}}{{operationIdCamelCase}}Request, {{/operation}}Object{{classname}} as {{classname}}{{^-last}}, {{/-last}} {{/operations}}{{/apis}}{{/apiInfo}}} from './types/ObjectParamAPI{{extensionForDeno}}';
|
||||||
|
{{/useObjectParameters}}
|
||||||
|
{{^useObjectParameters}}
|
||||||
|
{{#useRxJS}}
|
||||||
|
export { {{#apiInfo}}{{#apis}}{{#operations}}Observable{{classname}} as {{classname}}{{^-last}}, {{/-last}} {{/operations}}{{/apis}}{{/apiInfo}}} from './types/ObservableAPI{{extensionForDeno}}';
|
||||||
|
{{/useRxJS}}
|
||||||
|
{{^useRxJS}}
|
||||||
|
export { {{#apiInfo}}{{#apis}}{{#operations}}Promise{{classname}} as {{classname}}{{^-last}}, {{/-last}} {{/operations}}{{/apis}}{{/apiInfo}}} from './types/PromiseAPI{{extensionForDeno}}';
|
||||||
|
{{/useRxJS}}
|
||||||
|
{{/useObjectParameters}}
|
||||||
|
|
||||||
|
{{#useInversify}}
|
||||||
|
export * from "./services/index{{extensionForDeno}}";
|
||||||
|
{{#useObjectParameters}}
|
||||||
|
export { {{#apiInfo}}{{#apis}}{{#operations}}AbstractObject{{classname}} as Abstract{{classname}}{{^-last}}, {{/-last}} {{/operations}}{{/apis}}{{/apiInfo}}} from './services/ObjectParamAPI';
|
||||||
|
{{/useObjectParameters}}
|
||||||
|
{{^useObjectParameters}}
|
||||||
|
{{#useRxJS}}
|
||||||
|
export { {{#apiInfo}}{{#apis}}{{#operations}}AbstractObservable{{classname}} as Abstract{{classname}}{{^-last}}, {{/-last}} {{/operations}}{{/apis}}{{/apiInfo}}} from './services/ObservableAPI{{extensionForDeno}}';
|
||||||
|
{{/useRxJS}}
|
||||||
|
{{^useRxJS}}
|
||||||
|
export { {{#apiInfo}}{{#apis}}{{#operations}}AbstractPromise{{classname}} as Abstract{{classname}}{{^-last}}, {{/-last}} {{/operations}}{{/apis}}{{/apiInfo}}} from './services/PromiseAPI{{extensionForDeno}}';
|
||||||
|
{{/useRxJS}}
|
||||||
|
{{/useObjectParameters}}
|
||||||
|
{{/useInversify}}
|
||||||
11
templates/licenseInfo.mustache
Normal file
11
templates/licenseInfo.mustache
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* {{{appName}}}
|
||||||
|
* {{{appDescription}}}
|
||||||
|
*
|
||||||
|
* {{#version}}OpenAPI spec version: {{{.}}}{{/version}}
|
||||||
|
* {{#infoEmail}}Contact: {{{.}}}{{/infoEmail}}
|
||||||
|
*
|
||||||
|
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||||
|
* https://openapi-generator.tech
|
||||||
|
* Do not edit the class manually.
|
||||||
|
*/
|
||||||
261
templates/model/ObjectSerializer.mustache
Normal file
261
templates/model/ObjectSerializer.mustache
Normal file
@@ -0,0 +1,261 @@
|
|||||||
|
{{#models}}
|
||||||
|
{{#model}}
|
||||||
|
export * from '{{{ importPath }}}{{extensionForDeno}}';
|
||||||
|
{{/model}}
|
||||||
|
{{/models}}
|
||||||
|
|
||||||
|
{{#models}}
|
||||||
|
{{#model}}
|
||||||
|
import { {{classname}}{{#hasEnums}}{{#vars}}{{#isEnum}}, {{classname}}{{enumName}} {{/isEnum}} {{/vars}}{{/hasEnums}} } from '{{{ importPath }}}{{extensionForDeno}}';
|
||||||
|
{{/model}}
|
||||||
|
{{/models}}
|
||||||
|
|
||||||
|
/* tslint:disable:no-unused-variable */
|
||||||
|
let primitives = [
|
||||||
|
"string",
|
||||||
|
"boolean",
|
||||||
|
"double",
|
||||||
|
"integer",
|
||||||
|
"long",
|
||||||
|
"float",
|
||||||
|
"number",
|
||||||
|
"any"
|
||||||
|
];
|
||||||
|
|
||||||
|
const supportedMediaTypes: { [mediaType: string]: number } = {
|
||||||
|
"application/json": Infinity,
|
||||||
|
"application/octet-stream": 0,
|
||||||
|
"application/x-www-form-urlencoded": 0
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let enumsMap: Set<string> = new Set<string>([
|
||||||
|
{{#models}}
|
||||||
|
{{#model}}
|
||||||
|
{{#isEnum}}
|
||||||
|
"{{classname}}{{enumName}}",
|
||||||
|
{{/isEnum}}
|
||||||
|
{{#hasEnums}}
|
||||||
|
{{#vars}}
|
||||||
|
{{#isEnum}}
|
||||||
|
"{{classname}}{{enumName}}",
|
||||||
|
{{/isEnum}}
|
||||||
|
{{/vars}}
|
||||||
|
{{/hasEnums}}
|
||||||
|
{{/model}}
|
||||||
|
{{/models}}
|
||||||
|
]);
|
||||||
|
|
||||||
|
let typeMap: {[index: string]: any} = {
|
||||||
|
{{#models}}
|
||||||
|
{{#model}}
|
||||||
|
{{^isEnum}}
|
||||||
|
"{{classname}}": {{classname}},
|
||||||
|
{{/isEnum}}
|
||||||
|
{{/model}}
|
||||||
|
{{/models}}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ObjectSerializer {
|
||||||
|
public static findCorrectType(data: any, expectedType: string) {
|
||||||
|
if (data == undefined) {
|
||||||
|
return expectedType;
|
||||||
|
} else if (primitives.indexOf(expectedType.toLowerCase()) !== -1) {
|
||||||
|
return expectedType;
|
||||||
|
} else if (expectedType === "Date") {
|
||||||
|
return expectedType;
|
||||||
|
} else {
|
||||||
|
if (enumsMap.has(expectedType)) {
|
||||||
|
return expectedType;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!typeMap[expectedType]) {
|
||||||
|
return expectedType; // w/e we don't know the type
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the discriminator
|
||||||
|
let discriminatorProperty = typeMap[expectedType].discriminator;
|
||||||
|
if (discriminatorProperty == null) {
|
||||||
|
return expectedType; // the type does not have a discriminator. use it.
|
||||||
|
} else {
|
||||||
|
if (data[discriminatorProperty]) {
|
||||||
|
var discriminatorType = data[discriminatorProperty];
|
||||||
|
if(typeMap[discriminatorType]){
|
||||||
|
return discriminatorType; // use the type given in the discriminator
|
||||||
|
} else {
|
||||||
|
return expectedType; // discriminator did not map to a type
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return expectedType; // discriminator was not present (or an empty string)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static serialize(data: any, type: string, format: string) {
|
||||||
|
if (data == undefined) {
|
||||||
|
return data;
|
||||||
|
} else if (primitives.indexOf(type.toLowerCase()) !== -1) {
|
||||||
|
return data;
|
||||||
|
} else if (type.lastIndexOf("Array<", 0) === 0) { // string.startsWith pre es6
|
||||||
|
let subType: string = type.replace("Array<", ""); // Array<Type> => Type>
|
||||||
|
subType = subType.substring(0, subType.length - 1); // Type> => Type
|
||||||
|
let transformedData: any[] = [];
|
||||||
|
for (let index in data) {
|
||||||
|
let date = data[index];
|
||||||
|
transformedData.push(ObjectSerializer.serialize(date, subType, format));
|
||||||
|
}
|
||||||
|
return transformedData;
|
||||||
|
} else if (type === "Date") {
|
||||||
|
if (format == "date") {
|
||||||
|
let month = data.getMonth()+1
|
||||||
|
month = month < 10 ? "0" + month.toString() : month.toString()
|
||||||
|
let day = data.getDate();
|
||||||
|
day = day < 10 ? "0" + day.toString() : day.toString();
|
||||||
|
|
||||||
|
return data.getFullYear() + "-" + month + "-" + day;
|
||||||
|
} else {
|
||||||
|
return data.toISOString();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (enumsMap.has(type)) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
if (!typeMap[type]) { // in case we dont know the type
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the actual type of this object
|
||||||
|
type = this.findCorrectType(data, type);
|
||||||
|
|
||||||
|
// get the map for the correct type.
|
||||||
|
let attributeTypes = typeMap[type].getAttributeTypeMap();
|
||||||
|
let instance: {[index: string]: any} = {};
|
||||||
|
for (let index in attributeTypes) {
|
||||||
|
let attributeType = attributeTypes[index];
|
||||||
|
instance[attributeType.baseName] = ObjectSerializer.serialize(data[attributeType.name], attributeType.type, attributeType.format);
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static deserialize(data: any, type: string, format: string) {
|
||||||
|
// polymorphism may change the actual type.
|
||||||
|
type = ObjectSerializer.findCorrectType(data, type);
|
||||||
|
if (data == undefined) {
|
||||||
|
return data;
|
||||||
|
} else if (primitives.indexOf(type.toLowerCase()) !== -1) {
|
||||||
|
return data;
|
||||||
|
} else if (type.lastIndexOf("Array<", 0) === 0) { // string.startsWith pre es6
|
||||||
|
let subType: string = type.replace("Array<", ""); // Array<Type> => Type>
|
||||||
|
subType = subType.substring(0, subType.length - 1); // Type> => Type
|
||||||
|
let transformedData: any[] = [];
|
||||||
|
for (let index in data) {
|
||||||
|
let date = data[index];
|
||||||
|
transformedData.push(ObjectSerializer.deserialize(date, subType, format));
|
||||||
|
}
|
||||||
|
return transformedData;
|
||||||
|
} else if (type === "Date") {
|
||||||
|
return new Date(data);
|
||||||
|
} else {
|
||||||
|
if (enumsMap.has(type)) {// is Enum
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!typeMap[type]) { // dont know the type
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
let instance = new typeMap[type]();
|
||||||
|
let attributeTypes = typeMap[type].getAttributeTypeMap();
|
||||||
|
for (let index in attributeTypes) {
|
||||||
|
let attributeType = attributeTypes[index];
|
||||||
|
let value = ObjectSerializer.deserialize(data[attributeType.baseName], attributeType.type, attributeType.format);
|
||||||
|
if (value !== undefined) {
|
||||||
|
instance[attributeType.name] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize media type
|
||||||
|
*
|
||||||
|
* We currently do not handle any media types attributes, i.e. anything
|
||||||
|
* after a semicolon. All content is assumed to be UTF-8 compatible.
|
||||||
|
*/
|
||||||
|
public static normalizeMediaType(mediaType: string | undefined): string | undefined {
|
||||||
|
if (mediaType === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return mediaType.split(";")[0].trim().toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* From a list of possible media types, choose the one we can handle best.
|
||||||
|
*
|
||||||
|
* The order of the given media types does not have any impact on the choice
|
||||||
|
* made.
|
||||||
|
*/
|
||||||
|
public static getPreferredMediaType(mediaTypes: Array<string>): string {
|
||||||
|
/** According to OAS 3 we should default to json */
|
||||||
|
if (!mediaTypes) {
|
||||||
|
return "application/json";
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalMediaTypes = mediaTypes.map(this.normalizeMediaType);
|
||||||
|
let selectedMediaType: string | undefined = undefined;
|
||||||
|
let selectedRank: number = -Infinity;
|
||||||
|
for (const mediaType of normalMediaTypes) {
|
||||||
|
if (supportedMediaTypes[mediaType!] > selectedRank) {
|
||||||
|
selectedMediaType = mediaType;
|
||||||
|
selectedRank = supportedMediaTypes[mediaType!];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedMediaType === undefined) {
|
||||||
|
throw new Error("None of the given media types are supported: " + mediaTypes.join(", "));
|
||||||
|
}
|
||||||
|
|
||||||
|
return selectedMediaType!;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert data to a string according the given media type
|
||||||
|
*/
|
||||||
|
public static stringify(data: any, mediaType: string): string {
|
||||||
|
if (mediaType === "text/plain") {
|
||||||
|
return String(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mediaType === "application/json") {
|
||||||
|
return JSON.stringify(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error("The mediaType " + mediaType + " is not supported by ObjectSerializer.stringify.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse data from a string according to the given media type
|
||||||
|
*/
|
||||||
|
public static parse(rawData: string, mediaType: string | undefined) {
|
||||||
|
if (mediaType === undefined) {
|
||||||
|
throw new Error("Cannot parse content. No Content-Type defined.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mediaType === "text/plain") {
|
||||||
|
return rawData;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mediaType === "application/json") {
|
||||||
|
return JSON.parse(rawData);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mediaType === "text/html") {
|
||||||
|
return rawData;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error("The mediaType " + mediaType + " is not supported by ObjectSerializer.parse.");
|
||||||
|
}
|
||||||
|
}
|
||||||
126
templates/model/model.mustache
Normal file
126
templates/model/model.mustache
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
{{>licenseInfo}}
|
||||||
|
{{#models}}
|
||||||
|
{{#model}}
|
||||||
|
{{#tsImports}}
|
||||||
|
import { {{classname}} } from '{{filename}}{{extensionForDeno}}';
|
||||||
|
{{/tsImports}}
|
||||||
|
import { HttpFile } from '../http/http{{extensionForDeno}}';
|
||||||
|
|
||||||
|
{{#description}}
|
||||||
|
/**
|
||||||
|
* {{{.}}}
|
||||||
|
*/
|
||||||
|
{{/description}}
|
||||||
|
{{^isEnum}}
|
||||||
|
export interface I{{classname}} {{#parent}}extends I{{{.}}} {{/parent}}{
|
||||||
|
{{#vars}}
|
||||||
|
{{#description}}
|
||||||
|
/**
|
||||||
|
* {{{.}}}
|
||||||
|
*/
|
||||||
|
{{/description}}
|
||||||
|
'{{name}}'?: {{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}};
|
||||||
|
{{/vars}}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}implements I{{classname}} {
|
||||||
|
{{#vars}}
|
||||||
|
{{#description}}
|
||||||
|
/**
|
||||||
|
* {{{.}}}
|
||||||
|
*/
|
||||||
|
{{/description}}
|
||||||
|
'{{name}}'{{#required}}!{{/required}}{{^required}}?{{/required}}: {{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}};
|
||||||
|
{{/vars}}
|
||||||
|
|
||||||
|
{{#discriminator}}
|
||||||
|
static readonly discriminator: string | undefined = "{{discriminatorName}}";
|
||||||
|
{{/discriminator}}
|
||||||
|
{{^discriminator}}
|
||||||
|
static readonly discriminator: string | undefined = undefined;
|
||||||
|
{{/discriminator}}
|
||||||
|
|
||||||
|
{{^isArray}}
|
||||||
|
static readonly attributeTypeMap: Array<{name: string, baseName: string, type: string, format: string, required?: boolean, minLength?: number, maxLength?: number, min?: number, max?: number, pattern?: RegExp}> = [
|
||||||
|
{{#vars}}
|
||||||
|
{
|
||||||
|
"name": "{{name}}",
|
||||||
|
"baseName": "{{baseName}}",
|
||||||
|
"type": "{{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}",
|
||||||
|
"format": "{{dataFormat}}"
|
||||||
|
{{#required}},"required": {{required}}{{/required}}
|
||||||
|
{{#minLength}},"minLength": {{minLength}}{{/minLength}}
|
||||||
|
{{#maxLength}},"maxLength": {{maxLength}}{{/maxLength}}
|
||||||
|
{{#min}},"min": {{min}}{{/min}}
|
||||||
|
{{#max}},"max": {{max}}{{/max}}
|
||||||
|
{{#pattern}},"pattern": {{pattern}}{{/pattern}}
|
||||||
|
}{{^-last}},
|
||||||
|
{{/-last}}
|
||||||
|
{{/vars}}
|
||||||
|
];
|
||||||
|
|
||||||
|
static getAttributeTypeMap() {
|
||||||
|
{{#parent}}
|
||||||
|
return super.getAttributeTypeMap().concat({{classname}}.attributeTypeMap);
|
||||||
|
{{/parent}}
|
||||||
|
{{^parent}}
|
||||||
|
return {{classname}}.attributeTypeMap;
|
||||||
|
{{/parent}}
|
||||||
|
}
|
||||||
|
{{/isArray}}
|
||||||
|
|
||||||
|
public constructor(json?: I{{classname}} | any) {
|
||||||
|
{{#parent}}
|
||||||
|
super();
|
||||||
|
{{/parent}}
|
||||||
|
{{#allVars}}
|
||||||
|
{{#discriminatorValue}}
|
||||||
|
this.{{name}} = "{{discriminatorValue}}";
|
||||||
|
{{/discriminatorValue}}
|
||||||
|
{{/allVars}}
|
||||||
|
{{#discriminatorName}}
|
||||||
|
this.{{discriminatorName}} = "{{classname}}";
|
||||||
|
{{/discriminatorName}}
|
||||||
|
this.init(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
{{^isArray}}
|
||||||
|
static fromJson(json?: I{{classname}} | any): {{classname}} {
|
||||||
|
return new {{classname}}().init(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
init(json?: I{{classname}} | any): this {
|
||||||
|
{{#parent}}
|
||||||
|
super.init(json);
|
||||||
|
{{/parent}}
|
||||||
|
{{#vars}}
|
||||||
|
this['{{name}}'] = {{#isDateTime}}(json && (['number', 'string'].includes(typeof json['{{name}}']) || json['{{name}}'] instanceof Date)) ? new Date(json['{{name}}'] as any){{/isDateTime}}{{^isDateTime}}json ? {{#isModel}}{{dataType}}.fromJson(json['{{name}}']){{/isModel}}{{^isModel}}json['{{name}}']{{/isModel}}{{/isDateTime}} : {{defaultValue}} as any;
|
||||||
|
{{/vars}}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson(): any {
|
||||||
|
const ret: any = {};
|
||||||
|
{{#vars}}
|
||||||
|
ret['{{name}}'] = {{#isDateTime}}this.{{name}} instanceof Date ? this.{{name}}.toISOString() : this.{{name}}{{/isDateTime}}{{^isDateTime}}this['{{name}}']{{#isModel}}?.toJson(){{/isModel}}{{/isDateTime}};
|
||||||
|
{{/vars}}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
{{/isArray}}
|
||||||
|
}
|
||||||
|
|
||||||
|
{{#hasEnums}}
|
||||||
|
|
||||||
|
{{#vars}}
|
||||||
|
{{#isEnum}}
|
||||||
|
export type {{classname}}{{enumName}} ={{#allowableValues}}{{#values}} "{{.}}" {{^-last}}|{{/-last}}{{/values}}{{/allowableValues}};
|
||||||
|
{{/isEnum}}
|
||||||
|
{{/vars}}
|
||||||
|
|
||||||
|
{{/hasEnums}}
|
||||||
|
{{/isEnum}}
|
||||||
|
{{#isEnum}}
|
||||||
|
export type {{classname}} ={{#allowableValues}}{{#values}} "{{.}}" {{^-last}}|{{/-last}}{{/values}}{{/allowableValues}};
|
||||||
|
{{/isEnum}}
|
||||||
|
{{/model}}
|
||||||
|
{{/models}}
|
||||||
5
templates/model/models_all.mustache
Normal file
5
templates/model/models_all.mustache
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{{#models}}
|
||||||
|
{{#model}}
|
||||||
|
export * from '{{{ importPath }}}{{extensionForDeno}}'
|
||||||
|
{{/model}}
|
||||||
|
{{/models}}
|
||||||
76
templates/package.mustache
Normal file
76
templates/package.mustache
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
{
|
||||||
|
"name": "{{npmName}}",
|
||||||
|
"version": "{{npmVersion}}",
|
||||||
|
"description": "OpenAPI client for {{npmName}}",
|
||||||
|
"author": "OpenAPI-Generator Contributors",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://{{gitHost}}/{{gitUserId}}/{{gitRepoId}}.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"fetch",
|
||||||
|
"typescript",
|
||||||
|
"openapi-client",
|
||||||
|
"openapi-generator"
|
||||||
|
],
|
||||||
|
"license": "Unlicense",
|
||||||
|
"main": "./dist/index.js",
|
||||||
|
{{#supportsES6}}
|
||||||
|
"type": "module",
|
||||||
|
"module": "./dist/index.js",
|
||||||
|
{{/supportsES6}}
|
||||||
|
{{^supportsES6}}
|
||||||
|
"type": "commonjs",
|
||||||
|
{{/supportsES6}}
|
||||||
|
"exports": {
|
||||||
|
".": "./dist/index.js"
|
||||||
|
},
|
||||||
|
"typings": "./dist/index.d.ts",
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc",
|
||||||
|
"prepare": "npm run build"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
{{#frameworks}}
|
||||||
|
{{#fetch-api}}
|
||||||
|
{{#platforms}}
|
||||||
|
{{#node}}
|
||||||
|
"node-fetch": "^2.6.0",
|
||||||
|
"@types/node-fetch": "^2.5.7",
|
||||||
|
{{/node}}
|
||||||
|
{{#browser}}
|
||||||
|
"whatwg-fetch": "^3.0.0",
|
||||||
|
{{/browser}}
|
||||||
|
{{/platforms}}
|
||||||
|
{{/fetch-api}}
|
||||||
|
{{#jquery}}
|
||||||
|
"@types/jquery": "^3.3.29",
|
||||||
|
"jquery": "^3.4.1",
|
||||||
|
{{/jquery}}
|
||||||
|
{{/frameworks}}
|
||||||
|
{{#platforms}}
|
||||||
|
{{#node}}
|
||||||
|
"@types/node": "*",
|
||||||
|
"form-data": "^2.5.0",
|
||||||
|
"btoa": "^1.2.1",
|
||||||
|
{{/node}}
|
||||||
|
{{/platforms}}
|
||||||
|
{{#useRxJS}}
|
||||||
|
"rxjs": "^6.4.0",
|
||||||
|
{{/useRxJS}}
|
||||||
|
{{#useInversify}}
|
||||||
|
"inversify": "^5.0.1",
|
||||||
|
{{/useInversify}}
|
||||||
|
"es6-promise": "^4.2.4",
|
||||||
|
"url-parse": "^1.4.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"typescript": "^4.0",
|
||||||
|
"@types/url-parse": "1.4.4"
|
||||||
|
}{{#npmRepository}},{{/npmRepository}}
|
||||||
|
{{#npmRepository}}
|
||||||
|
"publishConfig":{
|
||||||
|
"registry":"{{npmRepository}}"
|
||||||
|
}
|
||||||
|
{{/npmRepository}}
|
||||||
|
}
|
||||||
27
templates/rxjsStub.mustache
Normal file
27
templates/rxjsStub.mustache
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
export class Observable<T> {
|
||||||
|
constructor(private promise: Promise<T>) {}
|
||||||
|
|
||||||
|
toPromise() {
|
||||||
|
return this.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
pipe<S>(callback: (value: T) => S | Promise<S>): Observable<S> {
|
||||||
|
return new Observable(this.promise.then(callback));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function from<T>(promise: Promise<any>) {
|
||||||
|
return new Observable(promise);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function of<T>(value: T) {
|
||||||
|
return new Observable<T>(Promise.resolve(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mergeMap<T, S>(callback: (value: T) => Observable<S>) {
|
||||||
|
return (value: T) => callback(value).toPromise();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function map(callback: any) {
|
||||||
|
return callback;
|
||||||
|
}
|
||||||
35
templates/services/ObjectParamAPI.mustache
Normal file
35
templates/services/ObjectParamAPI.mustache
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import type { HttpFile } from '../http/http';
|
||||||
|
import type { Configuration } from '../configuration'
|
||||||
|
import type * as req from "../types/ObjectParamAPI";
|
||||||
|
{{#useRxJS}}
|
||||||
|
import type { Observable } from 'rxjs';
|
||||||
|
{{/useRxJS}}
|
||||||
|
|
||||||
|
{{#models}}
|
||||||
|
{{#model}}
|
||||||
|
import type { {{{ classname }}} } from '{{{ importPath }}}';
|
||||||
|
{{/model}}
|
||||||
|
{{/models}}
|
||||||
|
{{#apiInfo}}
|
||||||
|
{{#apis}}
|
||||||
|
{{#operations}}
|
||||||
|
|
||||||
|
|
||||||
|
export abstract class AbstractObject{{classname}} {
|
||||||
|
{{#operation}}
|
||||||
|
/**
|
||||||
|
{{#notes}}
|
||||||
|
* {{¬es}}
|
||||||
|
{{/notes}}
|
||||||
|
{{#summary}}
|
||||||
|
* {{&summary}}
|
||||||
|
{{/summary}}
|
||||||
|
* @param param the request object
|
||||||
|
*/
|
||||||
|
public abstract {{nickname}}(param: req.{{classname}}{{operationIdCamelCase}}Request, options?: Configuration): {{#useRxJS}}Observable{{/useRxJS}}{{^useRxJS}}Promise{{/useRxJS}}<{{{returnType}}}{{^returnType}}void{{/returnType}}>;
|
||||||
|
|
||||||
|
{{/operation}}
|
||||||
|
}
|
||||||
|
{{/operations}}
|
||||||
|
{{/apis}}
|
||||||
|
{{/apiInfo}}
|
||||||
23
templates/services/ObservableAPI.mustache
Normal file
23
templates/services/ObservableAPI.mustache
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import type { HttpFile } from "../http/http";
|
||||||
|
import type { Observable } from {{#useRxJS}}"rxjs"{{/useRxJS}}{{^useRxJS}}"../rxjsStub"{{/useRxJS}};
|
||||||
|
import type { Configuration } from "../configuration";
|
||||||
|
|
||||||
|
{{#models}}
|
||||||
|
{{#model}}
|
||||||
|
import { {{{ classname }}} } from "{{{ importPath }}}";
|
||||||
|
{{/model}}
|
||||||
|
{{/models}}
|
||||||
|
{{#apiInfo}}
|
||||||
|
{{#apis}}
|
||||||
|
{{#operations}}
|
||||||
|
|
||||||
|
|
||||||
|
export abstract class AbstractObservable{{classname}} {
|
||||||
|
{{#operation}}
|
||||||
|
public abstract {{nickname}}({{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{{dataType}}}, {{/allParams}}options?: Configuration): Observable<{{{returnType}}}{{^returnType}}void{{/returnType}}>;
|
||||||
|
|
||||||
|
{{/operation}}
|
||||||
|
}
|
||||||
|
{{/operations}}
|
||||||
|
{{/apis}}
|
||||||
|
{{/apiInfo}}
|
||||||
22
templates/services/PromiseAPI.mustache
Normal file
22
templates/services/PromiseAPI.mustache
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import type { HttpFile } from "../http/http";
|
||||||
|
import type { Configuration } from "../configuration";
|
||||||
|
|
||||||
|
{{#models}}
|
||||||
|
{{#model}}
|
||||||
|
import { {{{ classname }}} } from "{{{ importPath }}}";
|
||||||
|
{{/model}}
|
||||||
|
{{/models}}
|
||||||
|
{{#apiInfo}}
|
||||||
|
{{#apis}}
|
||||||
|
{{#operations}}
|
||||||
|
|
||||||
|
|
||||||
|
export abstract class AbstractPromise{{classname}} {
|
||||||
|
{{#operation}}
|
||||||
|
public abstract {{nickname}}({{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{{dataType}}}, {{/allParams}}options?: Configuration): Promise<{{{returnType}}}{{^returnType}}void{{/returnType}}>;
|
||||||
|
|
||||||
|
{{/operation}}
|
||||||
|
}
|
||||||
|
{{/operations}}
|
||||||
|
{{/apis}}
|
||||||
|
{{/apiInfo}}
|
||||||
23
templates/services/api.mustache
Normal file
23
templates/services/api.mustache
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import type { Configuration } from "../configuration";
|
||||||
|
import type { HttpFile, RequestContext, ResponseContext } from "../http/http";
|
||||||
|
|
||||||
|
{{#imports}}
|
||||||
|
import { {{classname}} } from "{{filename}}";
|
||||||
|
{{/imports}}
|
||||||
|
{{#operations}}
|
||||||
|
|
||||||
|
export abstract class Abstract{{classname}}RequestFactory {
|
||||||
|
{{#operation}}
|
||||||
|
public abstract {{nickname}}({{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{{dataType}}}, {{/allParams}}options?: Configuration): Promise<RequestContext>;
|
||||||
|
|
||||||
|
{{/operation}}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export abstract class Abstract{{classname}}ResponseProcessor {
|
||||||
|
{{#operation}}
|
||||||
|
public abstract {{nickname}}(response: ResponseContext): Promise<{{{returnType}}} {{^returnType}}void{{/returnType}}>;
|
||||||
|
|
||||||
|
{{/operation}}
|
||||||
|
}
|
||||||
|
{{/operations}}
|
||||||
21
templates/services/configuration.mustache
Normal file
21
templates/services/configuration.mustache
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import type { AbstractServerConfiguration } from "./http";
|
||||||
|
import type { HttpLibrary, RequestContext } from "../http/http";
|
||||||
|
import type { Middleware } from "../middleware";
|
||||||
|
import type { AuthMethods, TokenProvider } from "../auth/auth";
|
||||||
|
import type { Configuration } from "../configuration";
|
||||||
|
|
||||||
|
export abstract class AbstractConfiguration implements Configuration {
|
||||||
|
abstract get baseServer(): AbstractServerConfiguration;
|
||||||
|
abstract get httpApi(): HttpLibrary;
|
||||||
|
abstract get middleware(): Middleware[];
|
||||||
|
abstract get authMethods(): AuthMethods;
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class AbstractAuthMethod {
|
||||||
|
public abstract getName(): string;
|
||||||
|
public abstract applySecurityAuthentication(context: RequestContext): void | Promise<void>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export abstract class AbstractTokenProvider implements TokenProvider {
|
||||||
|
public abstract getToken(): string | Promise<string>;
|
||||||
|
}
|
||||||
19
templates/services/http.mustache
Normal file
19
templates/services/http.mustache
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{{#useRxJS}}
|
||||||
|
import type { Observable } from "rxjs";
|
||||||
|
{{/useRxJS}}
|
||||||
|
import type { {{^useRxJS}}Promise{{/useRxJS}}HttpLibrary, HttpMethod, RequestContext, ResponseContext } from "../http/http";
|
||||||
|
import type { {{^useRxJS}}Promise{{/useRxJS}}Middleware } from "../middleware";
|
||||||
|
import type { BaseServerConfiguration } from "../servers";
|
||||||
|
|
||||||
|
export abstract class AbstractHttpLibrary implements {{^useRxJS}}Promise{{/useRxJS}}HttpLibrary {
|
||||||
|
public abstract send(request: RequestContext): {{#useRxJS}}Observable{{/useRxJS}}{{^useRxJS}}Promise{{/useRxJS}}<ResponseContext>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export abstract class AbstractMiddleware implements {{^useRxJS}}Promise{{/useRxJS}}Middleware {
|
||||||
|
public abstract pre(context: RequestContext): {{#useRxJS}}Observable{{/useRxJS}}{{^useRxJS}}Promise{{/useRxJS}}<RequestContext>;
|
||||||
|
public abstract post(context: ResponseContext): {{#useRxJS}}Observable{{/useRxJS}}{{^useRxJS}}Promise{{/useRxJS}}<ResponseContext>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class AbstractServerConfiguration implements BaseServerConfiguration {
|
||||||
|
public abstract makeRequestContext(endpoint: string, httpMethod: HttpMethod): RequestContext;
|
||||||
|
};
|
||||||
165
templates/services/index.mustache
Normal file
165
templates/services/index.mustache
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
import { inject, injectable, multiInject, optional, interfaces } from "inversify";
|
||||||
|
|
||||||
|
import { Configuration } from "../configuration";
|
||||||
|
import { ServerConfiguration, servers } from "../servers";
|
||||||
|
import { HttpLibrary{{^useRxJS}}, wrapHttpLibrary{{/useRxJS}} } from "../http/http";
|
||||||
|
import { Middleware{{^useRxJS}}, PromiseMiddlewareWrapper{{/useRxJS}} } from "../middleware";
|
||||||
|
import { authMethodServices, AuthMethods } from "../auth/auth";
|
||||||
|
|
||||||
|
{{#frameworks}}
|
||||||
|
{{#fetch-api}}
|
||||||
|
import { IsomorphicFetchHttpLibrary as DefaultHttpLibrary } from "../http/isomorphic-fetch";
|
||||||
|
{{/fetch-api}}
|
||||||
|
{{#jquery}}
|
||||||
|
import { JQueryHttpLibrary as DefaultHttpLibrary } from "../http/jquery";
|
||||||
|
{{/jquery}}
|
||||||
|
{{/frameworks}}
|
||||||
|
|
||||||
|
import { AbstractHttpLibrary, AbstractMiddleware, AbstractServerConfiguration } from "./http";
|
||||||
|
import { AbstractConfiguration, AbstractAuthMethod, AbstractTokenProvider } from "./configuration";
|
||||||
|
|
||||||
|
export { AbstractHttpLibrary, AbstractMiddleware, AbstractServerConfiguration, AbstractConfiguration, AbstractAuthMethod, AbstractTokenProvider };
|
||||||
|
|
||||||
|
{{#useObjectParameters}}
|
||||||
|
import * as apis from "../types/ObjectParamAPI";
|
||||||
|
import * as apiServices from "./ObjectParamAPI";
|
||||||
|
{{/useObjectParameters}}
|
||||||
|
{{^useObjectParameters}}
|
||||||
|
{{#useRxJS}}
|
||||||
|
import * as apis from "../types/ObservableAPI";
|
||||||
|
import * as apiServices from "./ObservableAPI";
|
||||||
|
{{/useRxJS}}
|
||||||
|
{{^useRxJS}}
|
||||||
|
import * as apis from "../types/PromiseAPI";
|
||||||
|
import * as apiServices from "./PromiseAPI";
|
||||||
|
{{/useRxJS}}
|
||||||
|
{{/useObjectParameters}}
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
class InjectableConfiguration implements AbstractConfiguration {
|
||||||
|
public httpApi: HttpLibrary = new DefaultHttpLibrary();
|
||||||
|
public middleware: Middleware[] = [];
|
||||||
|
public authMethods: AuthMethods = {};
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@inject(AbstractServerConfiguration) @optional() public baseServer: AbstractServerConfiguration = servers[0],
|
||||||
|
@inject(AbstractHttpLibrary) @optional() httpApi: AbstractHttpLibrary,
|
||||||
|
@multiInject(AbstractMiddleware) @optional() middleware: AbstractMiddleware[] = [],
|
||||||
|
@multiInject(AbstractAuthMethod) @optional() securityConfiguration: AbstractAuthMethod[] = []
|
||||||
|
) {
|
||||||
|
{{#useRxJS}}
|
||||||
|
this.httpApi = httpApi || new DefaultHttpLibrary();
|
||||||
|
this.middleware = middleware;
|
||||||
|
{{/useRxJS}}
|
||||||
|
{{^useRxJS}}
|
||||||
|
this.httpApi = httpApi === undefined ? new DefaultHttpLibrary() : wrapHttpLibrary(httpApi);
|
||||||
|
for (const _middleware of middleware) {
|
||||||
|
this.middleware.push(new PromiseMiddlewareWrapper(_middleware));
|
||||||
|
}
|
||||||
|
{{/useRxJS}}
|
||||||
|
for (const authMethod of securityConfiguration) {
|
||||||
|
const authName = authMethod.getName();
|
||||||
|
// @ts-ignore
|
||||||
|
if (authMethodServices[authName] !== undefined) {
|
||||||
|
// @ts-ignore
|
||||||
|
this.authMethods[authName] = authMethod;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class to simplify binding the services
|
||||||
|
*/
|
||||||
|
export class ApiServiceBinder {
|
||||||
|
constructor(private container: interfaces.Container) {
|
||||||
|
this.container.bind(AbstractConfiguration).to(InjectableConfiguration);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows you to bind a server configuration without having to import the service identifier.
|
||||||
|
*/
|
||||||
|
public get bindServerConfiguration() {
|
||||||
|
return this.container.bind(AbstractServerConfiguration);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use one of the predefined server configurations.
|
||||||
|
*
|
||||||
|
* To customize the server variables you can call `setVariables` on the
|
||||||
|
* return value;
|
||||||
|
*/
|
||||||
|
public bindServerConfigurationToPredefined(idx: number) {
|
||||||
|
this.bindServerConfiguration.toConstantValue(servers[idx]);
|
||||||
|
return servers[idx];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Explicitly define the service base url
|
||||||
|
*/
|
||||||
|
public bindServerConfigurationToURL(url: string) {
|
||||||
|
return this.bindServerConfiguration.toConstantValue(
|
||||||
|
new ServerConfiguration<{}>(url, {})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows you to bind a http library without having to import the service identifier.
|
||||||
|
*/
|
||||||
|
public get bindHttpLibrary() {
|
||||||
|
return this.container.bind(AbstractHttpLibrary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows you to bind a middleware without having to import the service identifier.
|
||||||
|
*
|
||||||
|
* You can bind multiple middlewares by calling this multiple method times.
|
||||||
|
*/
|
||||||
|
public get bindMiddleware() {
|
||||||
|
return this.container.bind(AbstractMiddleware);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows you to bind an auth method without having to import the service identifier.
|
||||||
|
*
|
||||||
|
* Note: The name of the bound auth method needs to be known in the specs,
|
||||||
|
* because the name is used to decide for which endpoints to apply the authentication.
|
||||||
|
*/
|
||||||
|
public get bindAuthMethod() {
|
||||||
|
return this.container.bind(AbstractAuthMethod);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use one of the predefined auth methods.
|
||||||
|
*
|
||||||
|
* Make sure that you have injected all dependencies for it.
|
||||||
|
*/
|
||||||
|
public bindAuthMethodToPredefined(name: keyof AuthMethods) {
|
||||||
|
return this.bindAuthMethod.to(authMethodServices[name]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bind all the apis to their respective service identifiers
|
||||||
|
*
|
||||||
|
* If you want to only bind some of the apis, you need to do that manually.
|
||||||
|
*/
|
||||||
|
public bindAllApiServices() {
|
||||||
|
{{#apiInfo}}
|
||||||
|
{{#apis}}
|
||||||
|
{{#operations}}
|
||||||
|
{{#useObjectParameters}}
|
||||||
|
this.container.bind(apiServices.AbstractObject{{classname}}).to(apis.Object{{classname}}).inSingletonScope();
|
||||||
|
{{/useObjectParameters}}
|
||||||
|
{{^useObjectParameters}}
|
||||||
|
{{#useRxJS}}
|
||||||
|
this.container.bind(apiServices.AbstractObservable{{classname}}).to(apis.Observable{{classname}}).inSingletonScope();
|
||||||
|
{{/useRxJS}}
|
||||||
|
{{^useRxJS}}
|
||||||
|
this.container.bind(apiServices.AbstractPromise{{classname}}).to(apis.Promise{{classname}}).inSingletonScope();
|
||||||
|
{{/useRxJS}}
|
||||||
|
{{/useObjectParameters}}
|
||||||
|
{{/operations}}
|
||||||
|
{{/apis}}
|
||||||
|
{{/apiInfo}}
|
||||||
|
}
|
||||||
|
}
|
||||||
44
templates/tsconfig.mustache
Normal file
44
templates/tsconfig.mustache
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"strict": true,
|
||||||
|
/* Basic Options */
|
||||||
|
{{#supportsES6}}
|
||||||
|
"target": "es6",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
{{/supportsES6}}
|
||||||
|
{{^supportsES6}}
|
||||||
|
"target": "es5",
|
||||||
|
{{/supportsES6}}
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"declaration": true,
|
||||||
|
|
||||||
|
/* Additional Checks */
|
||||||
|
"noUnusedLocals": false, /* Report errors on unused locals. */ // TODO: reenable (unused imports!)
|
||||||
|
"noUnusedParameters": false, /* Report errors on unused parameters. */ // TODO: set to true again
|
||||||
|
"noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||||
|
"noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||||
|
|
||||||
|
"removeComments": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"outDir": "./dist",
|
||||||
|
"noLib": false,
|
||||||
|
{{#platforms}}
|
||||||
|
{{#node}}
|
||||||
|
"lib": [ "es2016" ],
|
||||||
|
{{/node}}
|
||||||
|
{{#browser}}
|
||||||
|
"lib": [ "es2016", "dom" ],
|
||||||
|
{{/browser}}
|
||||||
|
{{/platforms}}
|
||||||
|
{{#useInversify}}
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
{{/useInversify}}
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"dist",
|
||||||
|
"node_modules"
|
||||||
|
],
|
||||||
|
"filesGlob": [
|
||||||
|
"./**/*.ts",
|
||||||
|
]
|
||||||
|
}
|
||||||
57
templates/types/ObjectParamAPI.mustache
Normal file
57
templates/types/ObjectParamAPI.mustache
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import { ResponseContext, RequestContext, HttpFile } from '../http/http{{extensionForDeno}}';
|
||||||
|
import { Configuration} from '../configuration{{extensionForDeno}}'
|
||||||
|
{{#useRxJS}}
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
{{/useRxJS}}
|
||||||
|
|
||||||
|
{{#models}}
|
||||||
|
{{#model}}
|
||||||
|
import { {{{ classname }}} } from '{{{ importPath }}}{{extensionForDeno}}';
|
||||||
|
{{/model}}
|
||||||
|
{{/models}}
|
||||||
|
{{#apiInfo}}
|
||||||
|
{{#apis}}
|
||||||
|
|
||||||
|
{{#operations}}
|
||||||
|
import { Observable{{classname}} } from "./ObservableAPI{{extensionForDeno}}";
|
||||||
|
import { {{classname}}RequestFactory, {{classname}}ResponseProcessor} from "../apis/{{classname}}{{extensionForDeno}}";
|
||||||
|
|
||||||
|
{{#operation}}
|
||||||
|
export interface {{classname}}{{operationIdCamelCase}}Request {
|
||||||
|
{{#allParams}}
|
||||||
|
/**
|
||||||
|
* {{description}}
|
||||||
|
* @type {{dataType}}
|
||||||
|
* @memberof {{classname}}{{nickname}}
|
||||||
|
*/
|
||||||
|
{{paramName}}{{^required}}?{{/required}}: {{{dataType}}}
|
||||||
|
{{/allParams}}
|
||||||
|
}
|
||||||
|
|
||||||
|
{{/operation}}
|
||||||
|
export class Object{{classname}} {
|
||||||
|
private api: Observable{{classname}}
|
||||||
|
|
||||||
|
public constructor(configuration: Configuration, requestFactory?: {{classname}}RequestFactory, responseProcessor?: {{classname}}ResponseProcessor) {
|
||||||
|
this.api = new Observable{{classname}}(configuration, requestFactory, responseProcessor);
|
||||||
|
}
|
||||||
|
|
||||||
|
{{#operation}}
|
||||||
|
/**
|
||||||
|
{{#notes}}
|
||||||
|
* {{¬es}}
|
||||||
|
{{/notes}}
|
||||||
|
{{#summary}}
|
||||||
|
* {{&summary}}
|
||||||
|
{{/summary}}
|
||||||
|
* @param param the request object
|
||||||
|
*/
|
||||||
|
public {{nickname}}(param: {{classname}}{{operationIdCamelCase}}Request{{^hasRequiredParams}} = {}{{/hasRequiredParams}}, options?: Configuration): {{#useRxJS}}Observable{{/useRxJS}}{{^useRxJS}}Promise{{/useRxJS}}<{{{returnType}}}{{^returnType}}void{{/returnType}}> {
|
||||||
|
return this.api.{{nickname}}({{#allParams}}param.{{paramName}}, {{/allParams}} options){{^useRxJS}}.toPromise(){{/useRxJS}};
|
||||||
|
}
|
||||||
|
|
||||||
|
{{/operation}}
|
||||||
|
}
|
||||||
|
{{/operations}}
|
||||||
|
{{/apis}}
|
||||||
|
{{/apiInfo}}
|
||||||
87
templates/types/ObservableAPI.mustache
Normal file
87
templates/types/ObservableAPI.mustache
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import { ResponseContext, RequestContext, HttpFile } from '../http/http{{extensionForDeno}}';
|
||||||
|
import { Configuration} from '../configuration{{extensionForDeno}}'
|
||||||
|
import { Observable, of, from } from {{#useRxJS}}'rxjs'{{/useRxJS}}{{^useRxJS}}'../rxjsStub{{extensionForDeno}}'{{/useRxJS}};
|
||||||
|
import {mergeMap, map} from {{#useRxJS}}'rxjs/operators'{{/useRxJS}}{{^useRxJS}}'../rxjsStub{{extensionForDeno}}'{{/useRxJS}};
|
||||||
|
{{#useInversify}}
|
||||||
|
import { injectable, inject, optional } from "inversify";
|
||||||
|
import { AbstractConfiguration } from "../services/configuration{{extensionForDeno}}";
|
||||||
|
{{/useInversify}}
|
||||||
|
{{#models}}
|
||||||
|
{{#model}}
|
||||||
|
import { {{{ classname }}} } from '{{{ importPath }}}{{extensionForDeno}}';
|
||||||
|
{{/model}}
|
||||||
|
{{/models}}
|
||||||
|
{{#apiInfo}}
|
||||||
|
{{#apis}}
|
||||||
|
|
||||||
|
{{#operations}}
|
||||||
|
import { {{classname}}RequestFactory, {{classname}}ResponseProcessor} from "../apis/{{classname}}{{extensionForDeno}}";
|
||||||
|
{{#useInversify}}
|
||||||
|
import { Abstract{{classname}}RequestFactory, Abstract{{classname}}ResponseProcessor } from "../apis/{{classname}}.service{{extensionForDeno}}";
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
{{/useInversify}}
|
||||||
|
export class Observable{{classname}} {
|
||||||
|
{{#useInversify}}
|
||||||
|
private requestFactory: Abstract{{classname}}RequestFactory;
|
||||||
|
private responseProcessor: Abstract{{classname}}ResponseProcessor;
|
||||||
|
{{/useInversify}}
|
||||||
|
{{^useInversify}}
|
||||||
|
private requestFactory: {{classname}}RequestFactory;
|
||||||
|
private responseProcessor: {{classname}}ResponseProcessor;
|
||||||
|
{{/useInversify}}
|
||||||
|
private configuration: Configuration;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
{{#useInversify}}
|
||||||
|
@inject(AbstractConfiguration) configuration: Configuration,
|
||||||
|
@inject(Abstract{{classname}}RequestFactory) @optional() requestFactory?: Abstract{{classname}}RequestFactory,
|
||||||
|
@inject(Abstract{{classname}}ResponseProcessor) @optional() responseProcessor?: Abstract{{classname}}ResponseProcessor
|
||||||
|
{{/useInversify}}
|
||||||
|
{{^useInversify}}
|
||||||
|
configuration: Configuration,
|
||||||
|
requestFactory?: {{classname}}RequestFactory,
|
||||||
|
responseProcessor?: {{classname}}ResponseProcessor
|
||||||
|
{{/useInversify}}
|
||||||
|
) {
|
||||||
|
this.configuration = configuration;
|
||||||
|
this.requestFactory = requestFactory || new {{classname}}RequestFactory(configuration);
|
||||||
|
this.responseProcessor = responseProcessor || new {{classname}}ResponseProcessor();
|
||||||
|
}
|
||||||
|
|
||||||
|
{{#operation}}
|
||||||
|
/**
|
||||||
|
{{#notes}}
|
||||||
|
* {{¬es}}
|
||||||
|
{{/notes}}
|
||||||
|
{{#summary}}
|
||||||
|
* {{&summary}}
|
||||||
|
{{/summary}}
|
||||||
|
{{#allParams}}
|
||||||
|
* @param {{paramName}} {{description}}
|
||||||
|
{{/allParams}}
|
||||||
|
*/
|
||||||
|
public {{nickname}}({{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{{dataType}}}, {{/allParams}}_options?: Configuration): Observable<{{{returnType}}}{{^returnType}}void{{/returnType}}> {
|
||||||
|
const requestContextPromise = this.requestFactory.{{nickname}}({{#allParams}}{{paramName}}, {{/allParams}}_options);
|
||||||
|
|
||||||
|
// build promise chain
|
||||||
|
let middlewarePreObservable = from<RequestContext>(requestContextPromise);
|
||||||
|
for (let middleware of this.configuration.middleware) {
|
||||||
|
middlewarePreObservable = middlewarePreObservable.pipe(mergeMap((ctx: RequestContext) => middleware.pre(ctx)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return middlewarePreObservable.pipe(mergeMap((ctx: RequestContext) => this.configuration.httpApi.send(ctx))).
|
||||||
|
pipe(mergeMap((response: ResponseContext) => {
|
||||||
|
let middlewarePostObservable = of(response);
|
||||||
|
for (let middleware of this.configuration.middleware) {
|
||||||
|
middlewarePostObservable = middlewarePostObservable.pipe(mergeMap((rsp: ResponseContext) => middleware.post(rsp)));
|
||||||
|
}
|
||||||
|
return middlewarePostObservable.pipe(map((rsp: ResponseContext) => this.responseProcessor.{{nickname}}(rsp)));
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
{{/operation}}
|
||||||
|
}
|
||||||
|
{{/operations}}
|
||||||
|
{{/apis}}
|
||||||
|
{{/apiInfo}}
|
||||||
67
templates/types/PromiseAPI.mustache
Normal file
67
templates/types/PromiseAPI.mustache
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import { ResponseContext, RequestContext, HttpFile } from '../http/http{{extensionForDeno}}';
|
||||||
|
import { Configuration} from '../configuration{{extensionForDeno}}'
|
||||||
|
{{#useInversify}}
|
||||||
|
import { injectable, inject, optional } from "inversify";
|
||||||
|
import { AbstractConfiguration } from "../services/configuration";
|
||||||
|
{{/useInversify}}
|
||||||
|
|
||||||
|
{{#models}}
|
||||||
|
{{#model}}
|
||||||
|
import { {{{ classname }}} } from '{{{ importPath }}}{{extensionForDeno}}';
|
||||||
|
{{/model}}
|
||||||
|
{{/models}}
|
||||||
|
{{#apiInfo}}
|
||||||
|
{{#apis}}
|
||||||
|
import { Observable{{classname}} } from './ObservableAPI{{extensionForDeno}}';
|
||||||
|
|
||||||
|
{{#operations}}
|
||||||
|
import { {{classname}}RequestFactory, {{classname}}ResponseProcessor} from "../apis/{{classname}}{{extensionForDeno}}";
|
||||||
|
{{#useInversify}}
|
||||||
|
import { Abstract{{classname}}RequestFactory, Abstract{{classname}}ResponseProcessor } from "../apis/{{classname}}.service";
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
{{/useInversify}}
|
||||||
|
export class Promise{{classname}} {
|
||||||
|
private api: Observable{{classname}}
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
{{#useInversify}}
|
||||||
|
@inject(AbstractConfiguration) configuration: Configuration,
|
||||||
|
@inject(Abstract{{classname}}RequestFactory) @optional() requestFactory?: Abstract{{classname}}RequestFactory,
|
||||||
|
@inject(Abstract{{classname}}ResponseProcessor) @optional() responseProcessor?: Abstract{{classname}}ResponseProcessor
|
||||||
|
{{/useInversify}}
|
||||||
|
{{^useInversify}}
|
||||||
|
configuration: Configuration,
|
||||||
|
requestFactory?: {{classname}}RequestFactory,
|
||||||
|
responseProcessor?: {{classname}}ResponseProcessor
|
||||||
|
{{/useInversify}}
|
||||||
|
) {
|
||||||
|
this.api = new Observable{{classname}}(configuration, requestFactory, responseProcessor);
|
||||||
|
}
|
||||||
|
|
||||||
|
{{#operation}}
|
||||||
|
/**
|
||||||
|
{{#notes}}
|
||||||
|
* {{¬es}}
|
||||||
|
{{/notes}}
|
||||||
|
{{#summary}}
|
||||||
|
* {{&summary}}
|
||||||
|
{{/summary}}
|
||||||
|
{{#allParams}}
|
||||||
|
* @param {{paramName}} {{description}}
|
||||||
|
{{/allParams}}
|
||||||
|
*/
|
||||||
|
public {{nickname}}({{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{{dataType}}}, {{/allParams}}_options?: Configuration): Promise<{{{returnType}}}{{^returnType}}void{{/returnType}}> {
|
||||||
|
const result = this.api.{{nickname}}({{#allParams}}{{paramName}}, {{/allParams}}_options);
|
||||||
|
return result.toPromise();
|
||||||
|
}
|
||||||
|
|
||||||
|
{{/operation}}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
{{/operations}}
|
||||||
|
|
||||||
|
|
||||||
|
{{/apis}}
|
||||||
|
{{/apiInfo}}
|
||||||
37
templates/util.mustache
Normal file
37
templates/util.mustache
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* Returns if a specific http code is in a given code range
|
||||||
|
* where the code range is defined as a combination of digits
|
||||||
|
* and "X" (the letter X) with a length of 3
|
||||||
|
*
|
||||||
|
* @param codeRange string with length 3 consisting of digits and "X" (the letter X)
|
||||||
|
* @param code the http status code to be checked against the code range
|
||||||
|
*/
|
||||||
|
export function isCodeInRange(codeRange: string, code: number): boolean {
|
||||||
|
// This is how the default value is encoded in OAG
|
||||||
|
if (codeRange === "0") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (codeRange == code.toString()) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
const codeString = code.toString();
|
||||||
|
if (codeString.length != codeRange.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (let i = 0; i < codeString.length; i++) {
|
||||||
|
if (codeRange.charAt(i) != "X" && codeRange.charAt(i) != codeString.charAt(i)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns if it can consume form
|
||||||
|
*
|
||||||
|
* @param consumes array
|
||||||
|
*/
|
||||||
|
export function canConsumeForm(contentTypes: string[]): boolean {
|
||||||
|
return contentTypes.indexOf('multipart/form-data') !== -1
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user