oip-common 0.4.0 → 0.6.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oip-common",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "A template for cross-platform web applications based on sakai-ng and primeNG",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -8,7 +8,7 @@
8
8
  "template"
9
9
  ],
10
10
  "peerDependencies": {
11
- "@fortawesome/fontawesome-free": "^7.2.0"
11
+ "@angular-architects/module-federation": "^20.0.0"
12
12
  },
13
13
  "author": "Igor Tyulyakov aka g101k",
14
14
  "license": "MIT",
@@ -67,7 +67,7 @@ let config = {
67
67
  fixInvalidTypeNamePrefix: "Type",
68
68
  fixInvalidEnumKeyPrefix: "Value",
69
69
  codeGenConstructs: (constructs) => ({
70
- ...constructs,
70
+ ...constructs
71
71
  }),
72
72
  primitiveTypeConstructs: (constructs) => ({
73
73
  ...constructs,
@@ -84,13 +84,13 @@ let config = {
84
84
  onCreateRouteName: (routeNameInfo, rawRouteInfo) => {
85
85
  const route = rawRouteInfo.route || rawRouteInfo.path || "<unknown route>";
86
86
  const method = rawRouteInfo.method || rawRouteInfo.requestMethod || "<unknown method>";
87
- const tags = Array.isArray(rawRouteInfo.tags) ? rawRouteInfo.tags.join(", ") : (rawRouteInfo.tags || "<none>");
87
+ const tags = Array.isArray(rawRouteInfo.tags) ? rawRouteInfo.tags.join(", ") : rawRouteInfo.tags || "<none>";
88
88
  const moduleName = rawRouteInfo.moduleName;
89
89
 
90
90
  if (!moduleName) {
91
91
  throw new Error(
92
92
  `Invalid API route name for ${method.toUpperCase()} ${route}. Missing moduleName. Tags: ${tags}. ` +
93
- "Add a controller tag/module name or provide explicit operationId.",
93
+ "Add a controller tag/module name or provide explicit operationId."
94
94
  );
95
95
  }
96
96
 
@@ -98,7 +98,7 @@ let config = {
98
98
  throw new Error(
99
99
  `Invalid API route name for ${method.toUpperCase()} ${route}. Generated usage "${routeNameInfo.usage}" ` +
100
100
  `equals moduleName "${moduleName}". Add an action segment in controller route, ` +
101
- `e.g. [HttpGet("get-${moduleName}")], or provide explicit operationId.`,
101
+ `e.g. [HttpGet("get-${moduleName}")], or provide explicit operationId.`
102
102
  );
103
103
  }
104
104
 
@@ -8,6 +8,7 @@ const { apiConfig, generateResponses, config } = it;
8
8
  import { LayoutService } from "../services/app.layout.service";
9
9
  import { SecurityService } from "../services/security.service";
10
10
  import { inject, Injectable } from "@angular/core";
11
+ import { ActivatedRoute, Router } from "@angular/router";
11
12
  import { firstValueFrom } from "rxjs";
12
13
 
13
14
  export type QueryParamsType = Record<string | number, any>;
@@ -61,16 +62,41 @@ export enum ContentType {
61
62
  export class HttpClient<SecurityDataType = unknown> {
62
63
  protected securityService = inject(SecurityService);
63
64
  protected layoutService = inject(LayoutService);
65
+ protected router = inject(Router);
64
66
  public baseUrl: string = "<%~ apiConfig.baseUrl %>";
65
67
  private securityWorker?: ApiConfig<SecurityDataType>["securityWorker"] =
66
- () => ({
67
- headers: {
68
- "Accept-language": this.layoutService.language()
69
- ? this.layoutService.language()
70
- : 'en',
71
- "X-Timezone": this.layoutService.timeZone(),
72
- },
73
- });
68
+ () => {
69
+ const moduleInstanceId = this.getCurrentModuleInstanceId();
70
+ return {
71
+ headers: {
72
+ "Accept-language": this.layoutService.language()
73
+ ? this.layoutService.language()
74
+ : 'en',
75
+ "X-Timezone": this.layoutService.timeZone(),
76
+ ...(moduleInstanceId != null
77
+ ? { "X-Module-Instance-Id": String(moduleInstanceId) }
78
+ : {}),
79
+ },
80
+ };
81
+ };
82
+
83
+ /**
84
+ * Reads the module instance id of the deepest activated route.
85
+ * The backend uses it to check module instance rights on endpoints that do not carry the id in their contract.
86
+ */
87
+ protected getCurrentModuleInstanceId(): number | undefined {
88
+ // May run before the first navigation completes, when route snapshots are not available yet.
89
+ let route: ActivatedRoute | null | undefined = this.router.routerState?.root;
90
+ let id: string | null = null;
91
+
92
+ while (route) {
93
+ id = route.snapshot?.paramMap?.get("id") ?? id;
94
+ route = route.firstChild;
95
+ }
96
+
97
+ const parsed = id != null ? Number(id) : Number.NaN;
98
+ return Number.isFinite(parsed) ? parsed : undefined;
99
+ }
74
100
 
75
101
  private abortControllers = new Map<CancelToken, AbortController>();
76
102
  private customFetch = (...fetchParams: Parameters<typeof fetch>) => fetch(...fetchParams);
@@ -238,7 +264,10 @@ export class HttpClient<SecurityDataType = unknown> {
238
264
  }
239
265
 
240
266
  <% if (!config.disableThrowOnError) { %>
241
- if (!response.ok) throw data;
267
+ if (!response.ok) {
268
+ this.authorizeOnUnauthorized(response, path);
269
+ throw data;
270
+ }
242
271
  <% } %>
243
272
  <% if (config.unwrapResponseData) { %>
244
273
  return data.data;
@@ -248,6 +277,16 @@ export class HttpClient<SecurityDataType = unknown> {
248
277
  });
249
278
  };
250
279
 
280
+ private authorizeOnUnauthorized(response: Response, path: string): void {
281
+ if (response.status !== 401 || path.includes("/api/security/create-auth-session")) {
282
+ return;
283
+ }
284
+
285
+ this.securityService.authorize(
286
+ `${window.location.pathname}${window.location.search}${window.location.hash}`,
287
+ );
288
+ }
289
+
251
290
  private async getCsrfRequestParams(method: string | undefined, path: string): Promise<RequestParams> {
252
291
  if (!method || !["POST", "PUT", "PATCH", "DELETE"].includes(method.toUpperCase())) {
253
292
  return {};
@@ -1,23 +0,0 @@
1
- {
2
- "app-modules": {
3
- "title": "Modules",
4
- "refreshTooltip": "Refresh",
5
- "table": {
6
- "moduleId": "Module ID",
7
- "name": "Name",
8
- "currentlyLoaded": "Currently Loaded",
9
- "yes": "Yes",
10
- "no": "No",
11
- "deleteTooltip": "Delete"
12
- },
13
- "confirm": {
14
- "header": "Warning",
15
- "message": "Are you sure you want to delete the module?",
16
- "cancel": "Cancel",
17
- "delete": "Delete"
18
- },
19
- "messages": {
20
- "deleteSuccess": "Module deleted"
21
- }
22
- }
23
- }
@@ -1,23 +0,0 @@
1
- {
2
- "app-modules": {
3
- "title": "Модули",
4
- "refreshTooltip": "Обновить",
5
- "table": {
6
- "moduleId": "ID модуля",
7
- "name": "Название",
8
- "currentlyLoaded": "Загружен",
9
- "yes": "Да",
10
- "no": "Нет",
11
- "deleteTooltip": "Удалить"
12
- },
13
- "confirm": {
14
- "header": "Внимание",
15
- "message": "Вы уверены, что хотите удалить модуль?",
16
- "cancel": "Отмена",
17
- "delete": "Удалить"
18
- },
19
- "messages": {
20
- "deleteSuccess": "Модуль удален"
21
- }
22
- }
23
- }
@@ -1,46 +0,0 @@
1
- {
2
- "applications": {
3
- "title": "Applications",
4
- "subtitle": "Manage the frontend application registry",
5
- "searchPlaceholder": "Search applications",
6
- "search": "Search",
7
- "clear": "Clear",
8
- "add": "Add",
9
- "refreshTooltip": "Refresh",
10
- "empty": "No applications found",
11
- "table": {
12
- "code": "Code",
13
- "displayName": "Name",
14
- "baseUrl": "Base URL",
15
- "internalBaseUrl": "Internal Base URL",
16
- "icon": "Icon",
17
- "order": "Order",
18
- "enabled": "Enabled",
19
- "serviceType": "Service type",
20
- "current": "Current",
21
- "actions": "Actions",
22
- "yes": "Yes",
23
- "no": "No",
24
- "editTooltip": "Edit",
25
- "deleteTooltip": "Delete",
26
- "saveTooltip": "Save",
27
- "cancelTooltip": "Cancel"
28
- },
29
- "serviceTypes": {
30
- "service": "Service",
31
- "application": "Application"
32
- },
33
- "confirm": {
34
- "header": "Warning",
35
- "message": "Are you sure you want to delete application {{displayName}}?",
36
- "cancel": "Cancel",
37
- "delete": "Delete"
38
- },
39
- "messages": {
40
- "requiredFields": "Fill code, name and Base URL",
41
- "createSuccess": "Application created",
42
- "updateSuccess": "Application updated",
43
- "deleteSuccess": "Application deleted"
44
- }
45
- }
46
- }
@@ -1,46 +0,0 @@
1
- {
2
- "applications": {
3
- "title": "Приложения",
4
- "subtitle": "Управление реестром фронтенд-приложений",
5
- "searchPlaceholder": "Поиск по приложениям",
6
- "search": "Найти",
7
- "clear": "Очистить",
8
- "add": "Добавить",
9
- "refreshTooltip": "Обновить",
10
- "empty": "Приложения не найдены",
11
- "table": {
12
- "code": "Код",
13
- "displayName": "Название",
14
- "baseUrl": "Base URL",
15
- "internalBaseUrl": "Internal Base URL",
16
- "icon": "Иконка",
17
- "order": "Порядок",
18
- "enabled": "Включено",
19
- "serviceType": "Тип сервиса",
20
- "current": "Текущее",
21
- "actions": "Действия",
22
- "yes": "Да",
23
- "no": "Нет",
24
- "editTooltip": "Редактировать",
25
- "deleteTooltip": "Удалить",
26
- "saveTooltip": "Сохранить",
27
- "cancelTooltip": "Отменить"
28
- },
29
- "serviceTypes": {
30
- "service": "Сервис",
31
- "application": "Приложение"
32
- },
33
- "confirm": {
34
- "header": "Внимание",
35
- "message": "Вы уверены, что хотите удалить приложение {{displayName}}?",
36
- "cancel": "Отмена",
37
- "delete": "Удалить"
38
- },
39
- "messages": {
40
- "requiredFields": "Заполните код, название и Base URL",
41
- "createSuccess": "Приложение создано",
42
- "updateSuccess": "Приложение обновлено",
43
- "deleteSuccess": "Приложение удалено"
44
- }
45
- }
46
- }
@@ -1,18 +0,0 @@
1
- {
2
- "config": {
3
- "all": "All",
4
- "applicationManagement": "Application management",
5
- "dateFormat": "Date format",
6
- "dateTimeFormat": "Date and time format:",
7
- "goTo": "Go to",
8
- "localization": "Localization",
9
- "menu": "Menu",
10
- "moduleManagement": "Module management",
11
- "photo": "Photo",
12
- "profile": "Profile",
13
- "selectLanguage": "Select language",
14
- "timeFormat": "Time format",
15
- "timeZone": "Time zone",
16
- "usePhoto256x256Pixel": "Use photo 256x256 pixel"
17
- }
18
- }
@@ -1,18 +0,0 @@
1
- {
2
- "config": {
3
- "all": "Все",
4
- "applicationManagement": "Управление приложениями",
5
- "dateFormat": "Формат даты",
6
- "dateTimeFormat": "Формат даты и времени:",
7
- "goTo": "Перейти",
8
- "localization": "Локализация",
9
- "menu": "Меню",
10
- "moduleManagement": "Управление модулями",
11
- "photo": "Фото",
12
- "profile": "Профиль",
13
- "selectLanguage": "Выберите язык",
14
- "timeFormat": "Формат времени",
15
- "timeZone": "Часовой пояс",
16
- "usePhoto256x256Pixel": "Используйте фото 256x256 пикселей"
17
- }
18
- }
@@ -1,19 +0,0 @@
1
- {
2
- "db-migration": {
3
- "migrationManager": "Migration manager",
4
- "actions": {
5
- "refresh": "Refresh",
6
- "cleanFilter": "Clean filter",
7
- "applyMigration": "Apply migration"
8
- },
9
- "columns": {
10
- "name": "Migration name",
11
- "applied": "Applied",
12
- "exist": "Exist",
13
- "pending": "Pending"
14
- },
15
- "messages": {
16
- "errorRefreshing": "Error refreshing database"
17
- }
18
- }
19
- }
@@ -1,19 +0,0 @@
1
- {
2
- "db-migration": {
3
- "migrationManager": "Менеджер миграций",
4
- "actions": {
5
- "refresh": "Обновить",
6
- "cleanFilter": "Очистить фильтр",
7
- "applyMigration": "Применить миграцию"
8
- },
9
- "columns": {
10
- "name": "Название миграции",
11
- "applied": "Применена",
12
- "exist": "Существует",
13
- "pending": "Ожидает"
14
- },
15
- "messages": {
16
- "errorRefreshing": "Ошибка обновления базы данных"
17
- }
18
- }
19
- }
@@ -1,137 +0,0 @@
1
- {
2
- "app-configurator": {
3
- "primary": "Primary",
4
- "surface": "Surface",
5
- "presets": "Presets",
6
- "menuMode": "Menu Mode"
7
- },
8
- "msgService": {
9
- "success": "Success",
10
- "info": "Info",
11
- "warn": "Warn",
12
- "error": "Error",
13
- "secondary": "Secondary"
14
- },
15
- "securityComponent": {
16
- "security": "Security",
17
- "save": "Save",
18
- "savedSecurity": "Security settings saved",
19
- "selectRoles": "Select roles"
20
- },
21
- "profileComponent": {
22
- "changePhoto": "Upload Photo",
23
- "successfullyUploaded": "Uploaded successfully"
24
- },
25
- "configComponent": {
26
- "profile": "Profile",
27
- "photo": "Photo",
28
- "usePhoto256x256Pixel": "Use a 256x256 pixel photo",
29
- "localization": "Language",
30
- "selectLanguage": "Select language"
31
- },
32
- "baseComponent": {
33
- "content": "Content",
34
- "settings": "Settings",
35
- "security": "Security",
36
- "success": "Saved successfully"
37
- },
38
- "menuComponent": {
39
- "all": "All",
40
- "new": "Add"
41
- },
42
- "menuItemEditDialogComponent": {
43
- "header": "Edit menu item",
44
- "parentLabel": "Parent item",
45
- "label": "Label",
46
- "module": "Module",
47
- "selectModule": "Select module",
48
- "icon": "Icon",
49
- "cancel": "Cancel",
50
- "save": "Save",
51
- "security": "Read access"
52
- },
53
- "menuItemCreateDialogComponent": {
54
- "header": "New menu item",
55
- "parentLabel": "Parent item",
56
- "label": "Label",
57
- "module": "Module",
58
- "selectModule": "Select module",
59
- "icon": "Icon",
60
- "cancel": "Cancel",
61
- "save": "Save"
62
- },
63
- "unauthorized": {
64
- "welcomeToOip": "Welcome to OIP!",
65
- "signInToContinue": "Sign in to continue",
66
- "signIn": "Sign In"
67
- },
68
- "menuItemComponent": {
69
- "new": "Add",
70
- "edit": "Edit",
71
- "delete": "Delete",
72
- "deleteItemConfirmHeader": "Delete",
73
- "deleteItemConfirmMessage": "Are you sure you want to delete the item?",
74
- "deleteItemSuccessMessage": "Deleted successfully",
75
- "deleteItemConfirmRejectButtonPropsLabel": "Cancel",
76
- "deleteItemConfirmAcceptButtonPropsLabel": "Delete",
77
- "moveUp": "Move up",
78
- "moveDown": "Move down"
79
- },
80
- "topbar": {
81
- "logout": "Logout",
82
- "logoutConfirmHeader": "Logout",
83
- "logoutConfirmMessage": "Are you sure you want to logout?",
84
- "logoutConfirmCancel": "Cancel",
85
- "logoutConfirmAccept": "Logout",
86
- "profile": "Profile",
87
- "applications": "Applications"
88
- },
89
- "userNotifications": {
90
- "title": "Notifications",
91
- "empty": "No unread notifications",
92
- "refresh": "Refresh",
93
- "markAsRead": "Read",
94
- "markedAsRead": "Notification marked as read"
95
- },
96
- "discussionComponent": {
97
- "writeCommentPlaceholder": "Write a comment. Use @email or @first.last for mentions.",
98
- "mentionSuggestions": "Mention suggestions",
99
- "edit": "Edit",
100
- "preview": "Preview",
101
- "attachFiles": "Attach files",
102
- "send": "Send",
103
- "loadingComments": "Loading comments...",
104
- "noCommentsYet": "No comments yet.",
105
- "edited": "Edited",
106
- "editCommentPlaceholder": "Edit comment",
107
- "addAttachment": "Add attachment",
108
- "save": "Save",
109
- "cancel": "Cancel",
110
- "editHistory": "Edit history",
111
- "loadingHistory": "Loading history...",
112
- "before": "Before",
113
- "after": "After",
114
- "warning": "Warning",
115
- "delete": "Delete",
116
- "confirmDeleteComment": "Are you sure you want to delete this comment?",
117
- "confirmDeleteFile": "Are you sure you want to delete the file \"{{fileName}}\"?",
118
- "units": {
119
- "bytes": "B",
120
- "kilobytes": "KB",
121
- "megabytes": "MB"
122
- },
123
- "errors": {
124
- "loadComments": "Failed to load comments.",
125
- "createComment": "Failed to create comment.",
126
- "updateComment": "Failed to update comment.",
127
- "loadEditHistory": "Failed to load edit history.",
128
- "uploadAttachment": "Failed to upload attachment.",
129
- "deleteComment": "Failed to delete comment.",
130
- "deleteAttachment": "Failed to delete attachment.",
131
- "downloadAttachment": "Failed to download attachment.",
132
- "updateReaction": "Failed to update reaction.",
133
- "addReaction": "Failed to add reaction."
134
- }
135
- },
136
- "primeng": ""
137
- }
@@ -1,10 +0,0 @@
1
- {
2
- "iframe-module": {
3
- "iframeModule": {
4
- "urlPlaceholder": "Site URL",
5
- "settingSaveButtonLabel": "Save",
6
- "emptyUrlMessage": "Site URL is not specified.",
7
- "siteLoadingMessage": "Failed to load the site."
8
- }
9
- }
10
- }
@@ -1,10 +0,0 @@
1
- {
2
- "iframe-module": {
3
- "iframeModule": {
4
- "urlPlaceholder": "URL сайта",
5
- "settingSaveButtonLabel": "Сохранить",
6
- "emptyUrlMessage": "URL сайта не указан.",
7
- "siteLoadingMessage": "Не удалось загрузить сайт."
8
- }
9
- }
10
- }
@@ -1,8 +0,0 @@
1
- {
2
- "notfound": {
3
- "title": "Not Found",
4
- "errorCode": "404",
5
- "description": "Requested resource is not available.",
6
- "button": "Go to home"
7
- }
8
- }
@@ -1,8 +0,0 @@
1
- {
2
- "notfound": {
3
- "title": "Не найдено",
4
- "errorCode": "404",
5
- "description": "Запрашиваемый ресурс недоступен.",
6
- "button": "На главную"
7
- }
8
- }