@escapenavigator/types 1.10.142 → 1.10.144
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/client/query-client.dto.d.ts +1 -0
- package/dist/client/query-client.dto.js +5 -0
- package/dist/email-builder/index.d.ts +28 -0
- package/dist/email-builder/index.js +29 -17
- package/dist/profile/marketing-email/test-send-email-v2.dto.d.ts +56 -0
- package/dist/profile/marketing-email/test-send-email-v2.dto.js +85 -0
- package/dist/shared/export-limits.d.ts +18 -0
- package/dist/shared/export-limits.js +19 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +2 -2
|
@@ -3,6 +3,7 @@ export declare class QueryClientDto {
|
|
|
3
3
|
lastGame?: [string, string];
|
|
4
4
|
birthday?: [string, string];
|
|
5
5
|
gamesCount?: string[];
|
|
6
|
+
visitsCount?: 'exactly_1' | 'exactly_2' | 'more_2' | 'more_3' | 'more_4';
|
|
6
7
|
selectedMonth?: string | number;
|
|
7
8
|
blockedClient?: 'blocked' | 'not_blocked';
|
|
8
9
|
alowedToAds?: 'allowed' | 'not_allowed';
|
|
@@ -38,6 +38,11 @@ __decorate([
|
|
|
38
38
|
(0, class_transformer_1.Transform)(({ value }) => (typeof value === 'object' ? value : ['', ''])),
|
|
39
39
|
__metadata("design:type", Array)
|
|
40
40
|
], QueryClientDto.prototype, "gamesCount", void 0);
|
|
41
|
+
__decorate([
|
|
42
|
+
(0, class_validator_1.IsOptional)(),
|
|
43
|
+
(0, class_transformer_1.Expose)(),
|
|
44
|
+
__metadata("design:type", String)
|
|
45
|
+
], QueryClientDto.prototype, "visitsCount", void 0);
|
|
41
46
|
__decorate([
|
|
42
47
|
(0, class_validator_1.IsOptional)(),
|
|
43
48
|
(0, class_transformer_1.Expose)(),
|
|
@@ -133,8 +133,36 @@ export declare const isEmailContentJsonV2: (raw: unknown) => raw is EmailContent
|
|
|
133
133
|
*/
|
|
134
134
|
export declare const DEFAULT_EMAIL_THEME: EmailThemeV2;
|
|
135
135
|
export declare const createEmptyEmailContent: () => EmailContentJsonV2;
|
|
136
|
+
/**
|
|
137
|
+
* Локализуемые строки seed-шаблона (`createDefaultEmailContent`).
|
|
138
|
+
* Пакет `types` без i18n, поэтому строки приходят сверху из app-слоя
|
|
139
|
+
* (хелпер `useDefaultEmailTexts` в `components/email-builder`). Если
|
|
140
|
+
* не переданы — используются RU-дефолты (см. `DEFAULT_EMAIL_TEXTS`).
|
|
141
|
+
*/
|
|
142
|
+
export type DefaultEmailTexts = {
|
|
143
|
+
/** H1-заголовок письма. */
|
|
144
|
+
heading: string;
|
|
145
|
+
/** Текст ПОСЛЕ переменной названия компании (начинается с ", "). */
|
|
146
|
+
intro: string;
|
|
147
|
+
/** Абзац-подсказка про «/» и «@». */
|
|
148
|
+
tryEditing: string;
|
|
149
|
+
/** Подпись на кнопке-примере. */
|
|
150
|
+
buttonText: string;
|
|
151
|
+
/** Приглушённый абзац о развитии редактора. */
|
|
152
|
+
feedback: string;
|
|
153
|
+
/** Строка подписи («С уважением,»). */
|
|
154
|
+
signoff: string;
|
|
155
|
+
/** Подпись команды («Команда Escape Navigator»). */
|
|
156
|
+
team: string;
|
|
157
|
+
/** Лейбл inline-переменной названия компании. */
|
|
158
|
+
companyLabel: string;
|
|
159
|
+
};
|
|
160
|
+
/** RU-дефолты seed-шаблона — fallback, если `texts` не передан. */
|
|
161
|
+
export declare const DEFAULT_EMAIL_TEXTS: DefaultEmailTexts;
|
|
136
162
|
export type CreateDefaultEmailContentOptions = {
|
|
137
163
|
logo?: string | null;
|
|
164
|
+
/** Локализованные строки seed-шаблона. По умолчанию — RU. */
|
|
165
|
+
texts?: Partial<DefaultEmailTexts>;
|
|
138
166
|
};
|
|
139
167
|
/**
|
|
140
168
|
* Image-нода с предустановленным логотипом. Используется и в seed'е
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* for transactional emails that are NOT user-editable.
|
|
12
12
|
*/
|
|
13
13
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
-
exports.EmailScope = exports.normalizeEmailTheme = exports.createDefaultEmailContent = exports.createLogoNode = exports.createEmptyEmailContent = exports.DEFAULT_EMAIL_THEME = exports.isEmailContentJsonV2 = exports.resolveEmailThemeColors = exports.deriveEmailTextColors = exports.EMAIL_CONTENT_VERSION_V2 = void 0;
|
|
14
|
+
exports.EmailScope = exports.normalizeEmailTheme = exports.createDefaultEmailContent = exports.createLogoNode = exports.DEFAULT_EMAIL_TEXTS = exports.createEmptyEmailContent = exports.DEFAULT_EMAIL_THEME = exports.isEmailContentJsonV2 = exports.resolveEmailThemeColors = exports.deriveEmailTextColors = exports.EMAIL_CONTENT_VERSION_V2 = void 0;
|
|
15
15
|
exports.EMAIL_CONTENT_VERSION_V2 = 'v2';
|
|
16
16
|
/**
|
|
17
17
|
* Compute the relative luminance of an opaque hex color in [0, 1].
|
|
@@ -156,6 +156,25 @@ const createEmptyEmailContent = () => ({
|
|
|
156
156
|
theme: exports.DEFAULT_EMAIL_THEME,
|
|
157
157
|
});
|
|
158
158
|
exports.createEmptyEmailContent = createEmptyEmailContent;
|
|
159
|
+
/** RU-дефолты seed-шаблона — fallback, если `texts` не передан. */
|
|
160
|
+
exports.DEFAULT_EMAIL_TEXTS = {
|
|
161
|
+
heading: 'Email конструктор 2.0',
|
|
162
|
+
intro: ', готовы преобразить email-коммуникацию? ' +
|
|
163
|
+
'Встречайте мощный редактор писем, который ' +
|
|
164
|
+
'помогает быстро и красиво готовить рассылки ' +
|
|
165
|
+
'клиентам.',
|
|
166
|
+
tryEditing: 'Попробуйте отредактировать этот шаблон, или ' +
|
|
167
|
+
'начните с чистого листа — нажмите «/» в любом ' +
|
|
168
|
+
'месте, чтобы вставить блок, и «@» — чтобы ' +
|
|
169
|
+
'добавить переменную клиента.',
|
|
170
|
+
buttonText: 'Пример кнопки →',
|
|
171
|
+
feedback: 'Делитесь идеями и улучшениями — мы ' +
|
|
172
|
+
'постоянно развиваем редактор, чтобы ' +
|
|
173
|
+
'делать ваш маркетинг проще.',
|
|
174
|
+
signoff: 'С уважением,',
|
|
175
|
+
team: 'Команда Escape Navigator',
|
|
176
|
+
companyLabel: 'Название компании',
|
|
177
|
+
};
|
|
159
178
|
/**
|
|
160
179
|
* Бренд, используемый в `alt`/`title` для логотипа. Захардкожен — это
|
|
161
180
|
* не персонализированное имя профиля (которое подставляется через
|
|
@@ -193,6 +212,7 @@ const createLogoNode = (logo) => {
|
|
|
193
212
|
exports.createLogoNode = createLogoNode;
|
|
194
213
|
const createDefaultEmailContent = (options = {}) => {
|
|
195
214
|
const { logo } = options;
|
|
215
|
+
const txt = Object.assign(Object.assign({}, exports.DEFAULT_EMAIL_TEXTS), options.texts);
|
|
196
216
|
const content = [];
|
|
197
217
|
const logoNode = (0, exports.createLogoNode)(logo);
|
|
198
218
|
if (logoNode)
|
|
@@ -200,7 +220,7 @@ const createDefaultEmailContent = (options = {}) => {
|
|
|
200
220
|
content.push({
|
|
201
221
|
type: 'heading',
|
|
202
222
|
attrs: { level: 1, textAlign: 'left' },
|
|
203
|
-
content: [{ type: 'text', text:
|
|
223
|
+
content: [{ type: 'text', text: txt.heading }],
|
|
204
224
|
}, {
|
|
205
225
|
type: 'paragraph',
|
|
206
226
|
attrs: { textAlign: 'left' },
|
|
@@ -221,7 +241,7 @@ const createDefaultEmailContent = (options = {}) => {
|
|
|
221
241
|
type: 'variable',
|
|
222
242
|
attrs: {
|
|
223
243
|
id: 'profile.title',
|
|
224
|
-
label:
|
|
244
|
+
label: txt.companyLabel,
|
|
225
245
|
fallback: null,
|
|
226
246
|
required: false,
|
|
227
247
|
hideDefaultValue: false,
|
|
@@ -229,10 +249,7 @@ const createDefaultEmailContent = (options = {}) => {
|
|
|
229
249
|
},
|
|
230
250
|
{
|
|
231
251
|
type: 'text',
|
|
232
|
-
text:
|
|
233
|
-
'Встречайте мощный редактор писем, который ' +
|
|
234
|
-
'помогает быстро и красиво готовить рассылки ' +
|
|
235
|
-
'клиентам.',
|
|
252
|
+
text: txt.intro,
|
|
236
253
|
},
|
|
237
254
|
],
|
|
238
255
|
}, {
|
|
@@ -241,16 +258,13 @@ const createDefaultEmailContent = (options = {}) => {
|
|
|
241
258
|
content: [
|
|
242
259
|
{
|
|
243
260
|
type: 'text',
|
|
244
|
-
text:
|
|
245
|
-
'начните с чистого листа — нажмите «/» в любом ' +
|
|
246
|
-
'месте, чтобы вставить блок, и «@» — чтобы ' +
|
|
247
|
-
'добавить переменную клиента.',
|
|
261
|
+
text: txt.tryEditing,
|
|
248
262
|
},
|
|
249
263
|
],
|
|
250
264
|
}, {
|
|
251
265
|
type: 'button',
|
|
252
266
|
attrs: {
|
|
253
|
-
text:
|
|
267
|
+
text: txt.buttonText,
|
|
254
268
|
url: '',
|
|
255
269
|
alignment: 'left',
|
|
256
270
|
variant: 'filled',
|
|
@@ -266,9 +280,7 @@ const createDefaultEmailContent = (options = {}) => {
|
|
|
266
280
|
content: [
|
|
267
281
|
{
|
|
268
282
|
type: 'text',
|
|
269
|
-
text:
|
|
270
|
-
'постоянно развиваем редактор, чтобы ' +
|
|
271
|
-
'делать ваш маркетинг проще.',
|
|
283
|
+
text: txt.feedback,
|
|
272
284
|
marks: [MUTED_TEXT_MARK],
|
|
273
285
|
},
|
|
274
286
|
],
|
|
@@ -276,9 +288,9 @@ const createDefaultEmailContent = (options = {}) => {
|
|
|
276
288
|
type: 'paragraph',
|
|
277
289
|
attrs: { textAlign: 'left' },
|
|
278
290
|
content: [
|
|
279
|
-
{ type: 'text', text:
|
|
291
|
+
{ type: 'text', text: txt.signoff },
|
|
280
292
|
{ type: 'hardBreak' },
|
|
281
|
-
{ type: 'text', text:
|
|
293
|
+
{ type: 'text', text: txt.team },
|
|
282
294
|
],
|
|
283
295
|
});
|
|
284
296
|
return {
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { EmailScope } from '../../email-builder';
|
|
2
|
+
import { Languages } from '../../shared/enum/languages.enum';
|
|
3
|
+
import { EmailContentJsonV2Dto } from './create-profile-marketing-email-campaign.dto';
|
|
4
|
+
import { PreviewPromocodeDto } from './preview-campaign-v2.dto';
|
|
5
|
+
/**
|
|
6
|
+
* Request body for `POST /profile-marketing-email-campaigns/test-send-v2`.
|
|
7
|
+
*
|
|
8
|
+
* «Тестовая отправка» письма прямо из режима превью: бэк резолвит
|
|
9
|
+
* переменные шаблона на РЕАЛЬНЫХ данных и шлёт письмо на указанный
|
|
10
|
+
* `email`, как будто оно ушло в запланированное время.
|
|
11
|
+
*
|
|
12
|
+
* Источник данных зависит от scope письма:
|
|
13
|
+
* - письма, привязанные к игре/брони (`order-post-game`,
|
|
14
|
+
* `custom-booking-template`) → нужен `orderCode` (код брони).
|
|
15
|
+
* Бэк находит заказ по коду В РАМКАХ профиля (проверка владения)
|
|
16
|
+
* и резолвит `order.*` / `client.*` / `questroom.*` / `location.*`
|
|
17
|
+
* / `promocode.*` именно по нему;
|
|
18
|
+
* - письма, привязанные к клиенту (`birthday-marketing`,
|
|
19
|
+
* `marketing-campaign`) → нужен `clientId` (номер клиента в профиле).
|
|
20
|
+
*/
|
|
21
|
+
export declare class TestSendMarketingEmailV2Dto {
|
|
22
|
+
contentJson: EmailContentJsonV2Dto;
|
|
23
|
+
scope: EmailScope;
|
|
24
|
+
/** Куда отправить тестовое письмо (inbox оператора). */
|
|
25
|
+
email: string;
|
|
26
|
+
/**
|
|
27
|
+
* Код брони (человекочитаемый, напр. `B1234567`). Обязателен для
|
|
28
|
+
* scope'ов, завязанных на игру/бронь. Бэк проверяет, что бронь
|
|
29
|
+
* принадлежит текущему профилю.
|
|
30
|
+
*/
|
|
31
|
+
orderCode?: string;
|
|
32
|
+
/**
|
|
33
|
+
* Номер (id) клиента в рамках профиля. Обязателен для scope'ов,
|
|
34
|
+
* завязанных на клиента (birthday / marketing-campaign).
|
|
35
|
+
*/
|
|
36
|
+
clientId?: number;
|
|
37
|
+
/**
|
|
38
|
+
* Необязательная тема письма (если редактор/форма её знает —
|
|
39
|
+
* напр. `locale.title` из карточки рассылки). К теме всё равно
|
|
40
|
+
* добавляется префикс `[TEST]`. Нужна, чтобы при отправке из
|
|
41
|
+
* родительской карточки проверить и тему письма.
|
|
42
|
+
*/
|
|
43
|
+
subject?: string;
|
|
44
|
+
/**
|
|
45
|
+
* Язык письма. При отправке из карточки рассылки фронт шлёт язык
|
|
46
|
+
* выбранной локали — бэк подставляет его в `order.language`, чтобы
|
|
47
|
+
* резолверы выбрали нужную локаль квеста/локации и формат даты.
|
|
48
|
+
*/
|
|
49
|
+
language?: Languages;
|
|
50
|
+
/**
|
|
51
|
+
* Промокод для теста (код/скидка/срок) — те же поля, что и в
|
|
52
|
+
* preview. Если передан, письмо рендерит реальную «валидность»
|
|
53
|
+
* (срок действия) из настроек скидки рассылки, а не общий образец.
|
|
54
|
+
*/
|
|
55
|
+
previewPromocode?: PreviewPromocodeDto;
|
|
56
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.TestSendMarketingEmailV2Dto = void 0;
|
|
13
|
+
const class_transformer_1 = require("class-transformer");
|
|
14
|
+
const class_validator_1 = require("class-validator");
|
|
15
|
+
const email_builder_1 = require("../../email-builder");
|
|
16
|
+
const languages_enum_1 = require("../../shared/enum/languages.enum");
|
|
17
|
+
const create_profile_marketing_email_campaign_dto_1 = require("./create-profile-marketing-email-campaign.dto");
|
|
18
|
+
const preview_campaign_v2_dto_1 = require("./preview-campaign-v2.dto");
|
|
19
|
+
/**
|
|
20
|
+
* Request body for `POST /profile-marketing-email-campaigns/test-send-v2`.
|
|
21
|
+
*
|
|
22
|
+
* «Тестовая отправка» письма прямо из режима превью: бэк резолвит
|
|
23
|
+
* переменные шаблона на РЕАЛЬНЫХ данных и шлёт письмо на указанный
|
|
24
|
+
* `email`, как будто оно ушло в запланированное время.
|
|
25
|
+
*
|
|
26
|
+
* Источник данных зависит от scope письма:
|
|
27
|
+
* - письма, привязанные к игре/брони (`order-post-game`,
|
|
28
|
+
* `custom-booking-template`) → нужен `orderCode` (код брони).
|
|
29
|
+
* Бэк находит заказ по коду В РАМКАХ профиля (проверка владения)
|
|
30
|
+
* и резолвит `order.*` / `client.*` / `questroom.*` / `location.*`
|
|
31
|
+
* / `promocode.*` именно по нему;
|
|
32
|
+
* - письма, привязанные к клиенту (`birthday-marketing`,
|
|
33
|
+
* `marketing-campaign`) → нужен `clientId` (номер клиента в профиле).
|
|
34
|
+
*/
|
|
35
|
+
class TestSendMarketingEmailV2Dto {
|
|
36
|
+
}
|
|
37
|
+
exports.TestSendMarketingEmailV2Dto = TestSendMarketingEmailV2Dto;
|
|
38
|
+
__decorate([
|
|
39
|
+
(0, class_validator_1.IsDefined)(),
|
|
40
|
+
(0, class_validator_1.ValidateNested)(),
|
|
41
|
+
(0, class_transformer_1.Type)(() => create_profile_marketing_email_campaign_dto_1.EmailContentJsonV2Dto),
|
|
42
|
+
(0, class_transformer_1.Expose)(),
|
|
43
|
+
__metadata("design:type", create_profile_marketing_email_campaign_dto_1.EmailContentJsonV2Dto)
|
|
44
|
+
], TestSendMarketingEmailV2Dto.prototype, "contentJson", void 0);
|
|
45
|
+
__decorate([
|
|
46
|
+
(0, class_validator_1.IsEnum)(email_builder_1.EmailScope),
|
|
47
|
+
(0, class_transformer_1.Expose)(),
|
|
48
|
+
__metadata("design:type", String)
|
|
49
|
+
], TestSendMarketingEmailV2Dto.prototype, "scope", void 0);
|
|
50
|
+
__decorate([
|
|
51
|
+
(0, class_validator_1.IsEmail)(),
|
|
52
|
+
(0, class_transformer_1.Expose)(),
|
|
53
|
+
__metadata("design:type", String)
|
|
54
|
+
], TestSendMarketingEmailV2Dto.prototype, "email", void 0);
|
|
55
|
+
__decorate([
|
|
56
|
+
(0, class_validator_1.IsOptional)(),
|
|
57
|
+
(0, class_validator_1.IsString)(),
|
|
58
|
+
(0, class_transformer_1.Expose)(),
|
|
59
|
+
__metadata("design:type", String)
|
|
60
|
+
], TestSendMarketingEmailV2Dto.prototype, "orderCode", void 0);
|
|
61
|
+
__decorate([
|
|
62
|
+
(0, class_validator_1.IsOptional)(),
|
|
63
|
+
(0, class_validator_1.IsInt)(),
|
|
64
|
+
(0, class_transformer_1.Expose)(),
|
|
65
|
+
__metadata("design:type", Number)
|
|
66
|
+
], TestSendMarketingEmailV2Dto.prototype, "clientId", void 0);
|
|
67
|
+
__decorate([
|
|
68
|
+
(0, class_validator_1.IsOptional)(),
|
|
69
|
+
(0, class_validator_1.IsString)(),
|
|
70
|
+
(0, class_transformer_1.Expose)(),
|
|
71
|
+
__metadata("design:type", String)
|
|
72
|
+
], TestSendMarketingEmailV2Dto.prototype, "subject", void 0);
|
|
73
|
+
__decorate([
|
|
74
|
+
(0, class_validator_1.IsOptional)(),
|
|
75
|
+
(0, class_validator_1.IsEnum)(languages_enum_1.Languages),
|
|
76
|
+
(0, class_transformer_1.Expose)(),
|
|
77
|
+
__metadata("design:type", String)
|
|
78
|
+
], TestSendMarketingEmailV2Dto.prototype, "language", void 0);
|
|
79
|
+
__decorate([
|
|
80
|
+
(0, class_validator_1.IsOptional)(),
|
|
81
|
+
(0, class_validator_1.ValidateNested)(),
|
|
82
|
+
(0, class_transformer_1.Type)(() => preview_campaign_v2_dto_1.PreviewPromocodeDto),
|
|
83
|
+
(0, class_transformer_1.Expose)(),
|
|
84
|
+
__metadata("design:type", preview_campaign_v2_dto_1.PreviewPromocodeDto)
|
|
85
|
+
], TestSendMarketingEmailV2Dto.prototype, "previewPromocode", void 0);
|
|
@@ -24,3 +24,21 @@ export declare const EXPORT_LIMITS: {
|
|
|
24
24
|
readonly GOOGLE_PLACES: 5000;
|
|
25
25
|
};
|
|
26
26
|
export type ExportLimitKey = keyof typeof EXPORT_LIMITS;
|
|
27
|
+
/**
|
|
28
|
+
* Единый порог для всех CSV-экспортов (`GET /<resource>/export`).
|
|
29
|
+
*
|
|
30
|
+
* Один эндпоинт, один запрос с фронта:
|
|
31
|
+
* - `count <= EXPORT_SYNC_LIMIT` → CSV отдаётся сразу потоком (скачивание);
|
|
32
|
+
* - `count > EXPORT_SYNC_LIMIT` → запускается фоновая джоба, файл уходит
|
|
33
|
+
* на почту пользователя; фронт показывает тост «пришлём на e-mail».
|
|
34
|
+
*
|
|
35
|
+
* Порог низкий (1000) намеренно: синхронно отдаём только «лёгкие» срезы,
|
|
36
|
+
* всё крупное уводим в фон, чтобы не держать HTTP-соединение и heap.
|
|
37
|
+
*/
|
|
38
|
+
export declare const EXPORT_SYNC_LIMIT = 1000;
|
|
39
|
+
/**
|
|
40
|
+
* Сколько фоновых (email) экспортов разрешено запустить одному профилю в
|
|
41
|
+
* час. Защита от спама тяжёлыми выгрузками. При превышении бэкенд отвечает
|
|
42
|
+
* 429, фронт показывает тост «слишком часто, попробуйте позже».
|
|
43
|
+
*/
|
|
44
|
+
export declare const EXPORT_RATE_LIMIT_PER_HOUR = 10;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.EXPORT_LIMITS = void 0;
|
|
3
|
+
exports.EXPORT_RATE_LIMIT_PER_HOUR = exports.EXPORT_SYNC_LIMIT = exports.EXPORT_LIMITS = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* Единые лимиты на размер XLSX-выгрузок. Импортится и бэком
|
|
6
6
|
* (`*.Service.EXPORT_LIMIT` + count-precheck в контроллере), и
|
|
@@ -26,3 +26,21 @@ exports.EXPORT_LIMITS = {
|
|
|
26
26
|
USER_SESSIONS: 5000,
|
|
27
27
|
GOOGLE_PLACES: 5000,
|
|
28
28
|
};
|
|
29
|
+
/**
|
|
30
|
+
* Единый порог для всех CSV-экспортов (`GET /<resource>/export`).
|
|
31
|
+
*
|
|
32
|
+
* Один эндпоинт, один запрос с фронта:
|
|
33
|
+
* - `count <= EXPORT_SYNC_LIMIT` → CSV отдаётся сразу потоком (скачивание);
|
|
34
|
+
* - `count > EXPORT_SYNC_LIMIT` → запускается фоновая джоба, файл уходит
|
|
35
|
+
* на почту пользователя; фронт показывает тост «пришлём на e-mail».
|
|
36
|
+
*
|
|
37
|
+
* Порог низкий (1000) намеренно: синхронно отдаём только «лёгкие» срезы,
|
|
38
|
+
* всё крупное уводим в фон, чтобы не держать HTTP-соединение и heap.
|
|
39
|
+
*/
|
|
40
|
+
exports.EXPORT_SYNC_LIMIT = 1000;
|
|
41
|
+
/**
|
|
42
|
+
* Сколько фоновых (email) экспортов разрешено запустить одному профилю в
|
|
43
|
+
* час. Защита от спама тяжёлыми выгрузками. При превышении бэкенд отвечает
|
|
44
|
+
* 429, фронт показывает тост «слишком часто, попробуйте позже».
|
|
45
|
+
*/
|
|
46
|
+
exports.EXPORT_RATE_LIMIT_PER_HOUR = 10;
|