@mamindom/contracts 1.0.140 → 1.0.142
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/gen/audit.d.ts +0 -6
- package/dist/gen/banner.d.ts +0 -1
- package/dist/gen/bonus_settings.d.ts +0 -7
- package/dist/gen/brand.d.ts +0 -1
- package/dist/gen/bundle.d.ts +0 -9
- package/dist/gen/search.d.ts +0 -8
- package/dist/gen/seo_filter.d.ts +157 -0
- package/dist/gen/seo_filter.js +36 -0
- package/dist/gen/shop_filters.d.ts +7 -19
- package/dist/proto/audit.proto +6 -6
- package/dist/proto/banner.proto +1 -1
- package/dist/proto/bonus_settings.proto +4 -5
- package/dist/proto/brand.proto +1 -1
- package/dist/proto/bundle.proto +9 -14
- package/dist/proto/search.proto +1 -2
- package/dist/proto/seo_filter.proto +154 -0
- package/dist/proto/shop_filters.proto +15 -11
- package/dist/src/proto/paths.d.ts +1 -0
- package/dist/src/proto/paths.js +1 -0
- package/gen/audit.ts +5 -21
- package/gen/banner.ts +0 -1
- package/gen/bonus_settings.ts +0 -7
- package/gen/brand.ts +1 -4
- package/gen/bundle.ts +0 -9
- package/gen/search.ts +0 -10
- package/gen/seo_filter.ts +244 -0
- package/gen/shop_filters.ts +11 -28
- package/package.json +1 -1
- package/proto/audit.proto +6 -6
- package/proto/banner.proto +1 -1
- package/proto/bonus_settings.proto +4 -5
- package/proto/brand.proto +1 -1
- package/proto/bundle.proto +9 -14
- package/proto/search.proto +1 -2
- package/proto/seo_filter.proto +154 -0
- package/proto/shop_filters.proto +15 -11
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
syntax = "proto3";
|
|
2
|
+
|
|
3
|
+
package catalog.v1;
|
|
4
|
+
|
|
5
|
+
import "common.proto";
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
service SeoFilterService {
|
|
9
|
+
// ─── Public ──────────────────────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
// Резолв ЧПУ-URL → metadata + canonical. Викликається SSR Next.js для
|
|
12
|
+
// кожного category/[[...filters]] запиту. Якщо combo не знайдено —
|
|
13
|
+
// повертає isIndexed=false і автогенерований fallback metadata.
|
|
14
|
+
rpc ResolveSeoFilter (ResolveSeoFilterRequest) returns (ResolveSeoFilterResponse);
|
|
15
|
+
|
|
16
|
+
// ─── Admin ───────────────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
rpc ListSeoFilters (ListSeoFiltersRequest) returns (ListSeoFiltersResponse);
|
|
19
|
+
rpc GetSeoFilter (GetSeoFilterRequest) returns (SeoFilterResponse);
|
|
20
|
+
rpc CreateSeoFilter (CreateSeoFilterRequest) returns (SeoFilterResponse);
|
|
21
|
+
rpc UpdateSeoFilter (UpdateSeoFilterRequest) returns (SeoFilterResponse);
|
|
22
|
+
rpc DeleteSeoFilter (DeleteSeoFilterRequest) returns (DeleteResponse);
|
|
23
|
+
|
|
24
|
+
// Топ-N непокритих combinations (за impressions/popularity) — для
|
|
25
|
+
// швидкого створення SeoFilter з адмін-форми "Автопідказки".
|
|
26
|
+
rpc SuggestSeoFilters (SuggestSeoFiltersRequest) returns (SuggestSeoFiltersResponse);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ─────────────────────────── Domain types ────────────────────────────────
|
|
30
|
+
|
|
31
|
+
// Частина combo. Однаковий формат і у DB (combo_parts JSON), і у gRPC.
|
|
32
|
+
// type: 'brand'|'price'|'color'|'size'
|
|
33
|
+
message FilterPart {
|
|
34
|
+
string type = 1;
|
|
35
|
+
// Для brand/color/size:
|
|
36
|
+
optional string value_id = 2;
|
|
37
|
+
optional string value_slug = 3;
|
|
38
|
+
// Для price:
|
|
39
|
+
optional int32 price_min = 4;
|
|
40
|
+
optional int32 price_max = 5;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Локалізовані поля (uk/ru) у форматі { uk: "...", ru: "..." }.
|
|
44
|
+
message LocaleStrings {
|
|
45
|
+
string uk = 1;
|
|
46
|
+
string ru = 2;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
message SeoFilterResponse {
|
|
50
|
+
string id = 1;
|
|
51
|
+
string category_id = 2;
|
|
52
|
+
string combo_hash = 3;
|
|
53
|
+
repeated FilterPart combo_parts = 4;
|
|
54
|
+
LocaleStrings url_segments = 5;
|
|
55
|
+
LocaleStrings title = 6;
|
|
56
|
+
LocaleStrings description = 7;
|
|
57
|
+
LocaleStrings h1 = 8;
|
|
58
|
+
bool is_indexed = 9;
|
|
59
|
+
bool is_auto = 10;
|
|
60
|
+
int32 popularity = 11;
|
|
61
|
+
int64 created_at = 12;
|
|
62
|
+
int64 updated_at = 13;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ─────────────────────────── Resolve (public) ────────────────────────────
|
|
66
|
+
|
|
67
|
+
message ResolveSeoFilterRequest {
|
|
68
|
+
string category_id = 1;
|
|
69
|
+
// Канонічно відсортовані URL segments як приходять з Next.js, напр.
|
|
70
|
+
// ["brend-cybex", "cina-1000-5000"]. Caller гарантує що порядок canonical.
|
|
71
|
+
repeated string url_segments = 2;
|
|
72
|
+
string locale = 3; // 'uk' | 'ru'
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
message ResolveSeoFilterResponse {
|
|
76
|
+
// Якщо false → caller рендерить категорію з noindex,follow.
|
|
77
|
+
bool found = 1;
|
|
78
|
+
// Якщо false → caller додає <meta robots="noindex,follow"> навіть якщо
|
|
79
|
+
// запис знайдено (наприклад, isIndexed=false manual override).
|
|
80
|
+
bool is_indexed = 2;
|
|
81
|
+
LocaleStrings title = 3;
|
|
82
|
+
LocaleStrings description = 4;
|
|
83
|
+
LocaleStrings h1 = 5;
|
|
84
|
+
// Canonical-URL-path без host: "/uk/c/koliaski/brend-cybex"
|
|
85
|
+
string canonical_path = 6;
|
|
86
|
+
// Денормалізовані combo_parts з resolved valueId — для побудови
|
|
87
|
+
// запиту до products (categoryId + filter ids).
|
|
88
|
+
repeated FilterPart resolved_parts = 7;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ─────────────────────────── List (admin) ────────────────────────────────
|
|
92
|
+
|
|
93
|
+
message ListSeoFiltersRequest {
|
|
94
|
+
PaginationRequest pagination = 1;
|
|
95
|
+
optional string category_id = 2;
|
|
96
|
+
optional bool is_indexed = 3;
|
|
97
|
+
optional bool is_auto = 4;
|
|
98
|
+
// Пошук за uk/ru title (для адмін-фільтра).
|
|
99
|
+
optional string search = 5;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
message ListSeoFiltersResponse {
|
|
103
|
+
repeated SeoFilterResponse items = 1;
|
|
104
|
+
PaginationMeta meta = 2;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
message GetSeoFilterRequest {
|
|
108
|
+
string id = 1;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ─────────────────────────── Create / Update ─────────────────────────────
|
|
112
|
+
|
|
113
|
+
message CreateSeoFilterRequest {
|
|
114
|
+
string category_id = 1;
|
|
115
|
+
repeated FilterPart combo_parts = 2;
|
|
116
|
+
LocaleStrings title = 3;
|
|
117
|
+
LocaleStrings description = 4;
|
|
118
|
+
LocaleStrings h1 = 5;
|
|
119
|
+
bool is_indexed = 6;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
message UpdateSeoFilterRequest {
|
|
123
|
+
string id = 1;
|
|
124
|
+
optional LocaleStrings title = 2;
|
|
125
|
+
optional LocaleStrings description = 3;
|
|
126
|
+
optional LocaleStrings h1 = 4;
|
|
127
|
+
optional bool is_indexed = 5;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
message DeleteSeoFilterRequest {
|
|
131
|
+
string id = 1;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ─────────────────────────── Suggestions (admin) ─────────────────────────
|
|
135
|
+
|
|
136
|
+
message SuggestSeoFiltersRequest {
|
|
137
|
+
string category_id = 1;
|
|
138
|
+
int32 limit = 2; // default 20
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
message SuggestSeoFilterCandidate {
|
|
142
|
+
// Запропоновані combo_parts (валідні, з resolved slug).
|
|
143
|
+
repeated FilterPart combo_parts = 1;
|
|
144
|
+
// Оцінка популярності: clicks / impressions / товарообіг (single число).
|
|
145
|
+
int32 score = 2;
|
|
146
|
+
// Чи існує вже SeoFilter з таким комбо (тоді UI ховає кнопку Create).
|
|
147
|
+
bool already_exists = 3;
|
|
148
|
+
// Pre-built canonical URL path для preview.
|
|
149
|
+
LocaleStrings preview_segments = 4;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
message SuggestSeoFiltersResponse {
|
|
153
|
+
repeated SuggestSeoFilterCandidate items = 1;
|
|
154
|
+
}
|
|
@@ -15,8 +15,7 @@ service ShopFilterService {
|
|
|
15
15
|
message GetShopFiltersRequest {
|
|
16
16
|
string category_id = 1;
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
// з виключенням саме того facet — щоб лічильники реагували на вибір.
|
|
18
|
+
|
|
20
19
|
repeated string brand_ids = 2;
|
|
21
20
|
repeated string color_ids = 3;
|
|
22
21
|
repeated string size_ids = 4;
|
|
@@ -25,18 +24,16 @@ message GetShopFiltersRequest {
|
|
|
25
24
|
optional bool in_stock = 7;
|
|
26
25
|
optional string search = 8;
|
|
27
26
|
|
|
28
|
-
|
|
29
|
-
// по товарах, що належать поточній акції.
|
|
27
|
+
|
|
30
28
|
optional PromotionFilter promotion_filter = 9;
|
|
31
29
|
|
|
32
|
-
|
|
30
|
+
|
|
33
31
|
repeated string attribute_value_ids = 10;
|
|
34
32
|
|
|
35
|
-
|
|
36
|
-
// відрізнити "користувач не вибирав" від "обрав від 0".
|
|
33
|
+
|
|
37
34
|
optional bool price_active = 11;
|
|
38
35
|
|
|
39
|
-
|
|
36
|
+
|
|
40
37
|
optional string locale = 12;
|
|
41
38
|
}
|
|
42
39
|
|
|
@@ -48,7 +45,15 @@ message GetShopFiltersResponse {
|
|
|
48
45
|
repeated ShopFilterAttribute attributes = 4;
|
|
49
46
|
double price_min = 5;
|
|
50
47
|
double price_max = 6;
|
|
51
|
-
|
|
48
|
+
|
|
49
|
+
repeated ShopFilterSubcategory subcategories = 7;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
message ShopFilterSubcategory {
|
|
53
|
+
string id = 1;
|
|
54
|
+
string slug = 2;
|
|
55
|
+
string label = 3;
|
|
56
|
+
int32 count = 4;
|
|
52
57
|
}
|
|
53
58
|
|
|
54
59
|
message ShopFilterOption {
|
|
@@ -66,8 +71,7 @@ message ShopFilterColorOption {
|
|
|
66
71
|
|
|
67
72
|
message ShopFilterAttribute {
|
|
68
73
|
string slug = 1;
|
|
69
|
-
|
|
70
|
-
// одну рядкову label згідно locale запиту).
|
|
74
|
+
|
|
71
75
|
string label = 2;
|
|
72
76
|
repeated ShopFilterOption values = 3;
|
|
73
77
|
}
|
package/dist/src/proto/paths.js
CHANGED
|
@@ -20,6 +20,7 @@ exports.PROTO_PATHS = {
|
|
|
20
20
|
BUNDLE: (0, node_path_1.join)(__dirname, '../../proto/bundle.proto'),
|
|
21
21
|
FILTER: (0, node_path_1.join)(__dirname, '../../proto/shop_filters.proto'),
|
|
22
22
|
SEARCH: (0, node_path_1.join)(__dirname, '../../proto/search.proto'),
|
|
23
|
+
SEO_FILTER: (0, node_path_1.join)(__dirname, '../../proto/seo_filter.proto'),
|
|
23
24
|
PICKUP: (0, node_path_1.join)(__dirname, '../../proto/pickup.proto'),
|
|
24
25
|
PRODUCT_REVIEW: (0, node_path_1.join)(__dirname, '../../proto/product_review.proto'),
|
|
25
26
|
COMMON_PROMO: (0, node_path_1.join)(__dirname, '../../proto/common_promo.proto'),
|
package/gen/audit.ts
CHANGED
|
@@ -13,21 +13,11 @@ export const protobufPackage = "audit.v1";
|
|
|
13
13
|
export interface LogActionRequest {
|
|
14
14
|
actorId?: string | undefined;
|
|
15
15
|
actorName?: string | undefined;
|
|
16
|
-
actorEmail?:
|
|
17
|
-
| string
|
|
18
|
-
| undefined;
|
|
19
|
-
/** e.g. "order.cancelled", "product.price_changed" */
|
|
16
|
+
actorEmail?: string | undefined;
|
|
20
17
|
action: string;
|
|
21
|
-
/** e.g. "order", "product", "role" */
|
|
22
18
|
targetType: string;
|
|
23
|
-
targetId?:
|
|
24
|
-
|
|
25
|
-
| undefined;
|
|
26
|
-
/** human-readable summary */
|
|
27
|
-
summary?:
|
|
28
|
-
| string
|
|
29
|
-
| undefined;
|
|
30
|
-
/** serialized JSON snapshot (small!) */
|
|
19
|
+
targetId?: string | undefined;
|
|
20
|
+
summary?: string | undefined;
|
|
31
21
|
beforeJson?: string | undefined;
|
|
32
22
|
afterJson?: string | undefined;
|
|
33
23
|
ip?: string | undefined;
|
|
@@ -44,14 +34,8 @@ export interface ListAuditRequest {
|
|
|
44
34
|
actorId?: string | undefined;
|
|
45
35
|
targetType?: string | undefined;
|
|
46
36
|
targetId?: string | undefined;
|
|
47
|
-
action?:
|
|
48
|
-
|
|
49
|
-
| undefined;
|
|
50
|
-
/** "orders" matches order.* etc */
|
|
51
|
-
actionGroup?:
|
|
52
|
-
| string
|
|
53
|
-
| undefined;
|
|
54
|
-
/** ISO date */
|
|
37
|
+
action?: string | undefined;
|
|
38
|
+
actionGroup?: string | undefined;
|
|
55
39
|
from?: string | undefined;
|
|
56
40
|
to?: string | undefined;
|
|
57
41
|
}
|
package/gen/banner.ts
CHANGED
|
@@ -129,7 +129,6 @@ export interface UpdateBannerRequest {
|
|
|
129
129
|
id: string;
|
|
130
130
|
slug?: string | undefined;
|
|
131
131
|
name: { [key: string]: string };
|
|
132
|
-
/** empty = не оновлювати; non-empty = заміна повного списку плейсментів. */
|
|
133
132
|
placements: BannerPlacement[];
|
|
134
133
|
status?: boolean | undefined;
|
|
135
134
|
sortOrder?: number | undefined;
|
package/gen/bonus_settings.ts
CHANGED
|
@@ -11,16 +11,9 @@ import { Observable } from "rxjs";
|
|
|
11
11
|
export const protobufPackage = "promo.v1";
|
|
12
12
|
|
|
13
13
|
export interface BonusSettingsResponse {
|
|
14
|
-
/** Курс нарахування бонусів від суми замовлення (0.02 = 2%). */
|
|
15
14
|
earnRate: number;
|
|
16
|
-
/** Курс списання бонусів (1 бонус = N грн, default 1.0). */
|
|
17
15
|
redeemRate: number;
|
|
18
|
-
/** Мінімальна сума замовлення для нарахування бонусів (0 = без обмеження). */
|
|
19
16
|
minOrderForEarn: number;
|
|
20
|
-
/**
|
|
21
|
-
* Максимальний відсоток від суми замовлення, який можна оплатити бонусами
|
|
22
|
-
* (1.0 = до 100%, 0.5 = до 50%; 0 — без обмеження).
|
|
23
|
-
*/
|
|
24
17
|
maxRedeemShare: number;
|
|
25
18
|
updatedAt: string;
|
|
26
19
|
}
|
package/gen/brand.ts
CHANGED
|
@@ -20,10 +20,7 @@ export interface BrandResponse {
|
|
|
20
20
|
metaTitle: { [key: string]: string };
|
|
21
21
|
metaDescription: { [key: string]: string };
|
|
22
22
|
guid1c?: string | undefined;
|
|
23
|
-
imageId?:
|
|
24
|
-
| string
|
|
25
|
-
| undefined;
|
|
26
|
-
/** Кількість АКТИВНИХ товарів бренду (для shop UI). */
|
|
23
|
+
imageId?: string | undefined;
|
|
27
24
|
productCount: number;
|
|
28
25
|
}
|
|
29
26
|
|
package/gen/bundle.ts
CHANGED
|
@@ -94,7 +94,6 @@ export interface CreateBundleRequest {
|
|
|
94
94
|
metaDescription: { [key: string]: string };
|
|
95
95
|
items: BundleItemPayload[];
|
|
96
96
|
createdById: string;
|
|
97
|
-
/** NEW */
|
|
98
97
|
displayPlacements: DisplayPlacement[];
|
|
99
98
|
priceMode: PriceMode;
|
|
100
99
|
requireAllInStock: boolean;
|
|
@@ -145,7 +144,6 @@ export interface UpdateBundleRequest {
|
|
|
145
144
|
metaTitle: { [key: string]: string };
|
|
146
145
|
metaDescription: { [key: string]: string };
|
|
147
146
|
items: BundleItemPayload[];
|
|
148
|
-
/** NEW */
|
|
149
147
|
displayPlacements: DisplayPlacement[];
|
|
150
148
|
priceMode?: PriceMode | undefined;
|
|
151
149
|
requireAllInStock?: boolean | undefined;
|
|
@@ -320,7 +318,6 @@ export interface BundleDetailResponse {
|
|
|
320
318
|
originalPrice: number;
|
|
321
319
|
finalPrice: number;
|
|
322
320
|
inStock: boolean;
|
|
323
|
-
/** NEW */
|
|
324
321
|
displayPlacements: DisplayPlacement[];
|
|
325
322
|
priceMode: PriceMode;
|
|
326
323
|
requireAllInStock: boolean;
|
|
@@ -402,7 +399,6 @@ export interface BundleItemAvailability {
|
|
|
402
399
|
}
|
|
403
400
|
|
|
404
401
|
export interface ExportBundlesResponse {
|
|
405
|
-
/** JSON string or CSV string */
|
|
406
402
|
content: string;
|
|
407
403
|
format: string;
|
|
408
404
|
count: number;
|
|
@@ -410,18 +406,13 @@ export interface ExportBundlesResponse {
|
|
|
410
406
|
|
|
411
407
|
export interface GenerateBundleCollageRequest {
|
|
412
408
|
bundleId: string;
|
|
413
|
-
/** Optional: override cell pixel size (default 400) */
|
|
414
409
|
cellSize?: number | undefined;
|
|
415
410
|
}
|
|
416
411
|
|
|
417
412
|
export interface GenerateBundleCollageResponse {
|
|
418
|
-
/** Public CDN/S3 URL of the collage JPEG */
|
|
419
413
|
url: string;
|
|
420
|
-
/** S3 object key */
|
|
421
414
|
key: string;
|
|
422
|
-
/** Final image width in pixels */
|
|
423
415
|
width: number;
|
|
424
|
-
/** Final image height in pixels */
|
|
425
416
|
height: number;
|
|
426
417
|
bundleId: string;
|
|
427
418
|
}
|
package/gen/search.ts
CHANGED
|
@@ -165,11 +165,6 @@ export interface ClearZeroResultsRequest {
|
|
|
165
165
|
|
|
166
166
|
export const CATALOG_V1_PACKAGE_NAME = "catalog.v1";
|
|
167
167
|
|
|
168
|
-
/**
|
|
169
|
-
* SearchService — окремий сервіс під autocomplete та керування пошуком
|
|
170
|
-
* (синоніми, популярні запити). Логіка та індекс живуть у catalog-service.
|
|
171
|
-
*/
|
|
172
|
-
|
|
173
168
|
export interface SearchServiceClient {
|
|
174
169
|
/** ─── Public (autocomplete) ─────────────────────────────────────────────── */
|
|
175
170
|
|
|
@@ -206,11 +201,6 @@ export interface SearchServiceClient {
|
|
|
206
201
|
clearZeroResults(request: ClearZeroResultsRequest): Observable<SuccessResponse>;
|
|
207
202
|
}
|
|
208
203
|
|
|
209
|
-
/**
|
|
210
|
-
* SearchService — окремий сервіс під autocomplete та керування пошуком
|
|
211
|
-
* (синоніми, популярні запити). Логіка та індекс живуть у catalog-service.
|
|
212
|
-
*/
|
|
213
|
-
|
|
214
204
|
export interface SearchServiceController {
|
|
215
205
|
/** ─── Public (autocomplete) ─────────────────────────────────────────────── */
|
|
216
206
|
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
|
|
2
|
+
// versions:
|
|
3
|
+
// protoc-gen-ts_proto v2.11.4
|
|
4
|
+
// protoc v3.21.12
|
|
5
|
+
// source: seo_filter.proto
|
|
6
|
+
|
|
7
|
+
/* eslint-disable */
|
|
8
|
+
import { GrpcMethod, GrpcStreamMethod } from "@nestjs/microservices";
|
|
9
|
+
import { Observable } from "rxjs";
|
|
10
|
+
import { DeleteResponse, PaginationMeta, PaginationRequest } from "./common";
|
|
11
|
+
|
|
12
|
+
export const protobufPackage = "catalog.v1";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Частина combo. Однаковий формат і у DB (combo_parts JSON), і у gRPC.
|
|
16
|
+
* type: 'brand'|'price'|'color'|'size'
|
|
17
|
+
*/
|
|
18
|
+
export interface FilterPart {
|
|
19
|
+
type: string;
|
|
20
|
+
/** Для brand/color/size: */
|
|
21
|
+
valueId?: string | undefined;
|
|
22
|
+
valueSlug?:
|
|
23
|
+
| string
|
|
24
|
+
| undefined;
|
|
25
|
+
/** Для price: */
|
|
26
|
+
priceMin?: number | undefined;
|
|
27
|
+
priceMax?: number | undefined;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Локалізовані поля (uk/ru) у форматі { uk: "...", ru: "..." }. */
|
|
31
|
+
export interface LocaleStrings {
|
|
32
|
+
uk: string;
|
|
33
|
+
ru: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface SeoFilterResponse {
|
|
37
|
+
id: string;
|
|
38
|
+
categoryId: string;
|
|
39
|
+
comboHash: string;
|
|
40
|
+
comboParts: FilterPart[];
|
|
41
|
+
urlSegments: LocaleStrings | undefined;
|
|
42
|
+
title: LocaleStrings | undefined;
|
|
43
|
+
description: LocaleStrings | undefined;
|
|
44
|
+
h1: LocaleStrings | undefined;
|
|
45
|
+
isIndexed: boolean;
|
|
46
|
+
isAuto: boolean;
|
|
47
|
+
popularity: number;
|
|
48
|
+
createdAt: number;
|
|
49
|
+
updatedAt: number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface ResolveSeoFilterRequest {
|
|
53
|
+
categoryId: string;
|
|
54
|
+
/**
|
|
55
|
+
* Канонічно відсортовані URL segments як приходять з Next.js, напр.
|
|
56
|
+
* ["brend-cybex", "cina-1000-5000"]. Caller гарантує що порядок canonical.
|
|
57
|
+
*/
|
|
58
|
+
urlSegments: string[];
|
|
59
|
+
/** 'uk' | 'ru' */
|
|
60
|
+
locale: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface ResolveSeoFilterResponse {
|
|
64
|
+
/** Якщо false → caller рендерить категорію з noindex,follow. */
|
|
65
|
+
found: boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Якщо false → caller додає <meta robots="noindex,follow"> навіть якщо
|
|
68
|
+
* запис знайдено (наприклад, isIndexed=false manual override).
|
|
69
|
+
*/
|
|
70
|
+
isIndexed: boolean;
|
|
71
|
+
title: LocaleStrings | undefined;
|
|
72
|
+
description: LocaleStrings | undefined;
|
|
73
|
+
h1:
|
|
74
|
+
| LocaleStrings
|
|
75
|
+
| undefined;
|
|
76
|
+
/** Canonical-URL-path без host: "/uk/c/koliaski/brend-cybex" */
|
|
77
|
+
canonicalPath: string;
|
|
78
|
+
/**
|
|
79
|
+
* Денормалізовані combo_parts з resolved valueId — для побудови
|
|
80
|
+
* запиту до products (categoryId + filter ids).
|
|
81
|
+
*/
|
|
82
|
+
resolvedParts: FilterPart[];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface ListSeoFiltersRequest {
|
|
86
|
+
pagination: PaginationRequest | undefined;
|
|
87
|
+
categoryId?: string | undefined;
|
|
88
|
+
isIndexed?: boolean | undefined;
|
|
89
|
+
isAuto?:
|
|
90
|
+
| boolean
|
|
91
|
+
| undefined;
|
|
92
|
+
/** Пошук за uk/ru title (для адмін-фільтра). */
|
|
93
|
+
search?: string | undefined;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface ListSeoFiltersResponse {
|
|
97
|
+
items: SeoFilterResponse[];
|
|
98
|
+
meta: PaginationMeta | undefined;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface GetSeoFilterRequest {
|
|
102
|
+
id: string;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface CreateSeoFilterRequest {
|
|
106
|
+
categoryId: string;
|
|
107
|
+
comboParts: FilterPart[];
|
|
108
|
+
title: LocaleStrings | undefined;
|
|
109
|
+
description: LocaleStrings | undefined;
|
|
110
|
+
h1: LocaleStrings | undefined;
|
|
111
|
+
isIndexed: boolean;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface UpdateSeoFilterRequest {
|
|
115
|
+
id: string;
|
|
116
|
+
title?: LocaleStrings | undefined;
|
|
117
|
+
description?: LocaleStrings | undefined;
|
|
118
|
+
h1?: LocaleStrings | undefined;
|
|
119
|
+
isIndexed?: boolean | undefined;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface DeleteSeoFilterRequest {
|
|
123
|
+
id: string;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export interface SuggestSeoFiltersRequest {
|
|
127
|
+
categoryId: string;
|
|
128
|
+
/** default 20 */
|
|
129
|
+
limit: number;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export interface SuggestSeoFilterCandidate {
|
|
133
|
+
/** Запропоновані combo_parts (валідні, з resolved slug). */
|
|
134
|
+
comboParts: FilterPart[];
|
|
135
|
+
/** Оцінка популярності: clicks / impressions / товарообіг (single число). */
|
|
136
|
+
score: number;
|
|
137
|
+
/** Чи існує вже SeoFilter з таким комбо (тоді UI ховає кнопку Create). */
|
|
138
|
+
alreadyExists: boolean;
|
|
139
|
+
/** Pre-built canonical URL path для preview. */
|
|
140
|
+
previewSegments: LocaleStrings | undefined;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export interface SuggestSeoFiltersResponse {
|
|
144
|
+
items: SuggestSeoFilterCandidate[];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export const CATALOG_V1_PACKAGE_NAME = "catalog.v1";
|
|
148
|
+
|
|
149
|
+
/** ─── Public ────────────────────────────────────────────────────────────── */
|
|
150
|
+
|
|
151
|
+
export interface SeoFilterServiceClient {
|
|
152
|
+
/**
|
|
153
|
+
* Резолв ЧПУ-URL → metadata + canonical. Викликається SSR Next.js для
|
|
154
|
+
* кожного category/[[...filters]] запиту. Якщо combo не знайдено —
|
|
155
|
+
* повертає isIndexed=false і автогенерований fallback metadata.
|
|
156
|
+
*/
|
|
157
|
+
|
|
158
|
+
resolveSeoFilter(request: ResolveSeoFilterRequest): Observable<ResolveSeoFilterResponse>;
|
|
159
|
+
|
|
160
|
+
listSeoFilters(request: ListSeoFiltersRequest): Observable<ListSeoFiltersResponse>;
|
|
161
|
+
|
|
162
|
+
getSeoFilter(request: GetSeoFilterRequest): Observable<SeoFilterResponse>;
|
|
163
|
+
|
|
164
|
+
createSeoFilter(request: CreateSeoFilterRequest): Observable<SeoFilterResponse>;
|
|
165
|
+
|
|
166
|
+
updateSeoFilter(request: UpdateSeoFilterRequest): Observable<SeoFilterResponse>;
|
|
167
|
+
|
|
168
|
+
deleteSeoFilter(request: DeleteSeoFilterRequest): Observable<DeleteResponse>;
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Топ-N непокритих combinations (за impressions/popularity) — для
|
|
172
|
+
* швидкого створення SeoFilter з адмін-форми "Автопідказки".
|
|
173
|
+
*/
|
|
174
|
+
|
|
175
|
+
suggestSeoFilters(request: SuggestSeoFiltersRequest): Observable<SuggestSeoFiltersResponse>;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** ─── Public ────────────────────────────────────────────────────────────── */
|
|
179
|
+
|
|
180
|
+
export interface SeoFilterServiceController {
|
|
181
|
+
/**
|
|
182
|
+
* Резолв ЧПУ-URL → metadata + canonical. Викликається SSR Next.js для
|
|
183
|
+
* кожного category/[[...filters]] запиту. Якщо combo не знайдено —
|
|
184
|
+
* повертає isIndexed=false і автогенерований fallback metadata.
|
|
185
|
+
*/
|
|
186
|
+
|
|
187
|
+
resolveSeoFilter(
|
|
188
|
+
request: ResolveSeoFilterRequest,
|
|
189
|
+
): Promise<ResolveSeoFilterResponse> | Observable<ResolveSeoFilterResponse> | ResolveSeoFilterResponse;
|
|
190
|
+
|
|
191
|
+
listSeoFilters(
|
|
192
|
+
request: ListSeoFiltersRequest,
|
|
193
|
+
): Promise<ListSeoFiltersResponse> | Observable<ListSeoFiltersResponse> | ListSeoFiltersResponse;
|
|
194
|
+
|
|
195
|
+
getSeoFilter(
|
|
196
|
+
request: GetSeoFilterRequest,
|
|
197
|
+
): Promise<SeoFilterResponse> | Observable<SeoFilterResponse> | SeoFilterResponse;
|
|
198
|
+
|
|
199
|
+
createSeoFilter(
|
|
200
|
+
request: CreateSeoFilterRequest,
|
|
201
|
+
): Promise<SeoFilterResponse> | Observable<SeoFilterResponse> | SeoFilterResponse;
|
|
202
|
+
|
|
203
|
+
updateSeoFilter(
|
|
204
|
+
request: UpdateSeoFilterRequest,
|
|
205
|
+
): Promise<SeoFilterResponse> | Observable<SeoFilterResponse> | SeoFilterResponse;
|
|
206
|
+
|
|
207
|
+
deleteSeoFilter(
|
|
208
|
+
request: DeleteSeoFilterRequest,
|
|
209
|
+
): Promise<DeleteResponse> | Observable<DeleteResponse> | DeleteResponse;
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Топ-N непокритих combinations (за impressions/popularity) — для
|
|
213
|
+
* швидкого створення SeoFilter з адмін-форми "Автопідказки".
|
|
214
|
+
*/
|
|
215
|
+
|
|
216
|
+
suggestSeoFilters(
|
|
217
|
+
request: SuggestSeoFiltersRequest,
|
|
218
|
+
): Promise<SuggestSeoFiltersResponse> | Observable<SuggestSeoFiltersResponse> | SuggestSeoFiltersResponse;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function SeoFilterServiceControllerMethods() {
|
|
222
|
+
return function (constructor: Function) {
|
|
223
|
+
const grpcMethods: string[] = [
|
|
224
|
+
"resolveSeoFilter",
|
|
225
|
+
"listSeoFilters",
|
|
226
|
+
"getSeoFilter",
|
|
227
|
+
"createSeoFilter",
|
|
228
|
+
"updateSeoFilter",
|
|
229
|
+
"deleteSeoFilter",
|
|
230
|
+
"suggestSeoFilters",
|
|
231
|
+
];
|
|
232
|
+
for (const method of grpcMethods) {
|
|
233
|
+
const descriptor: any = Reflect.getOwnPropertyDescriptor(constructor.prototype, method);
|
|
234
|
+
GrpcMethod("SeoFilterService", method)(constructor.prototype[method], method, descriptor);
|
|
235
|
+
}
|
|
236
|
+
const grpcStreamMethods: string[] = [];
|
|
237
|
+
for (const method of grpcStreamMethods) {
|
|
238
|
+
const descriptor: any = Reflect.getOwnPropertyDescriptor(constructor.prototype, method);
|
|
239
|
+
GrpcStreamMethod("SeoFilterService", method)(constructor.prototype[method], method, descriptor);
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export const SEO_FILTER_SERVICE_NAME = "SeoFilterService";
|