oip-common 0.0.2 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ng-package.json +12 -1
- package/package.json +19 -16
- package/src/api/http-client.ts +26 -26
- package/src/components/db-migration/db-migration.component.ts +3 -1
- package/src/components/db-migration.component.ts +11 -10
- package/src/components/menu/menu-item-create-dialog.component.ts +7 -7
- package/src/components/menu/menu-item-edit-dialog.component.ts +7 -6
- package/src/components/profile.component.ts +4 -3
- package/src/helpers/date.helper.ts +2 -2
- package/src/user-api/http-client.ts +27 -25
- package/templates/api.ejs +30 -0
- package/templates/data-contract-jsdoc.ejs +37 -0
- package/templates/data-contracts.ejs +52 -0
- package/templates/enum-data-contract.ejs +12 -0
- package/templates/http-client.ejs +245 -0
- package/templates/interface-data-contract.ejs +10 -0
- package/templates/object-field-jsdoc.ejs +28 -0
- package/templates/procedure-call.ejs +100 -0
- package/templates/route-docs.ejs +29 -0
- package/templates/route-name.ejs +42 -0
- package/templates/route-type.ejs +23 -0
- package/templates/route-types.ejs +18 -0
- package/templates/type-data-contract.ejs +15 -0
package/ng-package.json
CHANGED
|
@@ -4,5 +4,16 @@
|
|
|
4
4
|
"lib": {
|
|
5
5
|
"entryFile": "src/public-api.ts"
|
|
6
6
|
},
|
|
7
|
-
"assets": [
|
|
7
|
+
"assets": [
|
|
8
|
+
{
|
|
9
|
+
"input": "./src/assets",
|
|
10
|
+
"glob": "**/*",
|
|
11
|
+
"output": "assets"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"input": "./templates",
|
|
15
|
+
"glob": "**/*",
|
|
16
|
+
"output": "templates"
|
|
17
|
+
}
|
|
18
|
+
]
|
|
8
19
|
}
|
package/package.json
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "oip-common",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "A template for cross-platform web applications based on sakai-ng and primeNG",
|
|
5
|
-
"main": "index.js",
|
|
6
|
-
"scripts": {
|
|
7
|
-
"pub": "npm publish"
|
|
8
|
-
},
|
|
9
|
-
"keywords": [
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "oip-common",
|
|
3
|
+
"version": "0.0.4",
|
|
4
|
+
"description": "A template for cross-platform web applications based on sakai-ng and primeNG",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"pub": "npm publish"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [
|
|
10
|
+
"oip",
|
|
11
|
+
"template"
|
|
12
|
+
],
|
|
13
|
+
"author": "Igor Tyulyakov aka g101k",
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/g10101k/Oip.git"
|
|
18
|
+
}
|
|
19
|
+
}
|
package/src/api/http-client.ts
CHANGED
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { inject, Injectable } from "@angular/core";
|
|
14
|
-
import { LayoutService,
|
|
15
|
-
|
|
14
|
+
import { LayoutService, } from "../services/app.layout.service"
|
|
15
|
+
import { SecurityService } from "../services/security.service";
|
|
16
16
|
export type QueryParamsType = Record<string | number, any>;
|
|
17
17
|
export type ResponseFormat = keyof Omit<Body, "body" | "bodyUsed">;
|
|
18
18
|
|
|
@@ -64,7 +64,7 @@ export enum ContentType {
|
|
|
64
64
|
Text = "text/plain",
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
@Injectable({
|
|
67
|
+
@Injectable({providedIn: "root"})
|
|
68
68
|
export class HttpClient<SecurityDataType = unknown> {
|
|
69
69
|
protected securityService = inject(SecurityService);
|
|
70
70
|
protected layoutService = inject(LayoutService);
|
|
@@ -202,16 +202,16 @@ export class HttpClient<SecurityDataType = unknown> {
|
|
|
202
202
|
};
|
|
203
203
|
|
|
204
204
|
public request = async <T = any, E = any>({
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
205
|
+
body,
|
|
206
|
+
secure,
|
|
207
|
+
path,
|
|
208
|
+
type,
|
|
209
|
+
query,
|
|
210
|
+
format,
|
|
211
|
+
baseUrl,
|
|
212
|
+
cancelToken,
|
|
213
|
+
...params
|
|
214
|
+
}: FullRequestParams): Promise<T> => {
|
|
215
215
|
const secureParams =
|
|
216
216
|
((typeof secure === "boolean" ? secure : this.baseApiParams.secure) &&
|
|
217
217
|
this.securityWorker &&
|
|
@@ -229,7 +229,7 @@ export class HttpClient<SecurityDataType = unknown> {
|
|
|
229
229
|
headers: {
|
|
230
230
|
...(requestParams.headers || {}),
|
|
231
231
|
...(type && type !== ContentType.FormData
|
|
232
|
-
? {
|
|
232
|
+
? {"Content-Type": type}
|
|
233
233
|
: {}),
|
|
234
234
|
},
|
|
235
235
|
signal:
|
|
@@ -252,18 +252,18 @@ export class HttpClient<SecurityDataType = unknown> {
|
|
|
252
252
|
const data = !responseFormat
|
|
253
253
|
? r
|
|
254
254
|
: await response[responseFormat]()
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
255
|
+
.then((data) => {
|
|
256
|
+
if (r.ok) {
|
|
257
|
+
r.data = data;
|
|
258
|
+
} else {
|
|
259
|
+
r.error = data;
|
|
260
|
+
}
|
|
261
|
+
return r;
|
|
262
|
+
})
|
|
263
|
+
.catch((e) => {
|
|
264
|
+
r.error = e;
|
|
265
|
+
return r;
|
|
266
|
+
});
|
|
267
267
|
|
|
268
268
|
if (cancelToken) {
|
|
269
269
|
this.abortControllers.delete(cancelToken);
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { Component, inject, OnDestroy, OnInit } from '@angular/core';
|
|
2
|
-
import { BaseModuleComponent, NoSettingsDto, SecurityComponent } from 'oip-common';
|
|
3
2
|
import { TagModule } from 'primeng/tag';
|
|
4
3
|
import { ConfirmationService, SharedModule } from 'primeng/api';
|
|
5
4
|
import { TableModule } from 'primeng/table';
|
|
@@ -11,6 +10,9 @@ import { ConfirmDialog } from 'primeng/confirmdialog';
|
|
|
11
10
|
import { NgIf } from '@angular/common';
|
|
12
11
|
import { Tooltip } from 'primeng/tooltip';
|
|
13
12
|
import { ActivatedRoute } from '@angular/router';
|
|
13
|
+
import { BaseModuleComponent } from "../base-module.component";
|
|
14
|
+
import { NoSettingsDto } from "../../dtos/no-settings.dto";
|
|
15
|
+
import { SecurityComponent } from "../security.component";
|
|
14
16
|
|
|
15
17
|
export interface MigrationDto {
|
|
16
18
|
name: string;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { Component, OnDestroy, OnInit } from '@angular/core';
|
|
2
|
-
import { BaseModuleComponent, NoSettingsDto, SecurityComponent } from 'oip-common';
|
|
3
2
|
import { TagModule } from 'primeng/tag';
|
|
4
3
|
import { ConfirmationService, SharedModule } from 'primeng/api';
|
|
5
4
|
import { TableModule } from 'primeng/table';
|
|
@@ -10,6 +9,9 @@ import { FormsModule } from '@angular/forms';
|
|
|
10
9
|
import { ConfirmDialog } from 'primeng/confirmdialog';
|
|
11
10
|
import { NgIf } from '@angular/common';
|
|
12
11
|
import { Tooltip } from 'primeng/tooltip';
|
|
12
|
+
import { BaseModuleComponent } from "./base-module.component";
|
|
13
|
+
import { NoSettingsDto } from "../dtos/no-settings.dto";
|
|
14
|
+
import { SecurityComponent } from "./security.component";
|
|
13
15
|
|
|
14
16
|
export interface MigrationDto {
|
|
15
17
|
name: string;
|
|
@@ -39,7 +41,7 @@ export interface ApplyMigrationRequest {
|
|
|
39
41
|
selector: 'db-migration',
|
|
40
42
|
template: `
|
|
41
43
|
<div *ngIf="isContent" class="card" style="height: 100%">
|
|
42
|
-
<p-confirmDialog
|
|
44
|
+
<p-confirmDialog/>
|
|
43
45
|
<div>
|
|
44
46
|
<h5>Migration manager</h5>
|
|
45
47
|
<div class="flex flex-row gap-2">
|
|
@@ -49,14 +51,14 @@ export interface ApplyMigrationRequest {
|
|
|
49
51
|
severity="secondary"
|
|
50
52
|
tooltipPosition="bottom"
|
|
51
53
|
[outlined]="true"
|
|
52
|
-
(click)="refreshAction()"
|
|
54
|
+
(click)="refreshAction()"/>
|
|
53
55
|
<p-button
|
|
54
56
|
icon="pi pi-filter-slash"
|
|
55
57
|
pTooltip="Clean filter"
|
|
56
58
|
severity="secondary"
|
|
57
59
|
tooltipPosition="bottom"
|
|
58
60
|
[outlined]="true"
|
|
59
|
-
(click)="dt.clear()"
|
|
61
|
+
(click)="dt.clear()"/>
|
|
60
62
|
</div>
|
|
61
63
|
<div>
|
|
62
64
|
<p-table #dt dataKey="name" editMode="row" size="small" [scrollable]="true" [value]="data">
|
|
@@ -64,7 +66,7 @@ export interface ApplyMigrationRequest {
|
|
|
64
66
|
<tr>
|
|
65
67
|
<th pSortableColumn="name" scope="col">
|
|
66
68
|
Migration name
|
|
67
|
-
<p-columnFilter display="menu" field="name" type="text"
|
|
69
|
+
<p-columnFilter display="menu" field="name" type="text"/>
|
|
68
70
|
</th>
|
|
69
71
|
<th scope="col">Applied</th>
|
|
70
72
|
<th scope="col">Exist</th>
|
|
@@ -89,7 +91,7 @@ export interface ApplyMigrationRequest {
|
|
|
89
91
|
</td>
|
|
90
92
|
<td>
|
|
91
93
|
@if (rowData.exist) {
|
|
92
|
-
<p-button icon="pi pi-check" severity="success" [rounded]="true" [text]="true"
|
|
94
|
+
<p-button icon="pi pi-check" severity="success" [rounded]="true" [text]="true"/>
|
|
93
95
|
}
|
|
94
96
|
</td>
|
|
95
97
|
<td>
|
|
@@ -116,15 +118,14 @@ export interface ApplyMigrationRequest {
|
|
|
116
118
|
</div>
|
|
117
119
|
</div>
|
|
118
120
|
@if (isSecurity) {
|
|
119
|
-
<security [controller]="controller" [id]="id"
|
|
121
|
+
<security [controller]="controller" [id]="id"/>
|
|
120
122
|
}
|
|
121
123
|
`,
|
|
122
124
|
providers: [ConfirmationService]
|
|
123
125
|
})
|
|
124
126
|
export class DbMigrationComponent
|
|
125
127
|
extends BaseModuleComponent<NoSettingsDto, NoSettingsDto>
|
|
126
|
-
implements OnInit, OnDestroy
|
|
127
|
-
{
|
|
128
|
+
implements OnInit, OnDestroy {
|
|
128
129
|
data: MigrationDto[];
|
|
129
130
|
|
|
130
131
|
async ngOnInit() {
|
|
@@ -148,7 +149,7 @@ export class DbMigrationComponent
|
|
|
148
149
|
}
|
|
149
150
|
|
|
150
151
|
async applyMigration(rowData: MigrationDto) {
|
|
151
|
-
const request = {
|
|
152
|
+
const request = {name: rowData.name} as ApplyMigrationRequest;
|
|
152
153
|
return this.baseDataService.sendRequest(`api/${this.controller}/apply-migration`, 'POST', request);
|
|
153
154
|
}
|
|
154
155
|
}
|
|
@@ -4,10 +4,10 @@ import { DialogModule } from 'primeng/dialog';
|
|
|
4
4
|
import { InputTextModule } from 'primeng/inputtext';
|
|
5
5
|
import { SelectModule } from 'primeng/select';
|
|
6
6
|
import { FormsModule } from '@angular/forms';
|
|
7
|
-
import { MenuService } from 'oip-common';
|
|
8
7
|
import { TranslatePipe } from '@ngx-translate/core';
|
|
9
8
|
import { AddModuleInstanceDto, IntKeyValueDto } from '../../api/data-contracts';
|
|
10
9
|
import { Menu } from '../../api/Menu';
|
|
10
|
+
import { MenuService } from "../../services/app.menu.service";
|
|
11
11
|
|
|
12
12
|
@Component({
|
|
13
13
|
selector: 'menu-item-create-dialog',
|
|
@@ -30,14 +30,14 @@ import { Menu } from '../../api/Menu';
|
|
|
30
30
|
id="oip-menu-item-create-dialog-parent-input"
|
|
31
31
|
pInputText
|
|
32
32
|
readonly
|
|
33
|
-
[ngModel]="menuService.contextMenuItem?.label"
|
|
33
|
+
[ngModel]="menuService.contextMenuItem?.label"/>
|
|
34
34
|
</div>
|
|
35
35
|
}
|
|
36
36
|
<div class="flex items-center gap-4 mb-4">
|
|
37
37
|
<label class="font-semibold w-1/3" for="oip-menu-item-create-label">
|
|
38
38
|
{{ 'menuItemCreateDialogComponent.label' | translate }}
|
|
39
39
|
</label>
|
|
40
|
-
<input autocomplete="off" class="flex-auto" id="oip-menu-item-create-label" pInputText [(ngModel)]="label"
|
|
40
|
+
<input autocomplete="off" class="flex-auto" id="oip-menu-item-create-label" pInputText [(ngModel)]="label"/>
|
|
41
41
|
</div>
|
|
42
42
|
<div class="flex items-center gap-4 mb-4">
|
|
43
43
|
<label class="font-semibold w-1/3" for="oip-menu-item-create-module">
|
|
@@ -51,14 +51,14 @@ import { Menu } from '../../api/Menu';
|
|
|
51
51
|
optionValue="key"
|
|
52
52
|
placeholder="{{ 'menuItemCreateDialogComponent.selectModule' | translate }}"
|
|
53
53
|
[options]="modules"
|
|
54
|
-
[(ngModel)]="selectModule"
|
|
54
|
+
[(ngModel)]="selectModule"/>
|
|
55
55
|
</div>
|
|
56
56
|
<div class="flex items-center gap-4 mb-4">
|
|
57
57
|
<label class="font-semibold w-1/3" for="oip-menu-item-create-dialog-icon">
|
|
58
58
|
{{ 'menuItemCreateDialogComponent.icon' | translate }}
|
|
59
59
|
</label>
|
|
60
60
|
<i class="{{ selectIcon }}"></i>
|
|
61
|
-
<input class="flex-auto" id="oip-menu-item-create-dialog-icon" pInputText [(ngModel)]="selectIcon"
|
|
61
|
+
<input class="flex-auto" id="oip-menu-item-create-dialog-icon" pInputText [(ngModel)]="selectIcon"/>
|
|
62
62
|
</div>
|
|
63
63
|
<div class="flex justify-end gap-2">
|
|
64
64
|
<p-button
|
|
@@ -66,12 +66,12 @@ import { Menu } from '../../api/Menu';
|
|
|
66
66
|
label="{{ 'menuItemCreateDialogComponent.cancel' | translate }}"
|
|
67
67
|
severity="secondary"
|
|
68
68
|
(click)="changeVisible()"
|
|
69
|
-
(keydown)="changeVisible()"
|
|
69
|
+
(keydown)="changeVisible()"/>
|
|
70
70
|
<p-button
|
|
71
71
|
id="oip-menu-item-create-save"
|
|
72
72
|
label="{{ 'menuItemCreateDialogComponent.save' | translate }}"
|
|
73
73
|
(click)="save()"
|
|
74
|
-
(keydown)="save()"
|
|
74
|
+
(keydown)="save()"/>
|
|
75
75
|
</div>
|
|
76
76
|
</p-dialog>
|
|
77
77
|
`
|
|
@@ -3,7 +3,8 @@ import { ButtonModule } from 'primeng/button';
|
|
|
3
3
|
import { DialogModule } from 'primeng/dialog';
|
|
4
4
|
import { InputTextModule } from 'primeng/inputtext';
|
|
5
5
|
import { FormsModule } from '@angular/forms';
|
|
6
|
-
import { MenuService
|
|
6
|
+
import { MenuService } from '../../services/app.menu.service'
|
|
7
|
+
import { SecurityDataService, } from '../../services/security-data.service'
|
|
7
8
|
import { TranslatePipe } from '@ngx-translate/core';
|
|
8
9
|
import { EditModuleInstanceDto } from '../../dtos/edit-module-instance.dto';
|
|
9
10
|
import { MultiSelectModule } from 'primeng/multiselect';
|
|
@@ -27,7 +28,7 @@ import { MultiSelectModule } from 'primeng/multiselect';
|
|
|
27
28
|
class="flex-auto"
|
|
28
29
|
id="oip-menu-item-edit-dialog-menu-input"
|
|
29
30
|
pInputText
|
|
30
|
-
[(ngModel)]="item.label"
|
|
31
|
+
[(ngModel)]="item.label"/>
|
|
31
32
|
</div>
|
|
32
33
|
|
|
33
34
|
<div class="flex items-center gap-4 mb-4">
|
|
@@ -35,7 +36,7 @@ import { MultiSelectModule } from 'primeng/multiselect';
|
|
|
35
36
|
{{ 'menuItemEditDialogComponent.icon' | translate }}
|
|
36
37
|
</label>
|
|
37
38
|
<i class="{{ item.icon }}"></i>
|
|
38
|
-
<input class="flex-auto" id="oip-menu-item-edit-dialog-icon" pInputText [(ngModel)]="item.icon"
|
|
39
|
+
<input class="flex-auto" id="oip-menu-item-edit-dialog-icon" pInputText [(ngModel)]="item.icon"/>
|
|
39
40
|
</div>
|
|
40
41
|
|
|
41
42
|
<div class="flex items-center gap-4 mb-4">
|
|
@@ -49,7 +50,7 @@ import { MultiSelectModule } from 'primeng/multiselect';
|
|
|
49
50
|
placeholder="Select roles"
|
|
50
51
|
[maxSelectedLabels]="10"
|
|
51
52
|
[options]="roles"
|
|
52
|
-
[(ngModel)]="item.viewRoles"
|
|
53
|
+
[(ngModel)]="item.viewRoles"/>
|
|
53
54
|
</div>
|
|
54
55
|
|
|
55
56
|
<div class="flex justify-end gap-2">
|
|
@@ -58,12 +59,12 @@ import { MultiSelectModule } from 'primeng/multiselect';
|
|
|
58
59
|
label="{{ 'menuItemEditDialogComponent.cancel' | translate }}"
|
|
59
60
|
severity="secondary"
|
|
60
61
|
(click)="changeVisible()"
|
|
61
|
-
(keydown)="changeVisible()"
|
|
62
|
+
(keydown)="changeVisible()"/>
|
|
62
63
|
<p-button
|
|
63
64
|
id="oip-menu-item-edit-dialog-save-edit-button"
|
|
64
65
|
label="{{ 'menuItemEditDialogComponent.save' | translate }}"
|
|
65
66
|
(click)="save()"
|
|
66
|
-
(keydown)="save()"
|
|
67
|
+
(keydown)="save()"/>
|
|
67
68
|
</div>
|
|
68
69
|
</p-dialog>
|
|
69
70
|
`
|
|
@@ -2,7 +2,8 @@ import { Component, inject } from '@angular/core';
|
|
|
2
2
|
import { FileUploadModule } from 'primeng/fileupload';
|
|
3
3
|
import { ImageModule } from 'primeng/image';
|
|
4
4
|
import { AvatarModule } from 'primeng/avatar';
|
|
5
|
-
import { MsgService
|
|
5
|
+
import { MsgService } from "../services/msg.service";
|
|
6
|
+
import { UserService } from "../services/user.service";
|
|
6
7
|
import { TranslatePipe, TranslateService } from '@ngx-translate/core';
|
|
7
8
|
|
|
8
9
|
@Component({
|
|
@@ -15,7 +16,7 @@ import { TranslatePipe, TranslateService } from '@ngx-translate/core';
|
|
|
15
16
|
id="oip-user-profile-photo-avatar"
|
|
16
17
|
shape="circle"
|
|
17
18
|
size="xlarge"
|
|
18
|
-
[image]="userService.photoLoaded ? userService.photo : null"
|
|
19
|
+
[image]="userService.photoLoaded ? userService.photo : null"/>
|
|
19
20
|
<div class="mt-2">
|
|
20
21
|
<p-fileupload
|
|
21
22
|
accept="image/*"
|
|
@@ -28,7 +29,7 @@ import { TranslatePipe, TranslateService } from '@ngx-translate/core';
|
|
|
28
29
|
url="/api/user-profile/post-user-photo"
|
|
29
30
|
withCredentials="true"
|
|
30
31
|
[auto]="true"
|
|
31
|
-
(onUpload)="onBasicUploadAuto($event)"
|
|
32
|
+
(onUpload)="onBasicUploadAuto($event)"/>
|
|
32
33
|
</div>
|
|
33
34
|
`
|
|
34
35
|
})
|
|
@@ -60,7 +60,7 @@ export function convertToPrimeNgDateFormat(dateformat: string): string {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
/**
|
|
63
|
-
*
|
|
63
|
+
* Convert all properties of an object with string dates to Date objects
|
|
64
64
|
* @param obj
|
|
65
65
|
*/
|
|
66
66
|
export function restoreDates(obj: any) {
|
|
@@ -86,7 +86,7 @@ export function restoreDates(obj: any) {
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
/**
|
|
89
|
-
*
|
|
89
|
+
* Check if a string is a date in ISO format
|
|
90
90
|
* @param str
|
|
91
91
|
*/
|
|
92
92
|
export function isIsoDate(str: string) {
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { inject, Injectable } from '@angular/core';
|
|
14
|
-
import { LayoutService
|
|
14
|
+
import { LayoutService } from '../services/app.layout.service';
|
|
15
|
+
import { SecurityService } from '../services/security.service';
|
|
15
16
|
|
|
16
17
|
export type QueryParamsType = Record<string | number, any>;
|
|
17
18
|
export type ResponseFormat = keyof Omit<Body, 'body' | 'bodyUsed'>;
|
|
@@ -58,7 +59,7 @@ export enum ContentType {
|
|
|
58
59
|
Text = 'text/plain'
|
|
59
60
|
}
|
|
60
61
|
|
|
61
|
-
@Injectable({
|
|
62
|
+
@Injectable({providedIn: 'root'})
|
|
62
63
|
export class HttpClient<SecurityDataType = unknown> {
|
|
63
64
|
protected securityService = inject(SecurityService);
|
|
64
65
|
public baseUrl: string = '';
|
|
@@ -75,6 +76,7 @@ export class HttpClient<SecurityDataType = unknown> {
|
|
|
75
76
|
const storageData = localStorage.getItem('0-oip-client');
|
|
76
77
|
return JSON.parse(storageData)?.authnResult?.access_token;
|
|
77
78
|
}
|
|
79
|
+
|
|
78
80
|
private getLanguage(): string {
|
|
79
81
|
// Получаем язык из localStorage или используем значение по умолчанию
|
|
80
82
|
return localStorage.getItem('user-language') || 'en';
|
|
@@ -189,16 +191,16 @@ export class HttpClient<SecurityDataType = unknown> {
|
|
|
189
191
|
};
|
|
190
192
|
|
|
191
193
|
public request = async <T = any, E = any>({
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
194
|
+
body,
|
|
195
|
+
secure,
|
|
196
|
+
path,
|
|
197
|
+
type,
|
|
198
|
+
query,
|
|
199
|
+
format,
|
|
200
|
+
baseUrl,
|
|
201
|
+
cancelToken,
|
|
202
|
+
...params
|
|
203
|
+
}: FullRequestParams): Promise<T> => {
|
|
202
204
|
const secureParams =
|
|
203
205
|
((typeof secure === 'boolean' ? secure : this.baseApiParams.secure) &&
|
|
204
206
|
this.securityWorker &&
|
|
@@ -213,7 +215,7 @@ export class HttpClient<SecurityDataType = unknown> {
|
|
|
213
215
|
...requestParams,
|
|
214
216
|
headers: {
|
|
215
217
|
...(requestParams.headers || {}),
|
|
216
|
-
...(type && type !== ContentType.FormData ? {
|
|
218
|
+
...(type && type !== ContentType.FormData ? {'Content-Type': type} : {})
|
|
217
219
|
},
|
|
218
220
|
signal: (cancelToken ? this.createAbortSignal(cancelToken) : requestParams.signal) || null,
|
|
219
221
|
body: typeof body === 'undefined' || body === null ? null : payloadFormatter(body)
|
|
@@ -227,18 +229,18 @@ export class HttpClient<SecurityDataType = unknown> {
|
|
|
227
229
|
const data = !responseFormat
|
|
228
230
|
? r
|
|
229
231
|
: await response[responseFormat]()
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
232
|
+
.then((data) => {
|
|
233
|
+
if (r.ok) {
|
|
234
|
+
r.data = data;
|
|
235
|
+
} else {
|
|
236
|
+
r.error = data;
|
|
237
|
+
}
|
|
238
|
+
return r;
|
|
239
|
+
})
|
|
240
|
+
.catch((e) => {
|
|
241
|
+
r.error = e;
|
|
242
|
+
return r;
|
|
243
|
+
});
|
|
242
244
|
|
|
243
245
|
if (cancelToken) {
|
|
244
246
|
this.abortControllers.delete(cancelToken);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<%
|
|
2
|
+
const { utils, route, config, modelTypes } = it;
|
|
3
|
+
const { _, pascalCase, require } = utils;
|
|
4
|
+
const apiClassName = pascalCase(route.moduleName);
|
|
5
|
+
const routes = route.routes;
|
|
6
|
+
const dataContracts = _.map(modelTypes, "name");
|
|
7
|
+
%>
|
|
8
|
+
|
|
9
|
+
<% if (config.httpClientType === config.constants.HTTP_CLIENT.AXIOS) { %> import type { AxiosRequestConfig, AxiosResponse } from "axios"; <% } %>
|
|
10
|
+
|
|
11
|
+
import { HttpClient, RequestParams, ContentType, HttpResponse } from "./<%~ config.fileNames.httpClient %>";
|
|
12
|
+
<% if (dataContracts.length) { %>
|
|
13
|
+
import { <%~ dataContracts.join(", ") %> } from "./<%~ config.fileNames.dataContracts %>"
|
|
14
|
+
<% } %>
|
|
15
|
+
import { Injectable } from "@angular/core";
|
|
16
|
+
|
|
17
|
+
@Injectable()
|
|
18
|
+
export class <%= apiClassName %><SecurityDataType = unknown><% if (!config.singleHttpClient) { %> extends HttpClient<SecurityDataType> <% } %> {
|
|
19
|
+
<% if(config.singleHttpClient) { %>
|
|
20
|
+
http: HttpClient<SecurityDataType>;
|
|
21
|
+
|
|
22
|
+
constructor (http: HttpClient<SecurityDataType>) {
|
|
23
|
+
this.http = http;
|
|
24
|
+
}
|
|
25
|
+
<% } %>
|
|
26
|
+
|
|
27
|
+
<% for (const route of routes) { %>
|
|
28
|
+
<%~ includeFile('./procedure-call.ejs', { ...it, route }) %>
|
|
29
|
+
<% } %>
|
|
30
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<%
|
|
2
|
+
const { data, utils } = it;
|
|
3
|
+
const { formatDescription, require, _ } = utils;
|
|
4
|
+
|
|
5
|
+
const stringify = (value) => _.isObject(value) ? JSON.stringify(value) : _.isString(value) ? `"${value}"` : value
|
|
6
|
+
|
|
7
|
+
const jsDocLines = _.compact([
|
|
8
|
+
data.title,
|
|
9
|
+
data.description && formatDescription(data.description),
|
|
10
|
+
!_.isUndefined(data.deprecated) && data.deprecated && '@deprecated',
|
|
11
|
+
!_.isUndefined(data.format) && `@format ${data.format}`,
|
|
12
|
+
!_.isUndefined(data.minimum) && `@min ${data.minimum}`,
|
|
13
|
+
!_.isUndefined(data.multipleOf) && `@multipleOf ${data.multipleOf}`,
|
|
14
|
+
!_.isUndefined(data.exclusiveMinimum) && `@exclusiveMin ${data.exclusiveMinimum}`,
|
|
15
|
+
!_.isUndefined(data.maximum) && `@max ${data.maximum}`,
|
|
16
|
+
!_.isUndefined(data.minLength) && `@minLength ${data.minLength}`,
|
|
17
|
+
!_.isUndefined(data.maxLength) && `@maxLength ${data.maxLength}`,
|
|
18
|
+
!_.isUndefined(data.exclusiveMaximum) && `@exclusiveMax ${data.exclusiveMaximum}`,
|
|
19
|
+
!_.isUndefined(data.maxItems) && `@maxItems ${data.maxItems}`,
|
|
20
|
+
!_.isUndefined(data.minItems) && `@minItems ${data.minItems}`,
|
|
21
|
+
!_.isUndefined(data.uniqueItems) && `@uniqueItems ${data.uniqueItems}`,
|
|
22
|
+
!_.isUndefined(data.default) && `@default ${stringify(data.default)}`,
|
|
23
|
+
!_.isUndefined(data.pattern) && `@pattern ${data.pattern}`,
|
|
24
|
+
!_.isUndefined(data.example) && `@example ${stringify(data.example)}`
|
|
25
|
+
]).join('\n').split('\n');
|
|
26
|
+
%>
|
|
27
|
+
<% if (jsDocLines.every(_.isEmpty)) { %>
|
|
28
|
+
<% } else if (jsDocLines.length === 1) { %>
|
|
29
|
+
/** <%~ jsDocLines[0] %> */
|
|
30
|
+
<% } else if (jsDocLines.length) { %>
|
|
31
|
+
/**
|
|
32
|
+
<% for (jsDocLine of jsDocLines) { %>
|
|
33
|
+
* <%~ jsDocLine %>
|
|
34
|
+
|
|
35
|
+
<% } %>
|
|
36
|
+
*/
|
|
37
|
+
<% } %>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<%
|
|
2
|
+
const { modelTypes, utils, config } = it;
|
|
3
|
+
const { formatDescription, require, _, Ts } = utils;
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
const buildGenerics = (contract) => {
|
|
7
|
+
if (!contract.genericArgs || !contract.genericArgs.length) return '';
|
|
8
|
+
|
|
9
|
+
return '<' + contract.genericArgs.map(({ name, default: defaultType, extends: extendsType }) => {
|
|
10
|
+
return [
|
|
11
|
+
name,
|
|
12
|
+
extendsType && `extends ${extendsType}`,
|
|
13
|
+
defaultType && `= ${defaultType}`,
|
|
14
|
+
].join('')
|
|
15
|
+
}).join(',') + '>'
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const dataContractTemplates = {
|
|
19
|
+
enum: (contract) => {
|
|
20
|
+
return `enum ${contract.name} {\r\n${contract.content} \r\n }`;
|
|
21
|
+
},
|
|
22
|
+
interface: (contract) => {
|
|
23
|
+
return `interface ${contract.name}${buildGenerics(contract)} {\r\n${contract.content}}\r\n\r\n`;
|
|
24
|
+
},
|
|
25
|
+
type: (contract) => {
|
|
26
|
+
return `type ${contract.name}${buildGenerics(contract)} = ${contract.content}`;
|
|
27
|
+
},
|
|
28
|
+
}
|
|
29
|
+
%>
|
|
30
|
+
|
|
31
|
+
<% if (config.internalTemplateOptions.addUtilRequiredKeysType) { %>
|
|
32
|
+
type <%~ config.Ts.CodeGenKeyword.UtilRequiredKeys %><T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>
|
|
33
|
+
<% } %>
|
|
34
|
+
|
|
35
|
+
<% for (const contract of modelTypes) {
|
|
36
|
+
if (contract.typeIdentifier === 'interface' && contract.rawContent && Array.isArray(contract.rawContent)) {
|
|
37
|
+
contract.rawContent.forEach((prop) => {
|
|
38
|
+
if (prop.format ==='date-time') {
|
|
39
|
+
prop.field = prop.field.replace(': string', ': Date');
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
let customContent = '';
|
|
43
|
+
contract.rawContent.forEach((prop) => {
|
|
44
|
+
if (prop.description) customContent += `/** ${prop.description} */ \r\n`;
|
|
45
|
+
customContent += `${prop.field};\r\n`;
|
|
46
|
+
})
|
|
47
|
+
contract.content = customContent;
|
|
48
|
+
}
|
|
49
|
+
%>
|
|
50
|
+
<%~ includeFile('./data-contract-jsdoc.ejs', { ...it, data: { ...contract, ...contract.typeData } }) %>
|
|
51
|
+
<%~ contract.internal ? '' : 'export'%> <%~ (dataContractTemplates[contract.typeIdentifier] || dataContractTemplates.type)(contract) %>
|
|
52
|
+
<% } %>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<%
|
|
2
|
+
const { contract, utils, config } = it;
|
|
3
|
+
const { formatDescription, require, _ } = utils;
|
|
4
|
+
const { name, $content } = contract;
|
|
5
|
+
%>
|
|
6
|
+
<% if (config.generateUnionEnums) { %>
|
|
7
|
+
export type <%~ name %> = <%~ _.map($content, ({ value }) => value).join(" | ") %>
|
|
8
|
+
<% } else { %>
|
|
9
|
+
export enum <%~ name %> {
|
|
10
|
+
<%~ _.map($content, ({ key, value }) => `${key} = ${value}`).join(",\n") %>
|
|
11
|
+
}
|
|
12
|
+
<% } %>
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
<%
|
|
2
|
+
const { apiConfig, generateResponses, config } = it;
|
|
3
|
+
%>
|
|
4
|
+
|
|
5
|
+
import { SecurityService } from "oip-common";
|
|
6
|
+
import { LayoutService } from "oip-common";
|
|
7
|
+
import { inject, Injectable } from "@angular/core";
|
|
8
|
+
|
|
9
|
+
export type QueryParamsType = Record<string | number, any>;
|
|
10
|
+
export type ResponseFormat = keyof Omit<Body, "body" | "bodyUsed">;
|
|
11
|
+
|
|
12
|
+
export interface FullRequestParams extends Omit<RequestInit, "body"> {
|
|
13
|
+
/** set parameter to `true` for call `securityWorker` for this request */
|
|
14
|
+
secure?: boolean;
|
|
15
|
+
/** request path */
|
|
16
|
+
path: string;
|
|
17
|
+
/** content type of request body */
|
|
18
|
+
type?: ContentType;
|
|
19
|
+
/** query params */
|
|
20
|
+
query?: QueryParamsType;
|
|
21
|
+
/** format of response (i.e. response.json() -> format: "json") */
|
|
22
|
+
format?: ResponseFormat;
|
|
23
|
+
/** request body */
|
|
24
|
+
body?: unknown;
|
|
25
|
+
/** base url */
|
|
26
|
+
baseUrl?: string;
|
|
27
|
+
/** request cancellation token */
|
|
28
|
+
cancelToken?: CancelToken;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type RequestParams = Omit<FullRequestParams, "body" | "method" | "query" | "path">
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
export interface ApiConfig<SecurityDataType = unknown> {
|
|
35
|
+
baseUrl?: string;
|
|
36
|
+
baseApiParams?: Omit<RequestParams, "baseUrl" | "cancelToken" | "signal">;
|
|
37
|
+
securityWorker?: (securityData: SecurityDataType | null) => Promise<RequestParams | void> | RequestParams | void;
|
|
38
|
+
customFetch?: typeof fetch;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface HttpResponse<D extends unknown, E extends unknown = unknown> extends Response {
|
|
42
|
+
data: D;
|
|
43
|
+
error: E;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
type CancelToken = Symbol | string | number;
|
|
47
|
+
|
|
48
|
+
export enum ContentType {
|
|
49
|
+
Json = "application/json",
|
|
50
|
+
FormData = "multipart/form-data",
|
|
51
|
+
UrlEncoded = "application/x-www-form-urlencoded",
|
|
52
|
+
Text = "text/plain",
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
@Injectable({ providedIn: 'root' })
|
|
56
|
+
export class HttpClient<SecurityDataType = unknown> {
|
|
57
|
+
protected securityService = inject(SecurityService);
|
|
58
|
+
protected layoutService = inject(LayoutService);
|
|
59
|
+
public baseUrl: string = "<%~ apiConfig.baseUrl %>";
|
|
60
|
+
private securityData: SecurityDataType | null = null;
|
|
61
|
+
private securityWorker?: ApiConfig<SecurityDataType>["securityWorker"] =
|
|
62
|
+
(securityData) => ({
|
|
63
|
+
headers: {
|
|
64
|
+
"Accept-language": this.layoutService.language()
|
|
65
|
+
? this.layoutService.language()
|
|
66
|
+
: 'en',
|
|
67
|
+
"X-Timezone": this.layoutService.timeZone(),
|
|
68
|
+
Authorization: `Bearer ${securityData}`,
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
private abortControllers = new Map<CancelToken, AbortController>();
|
|
72
|
+
private customFetch = (...fetchParams: Parameters<typeof fetch>) => fetch(...fetchParams);
|
|
73
|
+
|
|
74
|
+
private baseApiParams: RequestParams = {
|
|
75
|
+
credentials: 'same-origin',
|
|
76
|
+
headers: {},
|
|
77
|
+
redirect: 'follow',
|
|
78
|
+
referrerPolicy: 'no-referrer',
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
constructor() {
|
|
82
|
+
this.securityService.getAccessToken().subscribe((token) => {
|
|
83
|
+
this.securityData = token;
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
public setSecurityData = (data: SecurityDataType | null) => {
|
|
88
|
+
this.securityData = data;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
protected encodeQueryParam(key: string, value: any) {
|
|
92
|
+
const encodedKey = encodeURIComponent(key);
|
|
93
|
+
return `${encodedKey}=${encodeURIComponent(typeof value === "number" ? value : `${value}`)}`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
protected addQueryParam(query: QueryParamsType, key: string) {
|
|
97
|
+
return this.encodeQueryParam(key, query[key]);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
protected addArrayQueryParam(query: QueryParamsType, key: string) {
|
|
101
|
+
const value = query[key];
|
|
102
|
+
return value.map((v: any) => this.encodeQueryParam(key, v)).join("&");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
protected toQueryString(rawQuery?: QueryParamsType): string {
|
|
106
|
+
const query = rawQuery || {};
|
|
107
|
+
const keys = Object.keys(query).filter((key) => "undefined" !== typeof query[key]);
|
|
108
|
+
return keys
|
|
109
|
+
.map((key) =>
|
|
110
|
+
Array.isArray(query[key])
|
|
111
|
+
? this.addArrayQueryParam(query, key)
|
|
112
|
+
: this.addQueryParam(query, key),
|
|
113
|
+
)
|
|
114
|
+
.join("&");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
protected addQueryParams(rawQuery?: QueryParamsType): string {
|
|
118
|
+
const queryString = this.toQueryString(rawQuery);
|
|
119
|
+
return queryString ? `?${queryString}` : "";
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private contentFormatters: Record<ContentType, (input: any) => any> = {
|
|
123
|
+
[ContentType.Json]: (input:any) => input !== null && (typeof input === "object" || typeof input === "string") ? JSON.stringify(input) : input,
|
|
124
|
+
[ContentType.Text]: (input:any) => input !== null && typeof input !== "string" ? JSON.stringify(input) : input,
|
|
125
|
+
[ContentType.FormData]: (input: any) =>
|
|
126
|
+
Object.keys(input || {}).reduce((formData, key) => {
|
|
127
|
+
const property = input[key];
|
|
128
|
+
formData.append(
|
|
129
|
+
key,
|
|
130
|
+
property instanceof Blob ?
|
|
131
|
+
property :
|
|
132
|
+
typeof property === "object" && property !== null ?
|
|
133
|
+
JSON.stringify(property) :
|
|
134
|
+
`${property}`
|
|
135
|
+
);
|
|
136
|
+
return formData;
|
|
137
|
+
}, new FormData()),
|
|
138
|
+
[ContentType.UrlEncoded]: (input: any) => this.toQueryString(input),
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
protected mergeRequestParams(params1: RequestParams, params2?: RequestParams): RequestParams {
|
|
142
|
+
return {
|
|
143
|
+
...this.baseApiParams,
|
|
144
|
+
...params1,
|
|
145
|
+
...(params2 || {}),
|
|
146
|
+
headers: {
|
|
147
|
+
...(this.baseApiParams.headers || {}),
|
|
148
|
+
...(params1.headers || {}),
|
|
149
|
+
...((params2 && params2.headers) || {}),
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
protected createAbortSignal = (cancelToken: CancelToken): AbortSignal | undefined => {
|
|
155
|
+
if (this.abortControllers.has(cancelToken)) {
|
|
156
|
+
const abortController = this.abortControllers.get(cancelToken);
|
|
157
|
+
if (abortController) {
|
|
158
|
+
return abortController.signal;
|
|
159
|
+
}
|
|
160
|
+
return void 0;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const abortController = new AbortController();
|
|
164
|
+
this.abortControllers.set(cancelToken, abortController);
|
|
165
|
+
return abortController.signal;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
public abortRequest = (cancelToken: CancelToken) => {
|
|
169
|
+
const abortController = this.abortControllers.get(cancelToken)
|
|
170
|
+
|
|
171
|
+
if (abortController) {
|
|
172
|
+
abortController.abort();
|
|
173
|
+
this.abortControllers.delete(cancelToken);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
public request = async <T = any, E = any>({
|
|
178
|
+
body,
|
|
179
|
+
secure,
|
|
180
|
+
path,
|
|
181
|
+
type,
|
|
182
|
+
query,
|
|
183
|
+
format,
|
|
184
|
+
baseUrl,
|
|
185
|
+
cancelToken,
|
|
186
|
+
...params
|
|
187
|
+
<% if (config.unwrapResponseData) { %>
|
|
188
|
+
}: FullRequestParams): Promise<T> => {
|
|
189
|
+
<% } else { %>
|
|
190
|
+
}: FullRequestParams): Promise<HttpResponse<T, E>> => {
|
|
191
|
+
<% } %>
|
|
192
|
+
const secureParams = ((typeof secure === 'boolean' ? secure : this.baseApiParams.secure) && this.securityWorker && await this.securityWorker(this.securityData)) || {};
|
|
193
|
+
const requestParams = this.mergeRequestParams(params, secureParams);
|
|
194
|
+
const queryString = query && this.toQueryString(query);
|
|
195
|
+
const payloadFormatter = this.contentFormatters[type || ContentType.Json];
|
|
196
|
+
let responseFormat = format || requestParams.format;
|
|
197
|
+
|
|
198
|
+
return this.customFetch(
|
|
199
|
+
`${baseUrl || this.baseUrl || ""}${path}${queryString ? `?${queryString}` : ""}`,
|
|
200
|
+
{
|
|
201
|
+
...requestParams,
|
|
202
|
+
headers: {
|
|
203
|
+
...(requestParams.headers || {}),
|
|
204
|
+
...(type && type !== ContentType.FormData ? { "Content-Type": type } : {}),
|
|
205
|
+
},
|
|
206
|
+
signal: (cancelToken ? this.createAbortSignal(cancelToken) : requestParams.signal) || null,
|
|
207
|
+
body: typeof body === "undefined" || body === null ? null : payloadFormatter(body),
|
|
208
|
+
}
|
|
209
|
+
).then(async (response) => {
|
|
210
|
+
const r = response.clone() as HttpResponse<T, E>;
|
|
211
|
+
r.data = (null as unknown) as T;
|
|
212
|
+
r.error = (null as unknown) as E;
|
|
213
|
+
|
|
214
|
+
if (typeof E !== undefined && responseFormat === undefined)
|
|
215
|
+
responseFormat = 'json';
|
|
216
|
+
|
|
217
|
+
const data = !responseFormat ? r : await response[responseFormat]()
|
|
218
|
+
.then((data) => {
|
|
219
|
+
if (r.ok) {
|
|
220
|
+
r.data = data;
|
|
221
|
+
} else {
|
|
222
|
+
r.error = data;
|
|
223
|
+
}
|
|
224
|
+
return r;
|
|
225
|
+
})
|
|
226
|
+
.catch((e) => {
|
|
227
|
+
r.error = e;
|
|
228
|
+
return r;
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
if (cancelToken) {
|
|
232
|
+
this.abortControllers.delete(cancelToken);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
<% if (!config.disableThrowOnError) { %>
|
|
236
|
+
if (!response.ok) throw data;
|
|
237
|
+
<% } %>
|
|
238
|
+
<% if (config.unwrapResponseData) { %>
|
|
239
|
+
return data.data;
|
|
240
|
+
<% } else { %>
|
|
241
|
+
return data;
|
|
242
|
+
<% } %>
|
|
243
|
+
});
|
|
244
|
+
};
|
|
245
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<%
|
|
2
|
+
const { contract, utils } = it;
|
|
3
|
+
const { formatDescription, require, _ } = utils;
|
|
4
|
+
%>
|
|
5
|
+
export interface <%~ contract.name %> {
|
|
6
|
+
<% for (const field of contract.$content) { %>
|
|
7
|
+
<%~ includeFile('./object-field-jsdoc.ejs', { ...it, field }) %>
|
|
8
|
+
<%~ field.name %><%~ field.isRequired ? '' : '?' %>: <%~ field.value %><%~ field.isNullable ? ' | null' : ''%>;
|
|
9
|
+
<% } %>
|
|
10
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<%
|
|
2
|
+
const { field, utils } = it;
|
|
3
|
+
const { formatDescription, require, _ } = utils;
|
|
4
|
+
|
|
5
|
+
const comments = _.uniq(
|
|
6
|
+
_.compact([
|
|
7
|
+
field.title,
|
|
8
|
+
field.description,
|
|
9
|
+
field.deprecated && ` * @deprecated`,
|
|
10
|
+
!_.isUndefined(field.format) && `@format ${field.format}`,
|
|
11
|
+
!_.isUndefined(field.minimum) && `@min ${field.minimum}`,
|
|
12
|
+
!_.isUndefined(field.maximum) && `@max ${field.maximum}`,
|
|
13
|
+
!_.isUndefined(field.pattern) && `@pattern ${field.pattern}`,
|
|
14
|
+
!_.isUndefined(field.example) &&
|
|
15
|
+
`@example ${_.isObject(field.example) ? JSON.stringify(field.example) : field.example}`,
|
|
16
|
+
]).reduce((acc, comment) => [...acc, ...comment.split(/\n/g)], []),
|
|
17
|
+
);
|
|
18
|
+
%>
|
|
19
|
+
<% if (comments.length === 1) { %>
|
|
20
|
+
/** <%~ comments[0] %> */
|
|
21
|
+
<% } else if (comments.length) { %>
|
|
22
|
+
/**
|
|
23
|
+
<% comments.forEach(comment => { %>
|
|
24
|
+
* <%~ comment %>
|
|
25
|
+
|
|
26
|
+
<% }) %>
|
|
27
|
+
*/
|
|
28
|
+
<% } %>
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
<%
|
|
2
|
+
const { utils, route, config } = it;
|
|
3
|
+
const { requestBodyInfo, responseBodyInfo, specificArgNameResolver } = route;
|
|
4
|
+
const { _, getInlineParseContent, getParseContent, parseSchema, getComponentByRef, require } = utils;
|
|
5
|
+
const { parameters, path, method, payload, query, formData, security, requestParams } = route.request;
|
|
6
|
+
const { type, errorType, contentTypes } = route.response;
|
|
7
|
+
const { HTTP_CLIENT, RESERVED_REQ_PARAMS_ARG_NAMES } = config.constants;
|
|
8
|
+
const routeDocs = includeFile("./route-docs", { config, route, utils });
|
|
9
|
+
const queryName = (query && query.name) || "query";
|
|
10
|
+
const pathParams = _.values(parameters);
|
|
11
|
+
const pathParamsNames = _.map(pathParams, "name");
|
|
12
|
+
|
|
13
|
+
const isFetchTemplate = config.httpClientType === HTTP_CLIENT.FETCH;
|
|
14
|
+
|
|
15
|
+
const requestConfigParam = {
|
|
16
|
+
name: specificArgNameResolver.resolve(RESERVED_REQ_PARAMS_ARG_NAMES),
|
|
17
|
+
optional: true,
|
|
18
|
+
type: "RequestParams",
|
|
19
|
+
defaultValue: "{}",
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const argToTmpl = ({ name, optional, type, defaultValue }) => `${name}${!defaultValue && optional ? '?' : ''}: ${type}${defaultValue ? ` = ${defaultValue}` : ''}`;
|
|
23
|
+
|
|
24
|
+
const rawWrapperArgs = config.extractRequestParams ?
|
|
25
|
+
_.compact([
|
|
26
|
+
requestParams && {
|
|
27
|
+
name: pathParams.length ? `{ ${_.join(pathParamsNames, ", ")}, ...${queryName} }` : queryName,
|
|
28
|
+
optional: false,
|
|
29
|
+
type: getInlineParseContent(requestParams),
|
|
30
|
+
},
|
|
31
|
+
...(!requestParams ? pathParams : []),
|
|
32
|
+
payload,
|
|
33
|
+
requestConfigParam,
|
|
34
|
+
]) :
|
|
35
|
+
_.compact([
|
|
36
|
+
...pathParams,
|
|
37
|
+
query,
|
|
38
|
+
payload,
|
|
39
|
+
requestConfigParam,
|
|
40
|
+
])
|
|
41
|
+
|
|
42
|
+
const wrapperArgs = _
|
|
43
|
+
// Sort by optionality
|
|
44
|
+
.sortBy(rawWrapperArgs, [o => o.optional])
|
|
45
|
+
.map(argToTmpl)
|
|
46
|
+
.join(', ')
|
|
47
|
+
|
|
48
|
+
// RequestParams["type"]
|
|
49
|
+
const requestContentKind = {
|
|
50
|
+
"JSON": "ContentType.Json",
|
|
51
|
+
"URL_ENCODED": "ContentType.UrlEncoded",
|
|
52
|
+
"FORM_DATA": "ContentType.FormData",
|
|
53
|
+
"TEXT": "ContentType.Text",
|
|
54
|
+
}
|
|
55
|
+
// RequestParams["format"]
|
|
56
|
+
const responseContentKind = {
|
|
57
|
+
"JSON": '"json"',
|
|
58
|
+
"IMAGE": '"blob"',
|
|
59
|
+
"FORM_DATA": isFetchTemplate ? '"formData"' : '"document"'
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const bodyTmpl = _.get(payload, "name") || null;
|
|
63
|
+
const queryTmpl = (query != null && queryName) || null;
|
|
64
|
+
const bodyContentKindTmpl = requestContentKind[requestBodyInfo.contentKind] || null;
|
|
65
|
+
const responseFormatTmpl = responseContentKind[responseBodyInfo.success && responseBodyInfo.success.schema && responseBodyInfo.success.schema.contentKind] || null;
|
|
66
|
+
const securityTmpl = security ? 'true' : null;
|
|
67
|
+
|
|
68
|
+
const describeReturnType = () => {
|
|
69
|
+
if (!config.toJS) return "";
|
|
70
|
+
|
|
71
|
+
switch(config.httpClientType) {
|
|
72
|
+
case HTTP_CLIENT.AXIOS: {
|
|
73
|
+
return `Promise<AxiosResponse<${type}>>`
|
|
74
|
+
}
|
|
75
|
+
default: {
|
|
76
|
+
return `Promise<HttpResponse<${type}, ${errorType}>`
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
%>
|
|
82
|
+
/**
|
|
83
|
+
<%~ routeDocs.description %>
|
|
84
|
+
|
|
85
|
+
*<% /* Here you can add some other JSDoc tags */ %>
|
|
86
|
+
|
|
87
|
+
<%~ routeDocs.lines %>
|
|
88
|
+
|
|
89
|
+
*/
|
|
90
|
+
<%~ route.routeName.usage %> = (<%~ wrapperArgs %>)<%~ config.toJS ? `: ${describeReturnType()}` : "" %> =>
|
|
91
|
+
<%~ config.singleHttpClient ? 'this.http.request' : 'this.request' %><<%~ type %>, <%~ errorType %>>({
|
|
92
|
+
path: `<%~ path %>`,
|
|
93
|
+
method: '<%~ _.upperCase(method) %>',
|
|
94
|
+
<%~ queryTmpl ? `query: ${queryTmpl},` : '' %>
|
|
95
|
+
<%~ bodyTmpl ? `body: ${bodyTmpl},` : '' %>
|
|
96
|
+
<%~ securityTmpl ? `secure: ${securityTmpl},` : '' %>
|
|
97
|
+
<%~ bodyContentKindTmpl ? `type: ${bodyContentKindTmpl},` : '' %>
|
|
98
|
+
<%~ responseFormatTmpl ? `format: ${responseFormatTmpl},` : '' %>
|
|
99
|
+
...<%~ _.get(requestConfigParam, "name") %>,
|
|
100
|
+
})
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<%
|
|
2
|
+
const { config, route, utils } = it;
|
|
3
|
+
const { _, formatDescription, fmtToJSDocLine, pascalCase, require } = utils;
|
|
4
|
+
const { raw, request, routeName } = route;
|
|
5
|
+
|
|
6
|
+
const jsDocDescription = raw.summary ?
|
|
7
|
+
` * @description ${formatDescription(raw.summary, true)}` :
|
|
8
|
+
fmtToJSDocLine('No description', { eol: false });
|
|
9
|
+
const jsDocLines = _.compact([
|
|
10
|
+
_.size(raw.tags) && ` * @tags ${raw.tags.join(", ")}`,
|
|
11
|
+
` * @name ${_.camelCase(routeName.usage)}`,
|
|
12
|
+
` * @request ${_.upperCase(request.method)}:${raw.route}`,
|
|
13
|
+
raw.deprecated && ` * @deprecated`,
|
|
14
|
+
routeName.duplicate && ` * @originalName ${routeName.original}`,
|
|
15
|
+
routeName.duplicate && ` * @duplicate`,
|
|
16
|
+
request.security && ` * @secure`,
|
|
17
|
+
...(config.generateResponses && raw.responsesTypes.length
|
|
18
|
+
? raw.responsesTypes.map(
|
|
19
|
+
({ type, status, description, isSuccess }) =>
|
|
20
|
+
` * @response \`${status}\` \`${_.replace(_.replace(type, /\/\*/g, "\\*"), /\*\//g, "*\\")}\` ${description}`,
|
|
21
|
+
)
|
|
22
|
+
: []),
|
|
23
|
+
]).map(str => str.trimEnd()).join("\n");
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
description: jsDocDescription,
|
|
27
|
+
lines: jsDocLines,
|
|
28
|
+
}
|
|
29
|
+
%>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<%
|
|
2
|
+
const { routeInfo, utils } = it;
|
|
3
|
+
const {
|
|
4
|
+
operationId,
|
|
5
|
+
method,
|
|
6
|
+
route,
|
|
7
|
+
moduleName,
|
|
8
|
+
responsesTypes,
|
|
9
|
+
description,
|
|
10
|
+
tags,
|
|
11
|
+
summary,
|
|
12
|
+
pathArgs,
|
|
13
|
+
} = routeInfo;
|
|
14
|
+
const { _, fmtToJSDocLine, require } = utils;
|
|
15
|
+
|
|
16
|
+
const methodAliases = {
|
|
17
|
+
get: (pathName, hasPathInserts) => _.camelCase(`${pathName}`),
|
|
18
|
+
post: (pathName, hasPathInserts) => _.camelCase(`${pathName}`),
|
|
19
|
+
put: (pathName, hasPathInserts) => _.camelCase(`${pathName}`),
|
|
20
|
+
patch: (pathName, hasPathInserts) => _.camelCase(`${pathName}`),
|
|
21
|
+
delete: (pathName, hasPathInserts) => _.camelCase(`${pathName}`),
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const createCustomOperationId = (method, route, moduleName) => {
|
|
25
|
+
const hasPathInserts = /\{(\w){1,}\}$/g.test(route);
|
|
26
|
+
const splittedRouteBySlash = _.compact(_.replace(route, /\{(\w){1,}\}/g, "").split("/"));
|
|
27
|
+
const routeParts = (splittedRouteBySlash.length > 1
|
|
28
|
+
? splittedRouteBySlash.splice(1)
|
|
29
|
+
: splittedRouteBySlash
|
|
30
|
+
).join("_");
|
|
31
|
+
return routeParts.length > 3 && methodAliases[method]
|
|
32
|
+
? methodAliases[method](routeParts, hasPathInserts)
|
|
33
|
+
: _.camelCase(_.lowerCase(method) + "_" + [moduleName].join("_")) || "index";
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
if (operationId)
|
|
37
|
+
return _.camelCase(operationId);
|
|
38
|
+
if (route === "/")
|
|
39
|
+
return _.camelCase(`${_.lowerCase(method)}Root`);
|
|
40
|
+
|
|
41
|
+
return createCustomOperationId(method, route, moduleName);
|
|
42
|
+
%>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<%
|
|
2
|
+
const { route, utils, config } = it;
|
|
3
|
+
const { _, pascalCase, require } = utils;
|
|
4
|
+
const { query, payload, pathParams, headers } = route.request;
|
|
5
|
+
|
|
6
|
+
const routeDocs = includeFile("./route-docs", { config, route, utils });
|
|
7
|
+
const routeNamespace = pascalCase(route.routeName.usage);
|
|
8
|
+
|
|
9
|
+
%>
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
<%~ routeDocs.description %>
|
|
13
|
+
|
|
14
|
+
<%~ routeDocs.lines %>
|
|
15
|
+
|
|
16
|
+
*/
|
|
17
|
+
export namespace <%~ routeNamespace %> {
|
|
18
|
+
export type RequestParams = <%~ (pathParams && pathParams.type) || '{}' %>;
|
|
19
|
+
export type RequestQuery = <%~ (query && query.type) || '{}' %>;
|
|
20
|
+
export type RequestBody = <%~ (payload && payload.type) || 'never' %>;
|
|
21
|
+
export type RequestHeaders = <%~ (headers && headers.type) || '{}' %>;
|
|
22
|
+
export type ResponseBody = <%~ route.response.type %>;
|
|
23
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<%
|
|
2
|
+
const { utils, config, route, modelTypes } = it;
|
|
3
|
+
const { _, pascalCase } = utils;
|
|
4
|
+
const { routes, moduleName } = route;
|
|
5
|
+
const dataContracts = config.modular ? _.map(modelTypes, "name") : [];
|
|
6
|
+
|
|
7
|
+
%>
|
|
8
|
+
<% if (dataContracts.length) { %>
|
|
9
|
+
import { <%~ dataContracts.join(", ") %> } from "./<%~ config.fileNames.dataContracts %>"
|
|
10
|
+
<% } %>
|
|
11
|
+
|
|
12
|
+
export namespace <%~ pascalCase(moduleName) %> {
|
|
13
|
+
<% for (const route of routes) { %>
|
|
14
|
+
|
|
15
|
+
<%~ includeFile('./route-type.ejs', { ...it, route }) %>
|
|
16
|
+
|
|
17
|
+
<% } %>
|
|
18
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<%
|
|
2
|
+
const { contract, utils } = it;
|
|
3
|
+
const { formatDescription, require, _ } = utils;
|
|
4
|
+
|
|
5
|
+
%>
|
|
6
|
+
<% if (contract.$content.length) { %>
|
|
7
|
+
export type <%~ contract.name %> = {
|
|
8
|
+
<% for (const field of contract.$content) { %>
|
|
9
|
+
<%~ includeFile('./object-field-jsdoc.ejs', { ...it, field }) %>
|
|
10
|
+
<%~ field.field %>;
|
|
11
|
+
<% } %>
|
|
12
|
+
}<%~ utils.isNeedToAddNull(contract) ? ' | null' : ''%>
|
|
13
|
+
<% } else { %>
|
|
14
|
+
export type <%~ contract.name %> = Record<string, any>;
|
|
15
|
+
<% } %>
|