@ozdao/martyrs 0.2.563 → 0.2.565
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/dist/abac-BPl9Bmf9.js +1527 -0
- package/dist/builder.js +51 -39
- package/dist/{common.schema-GFSlNJo7.js → common.schema-DswiUXKB.js} +1 -1
- package/dist/community.server.js +48 -9
- package/dist/core.server.js +6 -4
- package/dist/{crud-C7FSTUes.js → crud-q1ye5IhV.js} +7 -7
- package/dist/events.server.js +3 -3
- package/dist/gallery.server.js +2 -2
- package/dist/inventory.server.js +4 -6
- package/dist/{main-CmjWiDVF.js → main-B9o1iBAZ.js} +1279 -1287
- package/dist/marketplace.server.js +1 -1
- package/dist/martyrs/src/components/Button/Button.vue2.js +33 -42
- package/dist/martyrs/src/components/Button/Button.vue2.js.map +1 -1
- package/dist/martyrs/src/components/EditImages/{EditImages.vue.js → EditImages.vue2.js} +2 -2
- package/dist/martyrs/src/components/EditImages/EditImages.vue2.js.map +1 -0
- package/dist/martyrs/src/components/Feed/Feed.vue.js +1 -1
- package/dist/martyrs/src/components/FieldPhone/FieldPhone.vue.js +1 -1
- package/dist/martyrs/src/components/FieldPhone/FieldPhone.vue.js.map +1 -1
- package/dist/martyrs/src/components/Loader/Loader.vue.js +1 -2
- package/dist/martyrs/src/components/Loader/Loader.vue.js.map +1 -1
- package/dist/martyrs/src/components/Menu/{Menu.vue.js → Menu.vue2.js} +2 -2
- package/dist/martyrs/src/components/Menu/Menu.vue2.js.map +1 -0
- package/dist/martyrs/src/components/Tab/{Tab.vue.js → Tab.vue2.js} +2 -2
- package/dist/martyrs/src/components/Tab/Tab.vue2.js.map +1 -0
- package/dist/martyrs/src/components/Tree/Tree.vue.js +6 -3
- package/dist/martyrs/src/components/Tree/Tree.vue.js.map +1 -1
- package/dist/martyrs/src/modules/auth/auth.client.js +10 -7
- package/dist/martyrs/src/modules/auth/auth.client.js.map +1 -1
- package/dist/martyrs/src/modules/auth/views/components/pages/EnterPassword.vue.js +1 -1
- package/dist/martyrs/src/modules/auth/views/components/pages/Invite.vue.js +1 -1
- package/dist/martyrs/src/modules/auth/views/components/pages/Profile.vue.js +1 -1
- package/dist/martyrs/src/modules/auth/views/components/pages/ProfileBlogposts.vue.js +1 -1
- package/dist/martyrs/src/modules/auth/views/components/pages/ResetPassword.vue.js +1 -1
- package/dist/martyrs/src/modules/auth/views/components/pages/SignIn.vue.js +12 -12
- package/dist/martyrs/src/modules/auth/views/components/pages/SignIn.vue.js.map +1 -1
- package/dist/martyrs/src/modules/auth/views/components/pages/SignUp.vue.js +1 -1
- package/dist/martyrs/src/modules/auth/views/router/auth.router.js +116 -0
- package/dist/martyrs/src/modules/auth/views/router/auth.router.js.map +1 -0
- package/dist/martyrs/src/modules/auth/views/router/users.router.js +180 -0
- package/dist/martyrs/src/modules/auth/views/router/users.router.js.map +1 -0
- package/dist/martyrs/src/modules/backoffice/components/partials/Sidebar.vue.js +3 -3
- package/dist/martyrs/src/modules/backoffice/components/partials/Sidebar.vue.js.map +1 -1
- package/dist/martyrs/src/modules/core/locales/en.js +45 -0
- package/dist/martyrs/src/modules/core/locales/en.js.map +1 -1
- package/dist/martyrs/src/modules/core/locales/ru.js +45 -0
- package/dist/martyrs/src/modules/core/locales/ru.js.map +1 -1
- package/dist/martyrs/src/modules/core/views/classes/i18n.manager.js +9 -0
- package/dist/martyrs/src/modules/core/views/classes/i18n.manager.js.map +1 -1
- package/dist/martyrs/src/modules/core/views/components/sections/{Filters.vue.js → Filters.vue2.js} +2 -2
- package/dist/martyrs/src/modules/core/views/components/sections/Filters.vue2.js.map +1 -0
- package/dist/martyrs/src/modules/core/views/components/sections/SectionPageTitle.vue.js +1 -1
- package/dist/martyrs/src/modules/core/views/mixins/mixins.js +1 -2
- package/dist/martyrs/src/modules/core/views/mixins/mixins.js.map +1 -1
- package/dist/martyrs/src/modules/core/views/router/addRoutes.js +6 -1
- package/dist/martyrs/src/modules/core/views/router/addRoutes.js.map +1 -1
- package/dist/martyrs/src/modules/events/components/pages/EditEvent.vue.js +1 -1
- package/dist/martyrs/src/modules/events/components/pages/Event.vue.js +1 -1
- package/dist/martyrs/src/modules/events/components/pages/EventsBackoffice.vue.js +1 -1
- package/dist/martyrs/src/modules/gallery/components/sections/BackofficeGallery.vue.js +1 -1
- package/dist/martyrs/src/modules/inventory/components/pages/InventoryEdit.vue.js +2 -2
- package/dist/martyrs/src/modules/inventory/components/pages/InventoryEdit.vue.js.map +1 -1
- package/dist/martyrs/src/modules/marketplace/views/components/pages/Marketplace.vue.js +1 -1
- package/dist/martyrs/src/modules/marketplace/views/store/marketplace.js +0 -16
- package/dist/martyrs/src/modules/marketplace/views/store/marketplace.js.map +1 -1
- package/dist/martyrs/src/modules/notifications/components/elements/NotificationBadge.vue.js +4 -4
- package/dist/martyrs/src/modules/notifications/components/elements/NotificationBadge.vue.js.map +1 -1
- package/dist/martyrs/src/modules/notifications/components/pages/Notifications.vue.js +1 -1
- package/dist/martyrs/src/modules/orders/components/elements/FieldSubscribeNewsletter.vue.js +3 -0
- package/dist/martyrs/src/modules/orders/components/elements/FieldSubscribeNewsletter.vue.js.map +1 -1
- package/dist/martyrs/src/modules/orders/components/pages/OrderCreateBackoffice.vue.js +1 -1
- package/dist/martyrs/src/modules/orders/components/pages/Orders.vue.js +1 -1
- package/dist/martyrs/src/modules/orders/components/sections/FormDelivery.vue.js +1 -1
- package/dist/martyrs/src/modules/organizations/components/blocks/CardOrganization.vue.js +1 -1
- package/dist/martyrs/src/modules/organizations/components/blocks/CardOrganization.vue.js.map +1 -1
- package/dist/martyrs/src/modules/organizations/components/pages/Organization.vue.js +1 -1
- package/dist/martyrs/src/modules/organizations/components/pages/OrganizationBackoffice.vue.js +1 -1
- package/dist/martyrs/src/modules/organizations/components/pages/OrganizationEdit.vue.js +2 -2
- package/dist/martyrs/src/modules/organizations/components/pages/OrganizationEdit.vue.js.map +1 -1
- package/dist/martyrs/src/modules/organizations/components/pages/Organizations.vue.js +1 -1
- package/dist/martyrs/src/modules/organizations/components/sections/Organizations.vue.js +1 -1
- package/dist/martyrs/src/modules/products/components/blocks/CardCategory.vue.js +1 -1
- package/dist/martyrs/src/modules/products/components/blocks/CardCategory.vue.js.map +1 -1
- package/dist/martyrs/src/modules/products/components/blocks/CardProduct.vue.js +15 -2
- package/dist/martyrs/src/modules/products/components/blocks/CardProduct.vue.js.map +1 -1
- package/dist/martyrs/src/modules/products/components/pages/Categories.vue.js +9 -6
- package/dist/martyrs/src/modules/products/components/pages/Categories.vue.js.map +1 -1
- package/dist/martyrs/src/modules/products/components/pages/CategoryEdit.vue.js +4 -3
- package/dist/martyrs/src/modules/products/components/pages/CategoryEdit.vue.js.map +1 -1
- package/dist/martyrs/src/modules/products/components/pages/Product.vue.js +11 -2
- package/dist/martyrs/src/modules/products/components/pages/Product.vue.js.map +1 -1
- package/dist/martyrs/src/modules/products/components/pages/ProductEdit.vue.js +2 -2
- package/dist/martyrs/src/modules/products/components/pages/Products.vue.js +2 -2
- package/dist/martyrs/src/modules/products/components/sections/EditVariants.vue.js +1 -1
- package/dist/martyrs/src/modules/products/components/sections/SectionProduct.vue.js +11 -8
- package/dist/martyrs/src/modules/products/components/sections/SectionProduct.vue.js.map +1 -1
- package/dist/martyrs/src/modules/rents/views/components/pages/Gant/GanttToolbar.vue.js +1 -1
- package/dist/martyrs/src/modules/rents/views/components/pages/Rents.vue.js +1 -1
- package/dist/martyrs/src/modules/rents/views/components/pages/RentsEdit.vue.js +210 -60
- package/dist/martyrs/src/modules/rents/views/components/pages/RentsEdit.vue.js.map +1 -1
- package/dist/martyrs/src/modules/spots/components/pages/Map.vue.js +3 -3
- package/dist/martyrs/src/modules/spots/components/pages/Map.vue.js.map +1 -1
- package/dist/martyrs/src/modules/spots/components/pages/SpotEdit.vue.js +1 -1
- package/dist/martyrs.css +1 -1
- package/dist/martyrs.es.js +1 -1
- package/dist/music.server.js +11 -12
- package/dist/node_modules/.pnpm/qrcode@1.5.4/node_modules/qrcode/lib/core/utils.js +1 -1
- package/dist/node_modules/.pnpm/qrcode@1.5.4/node_modules/qrcode/lib/renderer/utils.js +1 -1
- package/dist/notifications.server.js +0 -3
- package/dist/orders.server.js +5 -6
- package/dist/organizations.server.js +9 -10
- package/dist/products.server.js +27 -26
- package/dist/{queryProcessor-CBQgZycY.js → queryProcessor-C_5Iipam.js} +4 -1
- package/dist/rents.server.js +2 -3
- package/dist/spots.server.js +1 -1
- package/dist/style.css +38 -23
- package/dist/{web-cNKIl_cL.js → web-BF3ijvEr.js} +1 -1
- package/package.json +1 -1
- package/src/builder/modes/ssr.rspack.dev.js +4 -3
- package/src/builder/rspack/rspack.config.api.js +15 -4
- package/src/builder/rspack/rspack.config.base.js +3 -3
- package/src/builder/rspack/rspack.config.ssr.client.js +28 -28
- package/src/builder/templates/page.js +2 -2
- package/src/components/Button/Button.vue +50 -37
- package/src/components/FieldPhone/FieldPhone.vue +1 -1
- package/src/components/Loader/Loader.vue +1 -1
- package/src/components/Tree/Tree.vue +6 -3
- package/src/modules/PROCESS.md +0 -0
- package/src/modules/TASKS.MD +17 -0
- package/src/modules/auth/auth.client.js +11 -7
- package/src/modules/auth/views/components/pages/SignIn.vue +1 -1
- package/src/modules/auth/views/router/auth.router.js +94 -0
- package/src/modules/auth/views/router/users.router.js +153 -0
- package/src/modules/backoffice/components/partials/Sidebar.vue +7 -7
- package/src/modules/community/community.server.js +8 -0
- package/src/modules/community/policies/blog.policies.js +55 -0
- package/src/modules/community/routes/blog.routes.js +1 -1
- package/src/modules/community/routes/comments.routes.js +1 -1
- package/src/modules/community/routes/reactions.routes.js +1 -4
- package/src/modules/core/controllers/classes/abac/abac.adapter.express.js +206 -124
- package/src/modules/core/controllers/classes/abac/abac.adapter.ws.js +203 -50
- package/src/modules/core/controllers/classes/abac/abac.core.js +127 -36
- package/src/modules/core/controllers/classes/abac/abac.fields.js +144 -179
- package/src/modules/core/controllers/classes/abac/abac.js +201 -10
- package/src/modules/core/controllers/classes/abac/abac.policies.js +147 -57
- package/src/modules/core/controllers/classes/crud/crud.policies.js +5 -5
- package/src/modules/core/controllers/policies/core.policies.js +5 -2
- package/src/modules/core/controllers/utils/queryProcessor.js +4 -1
- package/src/modules/core/core.server.js +1 -0
- package/src/modules/core/locales/en.js +45 -0
- package/src/modules/core/locales/ru.js +45 -0
- package/src/modules/core/models/schemas/common.schema.js +1 -1
- package/src/modules/core/views/classes/i18n.manager.js +13 -0
- package/src/modules/core/views/components/sections/filters/FilterPrice.vue +81 -0
- package/src/modules/core/views/mixins/mixins.js +1 -2
- package/src/modules/core/views/router/addRoutes.js +6 -1
- package/src/modules/events/routes/events.routes.js +1 -1
- package/src/modules/inventory/components/pages/InventoryEdit.vue +3 -3
- package/src/modules/inventory/policies/inventory.policies.js +1 -1
- package/src/modules/inventory/routes/inventory.routes.js +1 -1
- package/src/modules/marketplace/marketplace.router.js +66 -0
- package/src/modules/marketplace/views/components/layouts/Marketplace.vue +363 -0
- package/src/modules/marketplace/views/components/pages/Catalog.vue +73 -0
- package/src/modules/marketplace/views/store/marketplace.js +0 -16
- package/src/modules/music/controllers/stream.controller.js +1 -1
- package/src/modules/music/music.server.js +1 -1
- package/src/modules/music/policies/music.policies.js +3 -2
- package/src/modules/music/router/library.router.js +26 -0
- package/src/modules/music/router/music.router.js +176 -0
- package/src/modules/notifications/components/elements/NotificationBadge.vue +5 -6
- package/src/modules/notifications/notifications.server.js +1 -3
- package/src/modules/orders/components/elements/FieldSubscribeNewsletter.vue +5 -0
- package/src/modules/orders/orders.server.js +0 -1
- package/src/modules/organizations/components/blocks/CardOrganization.vue +2 -2
- package/src/modules/organizations/components/pages/DepartmentEdit.vue +2 -2
- package/src/modules/organizations/components/pages/OrganizationEdit.vue +2 -2
- package/src/modules/organizations/policies/organizations.policies.js +12 -6
- package/src/modules/organizations/routes/organizations.routes.js +1 -3
- package/src/modules/products/components/blocks/CardCategory.vue +1 -1
- package/src/modules/products/components/blocks/CardProduct.vue +16 -2
- package/src/modules/products/components/pages/Categories.vue +9 -6
- package/src/modules/products/components/pages/CategoryEdit.vue +8 -4
- package/src/modules/products/components/pages/Product.vue +11 -5
- package/src/modules/products/components/sections/SectionProduct.vue +11 -7
- package/src/modules/products/controllers/categories.controller.js +32 -27
- package/src/modules/products/routes/categories.routes.js +1 -1
- package/src/modules/rents/controllers/routes/rents.routes.js +1 -1
- package/src/modules/rents/views/components/pages/RentsEdit.vue +208 -49
- package/src/modules/spots/components/pages/Map.vue +2 -2
- package/dist/abac-DYoheWuc.js +0 -1031
- package/dist/core.abac-DUPBnlk6.js +0 -298
- package/dist/core.logger-C3q8A9dl.js +0 -51
- package/dist/martyrs/src/components/EditImages/EditImages.vue.js.map +0 -1
- package/dist/martyrs/src/components/Menu/Menu.vue.js.map +0 -1
- package/dist/martyrs/src/components/Tab/Tab.vue.js.map +0 -1
- package/dist/martyrs/src/modules/auth/auth.router.js +0 -342
- package/dist/martyrs/src/modules/auth/auth.router.js.map +0 -1
- package/dist/martyrs/src/modules/core/views/components/sections/Filters.vue.js.map +0 -1
- package/src/modules/auth/auth.router.js +0 -262
- package/src/modules/core/controllers/classes/abac/v2/abac-core-fixed.js +0 -313
- package/src/modules/core/controllers/classes/abac/v2/abac-express-fixed.js +0 -276
- package/src/modules/core/controllers/classes/abac/v2/abac-fields-fixed.js +0 -425
- package/src/modules/core/controllers/classes/abac/v2/abac-main-fixed.js +0 -295
- package/src/modules/core/controllers/classes/abac/v2/abac-policies-fixed.js +0 -316
- package/src/modules/core/controllers/classes/abac/v2/abac-ws-fixed.js +0 -237
- package/src/modules/core/controllers/classes/core.abac.js +0 -310
- package/src/modules/core/controllers/classes/core.crud.js +0 -89
- package/src/modules/governance/reactcode/eslint.config.js +0 -28
|
@@ -1,237 +0,0 @@
|
|
|
1
|
-
// @martyrs/src/modules/core/controllers/classes/abac/abac.adapter.ws.js
|
|
2
|
-
export default class ABACWebSocketAdapter {
|
|
3
|
-
constructor(abac) {
|
|
4
|
-
this.abac = abac;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* WebSocket handler для проверки доступа
|
|
9
|
-
* @param {string} moduleName - Имя модуля
|
|
10
|
-
* @param {Object} [options] - Опции
|
|
11
|
-
* @returns {Function} Handler функция
|
|
12
|
-
*/
|
|
13
|
-
handler(moduleName, options = {}) {
|
|
14
|
-
return async (ws, message) => {
|
|
15
|
-
try {
|
|
16
|
-
const { action = 'access', resource = moduleName } = options;
|
|
17
|
-
|
|
18
|
-
const context = this._buildContext(ws, resource, action, message, options);
|
|
19
|
-
const accessResult = await this.abac.checkAccess(context, context.customPolicies);
|
|
20
|
-
|
|
21
|
-
if (!accessResult.allow) {
|
|
22
|
-
await this._sendError(ws, 'ACCESS_DENIED', accessResult.reason);
|
|
23
|
-
return false;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// Проверка полей если включена
|
|
27
|
-
if (options.checkFields && message) {
|
|
28
|
-
const fieldsResult = await this.abac.checkFields(context, message, action);
|
|
29
|
-
|
|
30
|
-
if (fieldsResult.errors.length > 0 && options.strictFieldsMode) {
|
|
31
|
-
await this._sendError(ws, 'FIELD_VALIDATION_ERROR', 'Field validation failed', {
|
|
32
|
-
fields: fieldsResult.errors
|
|
33
|
-
});
|
|
34
|
-
return false;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Возвращаем отфильтрованные данные
|
|
38
|
-
return {
|
|
39
|
-
allowed: true,
|
|
40
|
-
data: fieldsResult.transformed || fieldsResult.allowed,
|
|
41
|
-
fieldsResult
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return true;
|
|
46
|
-
} catch (error) {
|
|
47
|
-
this.abac.logger?.error('WebSocket access control error', {
|
|
48
|
-
module: moduleName,
|
|
49
|
-
error: error.message,
|
|
50
|
-
stack: error.stack
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
await this._sendError(ws, 'INTERNAL_ERROR', 'Access control error');
|
|
54
|
-
return false;
|
|
55
|
-
}
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* RPC handler для вызовов через WebSocket
|
|
61
|
-
* @param {string} module - Модуль
|
|
62
|
-
* @param {string} method - Метод
|
|
63
|
-
* @param {Object} [options] - Опции
|
|
64
|
-
* @returns {Function} RPC handler
|
|
65
|
-
*/
|
|
66
|
-
rpcHandler(module, method, options = {}) {
|
|
67
|
-
return async (params, rpcContext) => {
|
|
68
|
-
try {
|
|
69
|
-
const context = this._buildRPCContext(rpcContext, module, method, params, options);
|
|
70
|
-
const accessResult = await this.abac.checkAccess(context, context.customPolicies);
|
|
71
|
-
|
|
72
|
-
if (!accessResult.allow) {
|
|
73
|
-
throw new RPCError('ACCESS_DENIED', accessResult.reason);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Проверка полей если нужно
|
|
77
|
-
if (options.checkFields && params) {
|
|
78
|
-
const fieldsResult = await this.abac.checkFields(context, params, method);
|
|
79
|
-
|
|
80
|
-
if (fieldsResult.errors.length > 0) {
|
|
81
|
-
if (options.strictFieldsMode) {
|
|
82
|
-
throw new RPCError(
|
|
83
|
-
'FIELD_VALIDATION_ERROR',
|
|
84
|
-
`Fields validation failed: ${fieldsResult.errors.map(e => e.path).join(', ')}`
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Логируем ошибки в нестрогом режиме
|
|
89
|
-
this.abac.logger?.warn('RPC field validation errors', {
|
|
90
|
-
module,
|
|
91
|
-
method,
|
|
92
|
-
errors: fieldsResult.errors
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Возвращаем трансформированные данные
|
|
97
|
-
return fieldsResult.transformed || fieldsResult.allowed;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return params;
|
|
101
|
-
} catch (error) {
|
|
102
|
-
// Пробрасываем RPC ошибки как есть
|
|
103
|
-
if (error instanceof RPCError) {
|
|
104
|
-
throw error;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
this.abac.logger?.error('RPC access control error', {
|
|
108
|
-
module,
|
|
109
|
-
method,
|
|
110
|
-
error: error.message,
|
|
111
|
-
stack: error.stack
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
throw new RPCError('INTERNAL_ERROR', 'Access control error');
|
|
115
|
-
}
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Middleware для обработки WebSocket сообщений
|
|
121
|
-
* @param {Object} [options] - Опции
|
|
122
|
-
* @returns {Function} Middleware функция
|
|
123
|
-
*/
|
|
124
|
-
messageMiddleware(options = {}) {
|
|
125
|
-
return async (ws, message, next) => {
|
|
126
|
-
try {
|
|
127
|
-
const { resource = 'message', action = 'send' } = options;
|
|
128
|
-
const context = this._buildContext(ws, resource, action, message, options);
|
|
129
|
-
|
|
130
|
-
const accessResult = await this.abac.checkAccess(context, options.policies || {});
|
|
131
|
-
|
|
132
|
-
if (!accessResult.allow) {
|
|
133
|
-
await this._sendError(ws, 'ACCESS_DENIED', accessResult.reason);
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Сохраняем результат в контексте WS
|
|
138
|
-
ws.abacContext = context;
|
|
139
|
-
ws.abacAccessResult = accessResult;
|
|
140
|
-
|
|
141
|
-
next();
|
|
142
|
-
} catch (error) {
|
|
143
|
-
this.abac.logger?.error('WebSocket middleware error', {
|
|
144
|
-
error: error.message
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
await this._sendError(ws, 'INTERNAL_ERROR', 'Access control error');
|
|
148
|
-
}
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Построение контекста для WebSocket
|
|
154
|
-
* @private
|
|
155
|
-
*/
|
|
156
|
-
_buildContext(ws, resource, action, data, options) {
|
|
157
|
-
return {
|
|
158
|
-
user: ws.userId || ws.user?.id,
|
|
159
|
-
resource,
|
|
160
|
-
action,
|
|
161
|
-
data: data || {},
|
|
162
|
-
socket: ws,
|
|
163
|
-
customPolicies: options.policies || {},
|
|
164
|
-
options,
|
|
165
|
-
// Дополнительная информация о соединении
|
|
166
|
-
connectionInfo: {
|
|
167
|
-
id: ws.id,
|
|
168
|
-
remoteAddress: ws._socket?.remoteAddress,
|
|
169
|
-
protocol: ws.protocol,
|
|
170
|
-
readyState: ws.readyState
|
|
171
|
-
}
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Построение контекста для RPC
|
|
177
|
-
* @private
|
|
178
|
-
*/
|
|
179
|
-
_buildRPCContext(rpcContext, module, method, params, options) {
|
|
180
|
-
return {
|
|
181
|
-
user: rpcContext.userId || rpcContext.user?.id,
|
|
182
|
-
resource: module,
|
|
183
|
-
action: method,
|
|
184
|
-
data: params || {},
|
|
185
|
-
socket: rpcContext.ws,
|
|
186
|
-
customPolicies: options.policies || {},
|
|
187
|
-
options,
|
|
188
|
-
rpcInfo: {
|
|
189
|
-
id: rpcContext.id,
|
|
190
|
-
module,
|
|
191
|
-
method,
|
|
192
|
-
timestamp: new Date().toISOString()
|
|
193
|
-
}
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* Отправка ошибки через WebSocket
|
|
199
|
-
* @private
|
|
200
|
-
*/
|
|
201
|
-
async _sendError(ws, code, message, details = {}) {
|
|
202
|
-
if (ws.readyState !== 1) { // WebSocket.OPEN
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
try {
|
|
207
|
-
const errorMessage = JSON.stringify({
|
|
208
|
-
type: 'error',
|
|
209
|
-
error: {
|
|
210
|
-
code,
|
|
211
|
-
message,
|
|
212
|
-
...details
|
|
213
|
-
},
|
|
214
|
-
timestamp: new Date().toISOString()
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
ws.send(errorMessage);
|
|
218
|
-
} catch (error) {
|
|
219
|
-
this.abac.logger?.error('Failed to send WebSocket error', {
|
|
220
|
-
originalError: { code, message },
|
|
221
|
-
sendError: error.message
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* Класс для RPC ошибок
|
|
229
|
-
*/
|
|
230
|
-
class RPCError extends Error {
|
|
231
|
-
constructor(code, message, details = {}) {
|
|
232
|
-
super(message);
|
|
233
|
-
this.name = 'RPCError';
|
|
234
|
-
this.code = code;
|
|
235
|
-
this.details = details;
|
|
236
|
-
}
|
|
237
|
-
}
|
|
@@ -1,310 +0,0 @@
|
|
|
1
|
-
class GlobalABAC {
|
|
2
|
-
constructor(db, options = {}) {
|
|
3
|
-
// Основные политики и обработчики
|
|
4
|
-
this.policies = {
|
|
5
|
-
global: {}, // Глобальные политики
|
|
6
|
-
resources: {}, // Политики для ресурсов
|
|
7
|
-
extensions: {}, // Расширения от внешних модулей
|
|
8
|
-
};
|
|
9
|
-
// Настройки по умолчанию
|
|
10
|
-
this.options = {
|
|
11
|
-
strictMode: false,
|
|
12
|
-
defaultDeny: false,
|
|
13
|
-
serviceKey: process.env.SERVICE_KEY, // Добавляем ключ сервиса из env
|
|
14
|
-
...options,
|
|
15
|
-
};
|
|
16
|
-
// Сохраняем ссылку на базу данных
|
|
17
|
-
this.db = db;
|
|
18
|
-
}
|
|
19
|
-
// Регистрация глобальной политики
|
|
20
|
-
registerGlobalPolicy(name, policyFn) {
|
|
21
|
-
this.policies.global[name] = policyFn;
|
|
22
|
-
return this;
|
|
23
|
-
}
|
|
24
|
-
// Регистрация политики для ресурса
|
|
25
|
-
registerResourcePolicy(resourceName, policyFn) {
|
|
26
|
-
this.policies.resources[resourceName] = policyFn;
|
|
27
|
-
return this;
|
|
28
|
-
}
|
|
29
|
-
// Метод для регистрации расширений от внешних модулей
|
|
30
|
-
registerExtension(moduleName, extensionFn) {
|
|
31
|
-
// Если расширение уже существует, объединяем функции
|
|
32
|
-
if (this.policies.extensions[moduleName]) {
|
|
33
|
-
const existingExtension = this.policies.extensions[moduleName];
|
|
34
|
-
this.policies.extensions[moduleName] = async context => {
|
|
35
|
-
const existingResult = await existingExtension(context);
|
|
36
|
-
if (existingResult) return existingResult;
|
|
37
|
-
return await extensionFn(context);
|
|
38
|
-
};
|
|
39
|
-
} else {
|
|
40
|
-
this.policies.extensions[moduleName] = extensionFn;
|
|
41
|
-
}
|
|
42
|
-
return this;
|
|
43
|
-
}
|
|
44
|
-
// Автоматическое определение модели по имени ресурса
|
|
45
|
-
getResourceModel(resourceName) {
|
|
46
|
-
if (resourceName === 'posts') resourceName = 'blogposts';
|
|
47
|
-
// Преобразуем название ресурса в singural форму модели
|
|
48
|
-
const modelName = resourceName.endsWith('s') ? resourceName.slice(0, -1) : resourceName;
|
|
49
|
-
// Ищем модель в базе данных
|
|
50
|
-
const model = this.db[modelName];
|
|
51
|
-
if (!model) {
|
|
52
|
-
throw new Error(`Model for resource ${resourceName} not found`);
|
|
53
|
-
}
|
|
54
|
-
return model;
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Нормализация результата политики для единообразной обработки
|
|
58
|
-
* @param {any} result - Результат выполнения политики
|
|
59
|
-
* @param {string} policyName - Имя политики для формирования причины
|
|
60
|
-
* @returns {Object} Нормализованный результат
|
|
61
|
-
*/
|
|
62
|
-
_normalizeResult(result, policyName) {
|
|
63
|
-
// Результат уже в формате объекта с нужными полями
|
|
64
|
-
if (result && typeof result === 'object' && ('allow' in result || 'force' in result)) {
|
|
65
|
-
return {
|
|
66
|
-
allow: !!result.allow,
|
|
67
|
-
force: !!result.force,
|
|
68
|
-
reason: result.reason || `POLICY_${policyName.toUpperCase()}`,
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
// Boolean результаты
|
|
72
|
-
if (result === true) {
|
|
73
|
-
return { allow: true, force: false, reason: `ALLOWED_BY_${policyName.toUpperCase()}` };
|
|
74
|
-
}
|
|
75
|
-
if (result === false) {
|
|
76
|
-
return { allow: false, force: false, reason: `DENIED_BY_${policyName.toUpperCase()}` };
|
|
77
|
-
}
|
|
78
|
-
// Специальные случаи для adminAccessGranted и подобных
|
|
79
|
-
if (result === undefined && policyName === 'AdminModeratorAccessPolicy' && this._context && this._context.adminAccessGranted) {
|
|
80
|
-
return { allow: true, force: true, reason: 'ADMIN_MODERATOR_ACCESS_GRANTED' };
|
|
81
|
-
}
|
|
82
|
-
// Нейтральный результат (пропуск политики)
|
|
83
|
-
return { allow: true, force: false, reason: `NEUTRAL_${policyName.toUpperCase()}` };
|
|
84
|
-
}
|
|
85
|
-
// Базовый метод проверки доступа
|
|
86
|
-
async checkAccess(context) {
|
|
87
|
-
this._context = context; // Сохраняем контекст для использования в _normalizeResult
|
|
88
|
-
const {
|
|
89
|
-
user, // Пользователь
|
|
90
|
-
resource, // Тип ресурса
|
|
91
|
-
action, // Действие
|
|
92
|
-
data, // Данные ресурса
|
|
93
|
-
options = {}, // Добавляем опции
|
|
94
|
-
isServiceRequest, // Флаг сервисного запроса
|
|
95
|
-
} = context;
|
|
96
|
-
// Если это сервисный запрос, пропускаем проверки
|
|
97
|
-
if (isServiceRequest) {
|
|
98
|
-
return {
|
|
99
|
-
allow: true,
|
|
100
|
-
reason: 'SERVICE_REQUEST_ALLOWED',
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
// Проверка базовой аутентификации
|
|
104
|
-
if (!user && !options.allowUnauthenticated) {
|
|
105
|
-
return {
|
|
106
|
-
allow: false,
|
|
107
|
-
reason: 'UNAUTHENTICATED_ACCESS_DENIED',
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
// Предзагрузка ресурса
|
|
111
|
-
if ((action !== 'create') && (data._id || data.params?._id || data.url)) {
|
|
112
|
-
try {
|
|
113
|
-
const resourceModel = this.getResourceModel(resource);
|
|
114
|
-
let currentResource;
|
|
115
|
-
if (data._id || data.params?._id) {
|
|
116
|
-
currentResource = await resourceModel.findById(data._id || data.params._id);
|
|
117
|
-
} else if (data.url) {
|
|
118
|
-
currentResource = await resourceModel.findOne({ url: data.url });
|
|
119
|
-
}
|
|
120
|
-
if (!currentResource) {
|
|
121
|
-
return {
|
|
122
|
-
allow: false,
|
|
123
|
-
reason: 'RESOURCE_NOT_FOUND',
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
// Добавляем загруженный ресурс в контекст
|
|
127
|
-
context.currentResource = currentResource;
|
|
128
|
-
// Добавляем модель ресурса в контекст
|
|
129
|
-
context.resourceModel = resourceModel;
|
|
130
|
-
} catch (error) {
|
|
131
|
-
console.error('Resource loading error:', error);
|
|
132
|
-
return {
|
|
133
|
-
allow: false,
|
|
134
|
-
reason: 'RESOURCE_LOAD_ERROR',
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
// Выполнение всех глобальных политик параллельно
|
|
139
|
-
const policyEntries = Object.entries(this.policies.global);
|
|
140
|
-
const policyPromises = policyEntries.map(async ([policyName, policyFn]) => {
|
|
141
|
-
try {
|
|
142
|
-
const result = await policyFn(context);
|
|
143
|
-
return { policyName, result };
|
|
144
|
-
} catch (error) {
|
|
145
|
-
console.error(`Error in policy ${policyName}:`, error);
|
|
146
|
-
// При ошибке возвращаем нейтральный результат
|
|
147
|
-
return { policyName, result: undefined, error };
|
|
148
|
-
}
|
|
149
|
-
});
|
|
150
|
-
// Ожидаем выполнения всех политик
|
|
151
|
-
const policyResults = await Promise.all(policyPromises);
|
|
152
|
-
// Обработка результатов глобальных политик
|
|
153
|
-
let hasForceAllow = false;
|
|
154
|
-
let hasForceDisallow = false;
|
|
155
|
-
let hasDeny = false;
|
|
156
|
-
let denyReason = '';
|
|
157
|
-
let allowReason = '';
|
|
158
|
-
for (const { policyName, result, error } of policyResults) {
|
|
159
|
-
if (error) continue; // Пропускаем политики с ошибками
|
|
160
|
-
// Нормализуем результат для унифицированной обработки
|
|
161
|
-
const normalizedResult = this._normalizeResult(result, policyName);
|
|
162
|
-
if (normalizedResult.force) {
|
|
163
|
-
if (normalizedResult.allow) {
|
|
164
|
-
hasForceAllow = true;
|
|
165
|
-
allowReason = normalizedResult.reason;
|
|
166
|
-
} else {
|
|
167
|
-
hasForceDisallow = true;
|
|
168
|
-
denyReason = normalizedResult.reason;
|
|
169
|
-
}
|
|
170
|
-
} else if (!normalizedResult.allow) {
|
|
171
|
-
hasDeny = true;
|
|
172
|
-
if (!denyReason) denyReason = normalizedResult.reason;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
// Принятие решения на основе результатов
|
|
176
|
-
// 1. Проверка на стопроцентный запрет
|
|
177
|
-
if (hasForceDisallow) {
|
|
178
|
-
return {
|
|
179
|
-
allow: false,
|
|
180
|
-
reason: denyReason || 'FORCE_DENIED_BY_POLICY',
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
// 2. Проверка на стопроцентный доступ
|
|
184
|
-
if (hasForceAllow) {
|
|
185
|
-
return {
|
|
186
|
-
allow: true,
|
|
187
|
-
reason: allowReason || 'FORCE_ALLOWED_BY_POLICY',
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
// 3. Проверка на обычный запрет
|
|
191
|
-
if (hasDeny) {
|
|
192
|
-
return {
|
|
193
|
-
allow: false,
|
|
194
|
-
reason: denyReason || 'DENIED_BY_POLICY',
|
|
195
|
-
};
|
|
196
|
-
}
|
|
197
|
-
// 4. Проверка политик ресурсов (возможно асинхронное выполнение)
|
|
198
|
-
if (this.policies.resources[resource]) {
|
|
199
|
-
try {
|
|
200
|
-
const resourceResult = await this.policies.resources[resource](context);
|
|
201
|
-
const normalizedResult = this._normalizeResult(resourceResult, `RESOURCE_${resource}`);
|
|
202
|
-
if (normalizedResult.force) {
|
|
203
|
-
return {
|
|
204
|
-
allow: normalizedResult.allow,
|
|
205
|
-
reason: normalizedResult.reason,
|
|
206
|
-
};
|
|
207
|
-
}
|
|
208
|
-
if (!normalizedResult.allow) {
|
|
209
|
-
return {
|
|
210
|
-
allow: false,
|
|
211
|
-
reason: normalizedResult.reason,
|
|
212
|
-
};
|
|
213
|
-
}
|
|
214
|
-
if (normalizedResult.allow) {
|
|
215
|
-
return {
|
|
216
|
-
allow: true,
|
|
217
|
-
reason: normalizedResult.reason,
|
|
218
|
-
};
|
|
219
|
-
}
|
|
220
|
-
} catch (error) {
|
|
221
|
-
console.error(`Error in resource policy for ${resource}:`, error);
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
// 5. Выполнение расширений от внешних модулей
|
|
225
|
-
const extensionPromises = Object.entries(this.policies.extensions).map(async ([moduleName, extensionFn]) => {
|
|
226
|
-
try {
|
|
227
|
-
const extensionResult = await extensionFn(context);
|
|
228
|
-
return { moduleName, result: extensionResult };
|
|
229
|
-
} catch (error) {
|
|
230
|
-
console.error(`Error in extension ${moduleName}:`, error);
|
|
231
|
-
return { moduleName, result: null, error };
|
|
232
|
-
}
|
|
233
|
-
});
|
|
234
|
-
const extensionResults = await Promise.all(extensionPromises);
|
|
235
|
-
for (const { moduleName, result, error } of extensionResults) {
|
|
236
|
-
if (error) continue;
|
|
237
|
-
if (result && result.allow) {
|
|
238
|
-
return {
|
|
239
|
-
allow: true,
|
|
240
|
-
reason: `ALLOWED_BY_${moduleName.toUpperCase()}_EXTENSION`,
|
|
241
|
-
};
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
// 6. Финальное решение на основе настроек
|
|
245
|
-
return {
|
|
246
|
-
allow: !this.options.defaultDeny,
|
|
247
|
-
reason: this.options.defaultDeny ? 'ACCESS_DENIED' : 'ACCESS_ALLOWED',
|
|
248
|
-
};
|
|
249
|
-
}
|
|
250
|
-
// Проверка сервисного ключа
|
|
251
|
-
validateServiceKey(providedKey) {
|
|
252
|
-
return providedKey && providedKey === this.options.serviceKey;
|
|
253
|
-
}
|
|
254
|
-
// Middleware для Express
|
|
255
|
-
middleware(resource, action, options = {}) {
|
|
256
|
-
return async (req, res, next) => {
|
|
257
|
-
try {
|
|
258
|
-
// Проверка сервисного ключа
|
|
259
|
-
const serviceKey = req.headers['x-service-key'];
|
|
260
|
-
if (serviceKey && this.validateServiceKey(serviceKey)) {
|
|
261
|
-
req.isServiceRequest = true; // Помечаем, что это запрос от другого сервиса
|
|
262
|
-
return next();
|
|
263
|
-
}
|
|
264
|
-
const context = {
|
|
265
|
-
user: req.userId,
|
|
266
|
-
resource,
|
|
267
|
-
action,
|
|
268
|
-
data: {
|
|
269
|
-
...req.body,
|
|
270
|
-
...req.query,
|
|
271
|
-
params: req.params,
|
|
272
|
-
},
|
|
273
|
-
options, // Передаем опции в контекст
|
|
274
|
-
req, // Передаем весь объект запроса для максимальной гибкости
|
|
275
|
-
res, // И объект ответа
|
|
276
|
-
isServiceRequest: req.isServiceRequest, // Передаем флаг сервисного запроса
|
|
277
|
-
};
|
|
278
|
-
const accessResult = await this.checkAccess(context);
|
|
279
|
-
if (context.req?.body) req.body = context.req.body;
|
|
280
|
-
if (context.req?.query) req.query = context.req.query;
|
|
281
|
-
if (context.data?.params) req.params = context.data.params;
|
|
282
|
-
if (accessResult.allow) {
|
|
283
|
-
req.accessResult = accessResult;
|
|
284
|
-
return next();
|
|
285
|
-
}
|
|
286
|
-
return res.status(403).json({
|
|
287
|
-
errorCode: accessResult.reason,
|
|
288
|
-
message: 'Access Denied',
|
|
289
|
-
});
|
|
290
|
-
} catch (error) {
|
|
291
|
-
console.error('Access control error:', error);
|
|
292
|
-
return res.status(500).json({
|
|
293
|
-
errorCode: 'INTERNAL_ACCESS_CONTROL_ERROR',
|
|
294
|
-
message: 'Internal Server Error',
|
|
295
|
-
});
|
|
296
|
-
}
|
|
297
|
-
};
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
// Экспорт синглтона
|
|
301
|
-
let instance = null;
|
|
302
|
-
export const getInstance = (db, options) => {
|
|
303
|
-
if (!instance) {
|
|
304
|
-
instance = new GlobalABAC(db, options);
|
|
305
|
-
}
|
|
306
|
-
return instance;
|
|
307
|
-
};
|
|
308
|
-
export default {
|
|
309
|
-
getInstance,
|
|
310
|
-
};
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import Cache from '@martyrs/src/modules/core/controllers/classes/core.cache.js';
|
|
2
|
-
import Logger from '@martyrs/src/modules/core/controllers/classes/core.logger.js';
|
|
3
|
-
import coreQuery from '@martyrs/src/modules/core/controllers/utils/queryProcessor.js';
|
|
4
|
-
class CRUD {
|
|
5
|
-
constructor(basePath, app, db, model, options) {
|
|
6
|
-
this.model = model;
|
|
7
|
-
this.cache = new Cache();
|
|
8
|
-
this.logger = new Logger(db);
|
|
9
|
-
this.app = app;
|
|
10
|
-
this.basePath = basePath;
|
|
11
|
-
if (!options || (options && !options.disableDefaultRoutes)) this.registerRoutes();
|
|
12
|
-
}
|
|
13
|
-
registerRoutes() {
|
|
14
|
-
this.app.post(`${this.basePath}/create`, this.create.bind(this));
|
|
15
|
-
this.app.get(`${this.basePath}/read`, this.read.bind(this));
|
|
16
|
-
this.app.put(`${this.basePath}/update`, this.update.bind(this));
|
|
17
|
-
this.app.delete(`${this.basePath}/delete`, this.delete.bind(this));
|
|
18
|
-
}
|
|
19
|
-
async create(req, res) {
|
|
20
|
-
try {
|
|
21
|
-
const createdData = await this.model.create(req.body);
|
|
22
|
-
await this.cache.flush();
|
|
23
|
-
res.status(201).json(createdData);
|
|
24
|
-
} catch (error) {
|
|
25
|
-
console.log(error);
|
|
26
|
-
this.logger.error('Ошибка создания данных', error);
|
|
27
|
-
res.status(500).json({ error: error.message });
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
async read(req, res) {
|
|
31
|
-
try {
|
|
32
|
-
let stages = [];
|
|
33
|
-
stages = [
|
|
34
|
-
...coreQuery.getBasicOptions(req.query),
|
|
35
|
-
// For creator
|
|
36
|
-
coreQuery.getCreatorUserLookupStage(),
|
|
37
|
-
coreQuery.getCreatorOrganizationLookupStage(),
|
|
38
|
-
// For owner
|
|
39
|
-
coreQuery.getOwnerUserLookupStage(),
|
|
40
|
-
coreQuery.getOwnerOrganizationLookupStage(),
|
|
41
|
-
coreQuery.getAddFieldsCreatorOwnerStage(),
|
|
42
|
-
// Pagination
|
|
43
|
-
...coreQuery.getSortingOptions(req.query.sortParam, req.query.sortOrder),
|
|
44
|
-
...coreQuery.getPaginationOptions(req.query.skip, req.query.limit),
|
|
45
|
-
coreQuery.removeTempPropeties(),
|
|
46
|
-
];
|
|
47
|
-
const cacheKey = JSON.stringify({ stages });
|
|
48
|
-
let data = await this.cache.get(cacheKey);
|
|
49
|
-
if (!data) {
|
|
50
|
-
data = await this.model.aggregate(stages).exec();
|
|
51
|
-
await this.cache.set(cacheKey, data);
|
|
52
|
-
}
|
|
53
|
-
res.json(data);
|
|
54
|
-
} catch (error) {
|
|
55
|
-
this.logger.error(error);
|
|
56
|
-
res.status(500).json({ error: error.message });
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
async update(req, res) {
|
|
60
|
-
try {
|
|
61
|
-
const updatedData = await this.model.findOneAndUpdate({ _id: req.body._id }, req.body, {
|
|
62
|
-
new: true,
|
|
63
|
-
runValidators: true,
|
|
64
|
-
});
|
|
65
|
-
if (!updatedData) {
|
|
66
|
-
throw new Error('Документ не найден.');
|
|
67
|
-
}
|
|
68
|
-
await this.cache.flush();
|
|
69
|
-
res.json(updatedData);
|
|
70
|
-
} catch (error) {
|
|
71
|
-
this.logger.error('Ошибка обновления данных', error);
|
|
72
|
-
res.status(404).json({ error: error.message });
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
async delete(req, res) {
|
|
76
|
-
try {
|
|
77
|
-
const deletedData = await this.model.findOneAndDelete({ _id: req.body._id });
|
|
78
|
-
if (!deletedData) {
|
|
79
|
-
throw new Error('Документ не найден.');
|
|
80
|
-
}
|
|
81
|
-
await this.cache.flush();
|
|
82
|
-
res.status(204).send();
|
|
83
|
-
} catch (error) {
|
|
84
|
-
this.logger.error('Ошибка удаления данных', error);
|
|
85
|
-
res.status(404).json({ error: error.message });
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
export default CRUD;
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import js from '@eslint/js';
|
|
2
|
-
import globals from 'globals';
|
|
3
|
-
import reactHooks from 'eslint-plugin-react-hooks';
|
|
4
|
-
import reactRefresh from 'eslint-plugin-react-refresh';
|
|
5
|
-
import tseslint from 'typescript-eslint';
|
|
6
|
-
|
|
7
|
-
export default tseslint.config(
|
|
8
|
-
{ ignores: ['dist'] },
|
|
9
|
-
{
|
|
10
|
-
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
|
11
|
-
files: ['**/*.{ts,tsx}'],
|
|
12
|
-
languageOptions: {
|
|
13
|
-
ecmaVersion: 2020,
|
|
14
|
-
globals: core.browser,
|
|
15
|
-
},
|
|
16
|
-
plugins: {
|
|
17
|
-
'react-hooks': reactHooks,
|
|
18
|
-
'react-refresh': reactRefresh,
|
|
19
|
-
},
|
|
20
|
-
rules: {
|
|
21
|
-
...reactHooks.configs.recommended.rules,
|
|
22
|
-
'react-refresh/only-export-components': [
|
|
23
|
-
'warn',
|
|
24
|
-
{ allowConstantExport: true },
|
|
25
|
-
],
|
|
26
|
-
},
|
|
27
|
-
}
|
|
28
|
-
);
|